fix
This commit is contained in:
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/application/api/dto"
|
||||
@@ -37,6 +38,12 @@ type ApiApplicationService interface {
|
||||
|
||||
// 管理端API调用记录
|
||||
GetAdminApiCalls(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*dto.ApiCallListResponse, error)
|
||||
|
||||
// 加密参数接口
|
||||
EncryptParams(ctx context.Context, userID string, cmd *commands.EncryptCommand) (string, error)
|
||||
|
||||
// 解密参数接口
|
||||
DecryptParams(ctx context.Context, userID string, cmd *commands.DecryptCommand) (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
type ApiApplicationServiceImpl struct {
|
||||
@@ -102,9 +109,13 @@ func (s *ApiApplicationServiceImpl) CallApi(ctx context.Context, cmd *commands.A
|
||||
businessError = ErrFrozenAccount
|
||||
return ErrFrozenAccount
|
||||
}
|
||||
// 在开发环境下跳过IP白名单校验
|
||||
if s.config.App.IsDevelopment() {
|
||||
s.logger.Info("开发环境跳过IP白名单校验", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
|
||||
// 在开发环境或调试模式下跳过IP白名单校验
|
||||
if s.config.App.IsDevelopment() || cmd.Options.IsDebug {
|
||||
s.logger.Info("跳过IP白名单校验",
|
||||
zap.String("userId", apiUser.UserId),
|
||||
zap.String("ip", cmd.ClientIP),
|
||||
zap.Bool("isDevelopment", s.config.App.IsDevelopment()),
|
||||
zap.Bool("isDebug", cmd.Options.IsDebug))
|
||||
} else {
|
||||
if !apiUser.IsWhiteListed(cmd.ClientIP) {
|
||||
s.logger.Error("IP不在白名单内", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
|
||||
@@ -503,3 +514,42 @@ func (s *ApiApplicationServiceImpl) GetAdminApiCalls(ctx context.Context, filter
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EncryptParams 加密参数
|
||||
func (s *ApiApplicationServiceImpl) EncryptParams(ctx context.Context, userID string, cmd *commands.EncryptCommand) (string, error) {
|
||||
// 1. 将数据转换为JSON字节数组
|
||||
jsonData, err := json.Marshal(cmd.Data)
|
||||
if err != nil {
|
||||
s.logger.Error("序列化参数失败", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 2. 使用前端传来的SecretKey进行加密
|
||||
encryptedData, err := crypto.AesEncrypt(jsonData, cmd.SecretKey)
|
||||
if err != nil {
|
||||
s.logger.Error("加密参数失败", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
return encryptedData, nil
|
||||
}
|
||||
|
||||
// DecryptParams 解密参数
|
||||
func (s *ApiApplicationServiceImpl) DecryptParams(ctx context.Context, userID string, cmd *commands.DecryptCommand) (map[string]interface{}, error) {
|
||||
// 1. 使用前端传来的SecretKey进行解密
|
||||
decryptedData, err := crypto.AesDecrypt(cmd.EncryptedData, cmd.SecretKey)
|
||||
if err != nil {
|
||||
s.logger.Error("解密参数失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 将解密后的JSON字节数组转换为map
|
||||
var result map[string]interface{}
|
||||
err = json.Unmarshal(decryptedData, &result)
|
||||
if err != nil {
|
||||
s.logger.Error("反序列化解密数据失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -9,10 +9,18 @@ type ApiCallCommand struct {
|
||||
}
|
||||
|
||||
type ApiCallOptions struct {
|
||||
Json bool `json:"json,omitempty"` // 是否返回JSON格式
|
||||
Json bool `json:"json,omitempty"` // 是否返回JSON格式
|
||||
IsDebug bool `json:"is_debug,omitempty"` // 是否为调试调用
|
||||
}
|
||||
|
||||
// EncryptCommand 加密命令
|
||||
type EncryptCommand struct {
|
||||
Data map[string]interface{} `json:"data" binding:"required"`
|
||||
Data map[string]interface{} `json:"data" binding:"required"`
|
||||
SecretKey string `json:"secret_key" binding:"required"`
|
||||
}
|
||||
|
||||
// DecryptCommand 解密命令
|
||||
type DecryptCommand struct {
|
||||
EncryptedData string `json:"encrypted_data" binding:"required"`
|
||||
SecretKey string `json:"secret_key" binding:"required"`
|
||||
}
|
||||
|
||||
@@ -214,13 +214,18 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(s
|
||||
}
|
||||
}
|
||||
|
||||
var productResponse *responses.ProductSimpleResponse
|
||||
if subscription.Product != nil {
|
||||
productResponse = s.convertToProductSimpleResponse(subscription.Product)
|
||||
}
|
||||
|
||||
return &responses.SubscriptionInfoResponse{
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price.InexactFloat64(),
|
||||
User: userInfo,
|
||||
Product: s.convertToProductSimpleResponse(subscription.Product),
|
||||
Product: productResponse,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
@@ -229,6 +234,11 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(s
|
||||
|
||||
// convertToProductSimpleResponse 转换为产品简单信息响应
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(product *entities.Product) *responses.ProductSimpleResponse {
|
||||
var categoryResponse *responses.CategorySimpleResponse
|
||||
if product.Category != nil {
|
||||
categoryResponse = s.convertToCategorySimpleResponse(product.Category)
|
||||
}
|
||||
|
||||
return &responses.ProductSimpleResponse{
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
@@ -236,13 +246,17 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(prod
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
Category: s.convertToCategorySimpleResponse(product.Category),
|
||||
Category: categoryResponse,
|
||||
IsPackage: product.IsPackage,
|
||||
}
|
||||
}
|
||||
|
||||
// convertToCategorySimpleResponse 转换为分类简单信息响应
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
|
||||
if category == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &responses.CategorySimpleResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
|
||||
@@ -51,7 +51,7 @@ func ProcessIVYZ9363Request(ctx context.Context, params []byte, deps *processors
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G10SC02", reqData)
|
||||
respBytes, err := deps.WestDexService.CallAPI("G10XM02", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
"tyapi-server/internal/application/api"
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/application/api/dto"
|
||||
"tyapi-server/internal/shared/crypto"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -194,24 +192,8 @@ func (h *ApiHandler) EncryptParams(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户的SecretKey
|
||||
apiKeys, err := h.appService.GetUserApiKeys(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户API密钥失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "获取API密钥失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 将JSON对象转换为字节数组
|
||||
jsonData, err := json.Marshal(cmd.Data)
|
||||
if err != nil {
|
||||
h.logger.Error("序列化参数失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "参数序列化失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 加密参数
|
||||
encryptedData, err := crypto.AesEncrypt(jsonData, apiKeys.SecretKey)
|
||||
// 调用应用服务层进行加密
|
||||
encryptedData, err := h.appService.EncryptParams(c.Request.Context(), userID, &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("加密参数失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "加密参数失败")
|
||||
@@ -224,6 +206,43 @@ func (h *ApiHandler) EncryptParams(c *gin.Context) {
|
||||
h.responseBuilder.Success(c, response, "加密成功")
|
||||
}
|
||||
|
||||
// DecryptParams 解密参数
|
||||
// @Summary 解密参数
|
||||
// @Description 使用密钥解密加密的数据
|
||||
// @Tags API调试
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.DecryptCommand true "解密请求"
|
||||
// @Success 200 {object} map[string]interface{} "解密成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未授权"
|
||||
// @Failure 500 {object} map[string]interface{} "解密失败"
|
||||
// @Router /api/v1/decrypt [post]
|
||||
func (h *ApiHandler) DecryptParams(c *gin.Context) {
|
||||
userID := h.getCurrentUserID(c)
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未登录")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.DecryptCommand
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用应用服务层进行解密
|
||||
decryptedData, err := h.appService.DecryptParams(c.Request.Context(), userID, &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("解密参数失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "解密参数失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, decryptedData, "解密成功")
|
||||
}
|
||||
|
||||
// getCurrentUserID 获取当前用户ID
|
||||
func (h *ApiHandler) getCurrentUserID(c *gin.Context) string {
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
|
||||
@@ -43,6 +43,9 @@ func (r *ApiRoutes) Register(router *sharedhttp.GinRouter) {
|
||||
// 加密接口(用于前端调试)
|
||||
apiGroup.POST("/encrypt", r.authMiddleware.Handle(), r.apiHandler.EncryptParams)
|
||||
|
||||
// 解密接口(用于前端调试)
|
||||
apiGroup.POST("/decrypt", r.authMiddleware.Handle(), r.apiHandler.DecryptParams)
|
||||
|
||||
// API密钥管理接口
|
||||
apiGroup.GET("/api-keys", r.authMiddleware.Handle(), r.apiHandler.GetUserApiKeys)
|
||||
|
||||
|
||||
@@ -25,9 +25,31 @@ func RegisterCustomValidators(validate *validator.Validate) {
|
||||
// 统一社会信用代码验证器
|
||||
validate.RegisterValidation("social_credit_code", validateSocialCreditCode)
|
||||
|
||||
// 身份证号验证器
|
||||
// 姓名验证器(不能为空字符串,长度1-50字符)
|
||||
validate.RegisterValidation("validName", validateName)
|
||||
|
||||
// 身份证号验证器(兼容两种标签)
|
||||
validate.RegisterValidation("validIDCard", validateIDCard)
|
||||
validate.RegisterValidation("id_card", validateIDCard)
|
||||
|
||||
// 统一社会信用代码验证器
|
||||
validate.RegisterValidation("validUSCI", validateUSCI)
|
||||
|
||||
// 手机号验证器
|
||||
validate.RegisterValidation("validMobileNo", validateMobileNo)
|
||||
|
||||
// 手机类型验证器
|
||||
validate.RegisterValidation("validMobileType", validateMobileType)
|
||||
|
||||
// 日期验证器
|
||||
validate.RegisterValidation("validDate", validateDate)
|
||||
|
||||
// 时间范围验证器
|
||||
validate.RegisterValidation("validTimeRange", validateTimeRange)
|
||||
|
||||
// 银行卡验证器
|
||||
validate.RegisterValidation("validBankCard", validateBankCard)
|
||||
|
||||
// 价格验证器(非负数)
|
||||
validate.RegisterValidation("price", validatePrice)
|
||||
|
||||
@@ -52,6 +74,9 @@ func RegisterCustomValidators(validate *validator.Validate) {
|
||||
// IP地址验证器
|
||||
validate.RegisterValidation("ip", validateIP)
|
||||
|
||||
// 非空字符串验证器(不能为空字符串或只包含空格)
|
||||
validate.RegisterValidation("notEmpty", validateNotEmpty)
|
||||
|
||||
// 授权日期验证器
|
||||
validate.RegisterValidation("auth_date", validateAuthDate)
|
||||
}
|
||||
@@ -158,6 +183,20 @@ func validateIP(fl validator.FieldLevel) bool {
|
||||
return matched
|
||||
}
|
||||
|
||||
// validateName 姓名验证器
|
||||
func validateName(fl validator.FieldLevel) bool {
|
||||
name := fl.Field().String()
|
||||
// 去除首尾空格后检查长度
|
||||
trimmedName := strings.TrimSpace(name)
|
||||
if len(trimmedName) < 1 || len(trimmedName) > 50 {
|
||||
return false
|
||||
}
|
||||
// 姓名不能只包含空格或特殊字符
|
||||
// 必须包含至少一个中文字符或英文字母
|
||||
hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName)
|
||||
return hasValidChar
|
||||
}
|
||||
|
||||
// validateAuthDate 授权日期验证器
|
||||
// 格式:YYYYMMDD-YYYYMMDD,之前的日期范围必须包括今天
|
||||
func validateAuthDate(fl validator.FieldLevel) bool {
|
||||
@@ -237,3 +276,126 @@ func parseYYYYMMDD(dateStr string) (time.Time, error) {
|
||||
|
||||
return date, nil
|
||||
}
|
||||
|
||||
// validateUSCI 统一社会信用代码验证器
|
||||
func validateUSCI(fl validator.FieldLevel) bool {
|
||||
usci := fl.Field().String()
|
||||
// 统一社会信用代码格式:18位,由数字和大写字母组成
|
||||
// 格式:1位登记管理部门代码 + 1位机构类别代码 + 6位登记管理机关行政区划码 + 9位主体标识码 + 1位校验码
|
||||
matched, _ := regexp.MatchString(`^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$`, usci)
|
||||
return matched
|
||||
}
|
||||
|
||||
// validateMobileNo 手机号验证器
|
||||
func validateMobileNo(fl validator.FieldLevel) bool {
|
||||
mobile := fl.Field().String()
|
||||
// 中国手机号格式:1开头,第二位是3-9,总共11位数字
|
||||
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, mobile)
|
||||
return matched
|
||||
}
|
||||
|
||||
// validateMobileType 手机类型验证器
|
||||
func validateMobileType(fl validator.FieldLevel) bool {
|
||||
mobileType := fl.Field().String()
|
||||
// 手机类型:移动、联通、电信等
|
||||
validTypes := []string{"移动", "联通", "电信", "广电", "虚拟运营商"}
|
||||
for _, validType := range validTypes {
|
||||
if mobileType == validType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateDate 日期验证器
|
||||
func validateDate(fl validator.FieldLevel) bool {
|
||||
dateStr := fl.Field().String()
|
||||
// 检查日期格式:YYYY-MM-DD
|
||||
matched, _ := regexp.MatchString(`^\d{4}-\d{2}-\d{2}$`, dateStr)
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
|
||||
// 尝试解析日期
|
||||
_, err := time.Parse("2006-01-02", dateStr)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// validateTimeRange 时间范围验证器
|
||||
func validateTimeRange(fl validator.FieldLevel) bool {
|
||||
timeRange := fl.Field().String()
|
||||
if timeRange == "" {
|
||||
return true // 空值由omitempty标签处理
|
||||
}
|
||||
|
||||
// 时间范围格式:HH:MM-HH:MM
|
||||
parts := strings.Split(timeRange, "-")
|
||||
if len(parts) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
startTime := parts[0]
|
||||
endTime := parts[1]
|
||||
|
||||
// 检查时间格式:HH:MM
|
||||
timePattern := `^([01]?[0-9]|2[0-3]):[0-5][0-9]$`
|
||||
startMatched, _ := regexp.MatchString(timePattern, startTime)
|
||||
endMatched, _ := regexp.MatchString(timePattern, endTime)
|
||||
|
||||
if !startMatched || !endMatched {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查开始时间不能晚于结束时间
|
||||
start, _ := time.Parse("15:04", startTime)
|
||||
end, _ := time.Parse("15:04", endTime)
|
||||
|
||||
return start.Before(end) || start.Equal(end)
|
||||
}
|
||||
|
||||
// validateNotEmpty 非空字符串验证器
|
||||
func validateNotEmpty(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
// 去除首尾空格后检查是否为空
|
||||
trimmedValue := strings.TrimSpace(value)
|
||||
return len(trimmedValue) > 0
|
||||
}
|
||||
|
||||
// validateBankCard 银行卡验证器
|
||||
func validateBankCard(fl validator.FieldLevel) bool {
|
||||
bankCard := fl.Field().String()
|
||||
// 银行卡号格式:13-19位数字
|
||||
matched, _ := regexp.MatchString(`^\d{13,19}$`, bankCard)
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
|
||||
// 使用Luhn算法验证银行卡号
|
||||
return validateLuhn(bankCard)
|
||||
}
|
||||
|
||||
// validateLuhn Luhn算法验证银行卡号
|
||||
func validateLuhn(cardNumber string) bool {
|
||||
sum := 0
|
||||
alternate := false
|
||||
|
||||
// 从右到左遍历
|
||||
for i := len(cardNumber) - 1; i >= 0; i-- {
|
||||
digit, err := strconv.Atoi(string(cardNumber[i]))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if alternate {
|
||||
digit *= 2
|
||||
if digit > 9 {
|
||||
digit = digit%10 + digit/10
|
||||
}
|
||||
}
|
||||
|
||||
sum += digit
|
||||
alternate = !alternate
|
||||
}
|
||||
|
||||
return sum%10 == 0
|
||||
}
|
||||
Reference in New Issue
Block a user