From 347306426f954a140fb7982d9988a977fc9f9bff Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Sat, 28 Mar 2026 12:40:01 +0800 Subject: [PATCH 1/5] f --- .../api/internal/logic/admin_order/admingetorderlistlogic.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go index 1712be2..9084458 100644 --- a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go +++ b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go @@ -85,7 +85,7 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR return nil }, func() error { var err error - orders, err = l.svcCtx.OrderModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "id DESC") + orders, err = l.svcCtx.OrderModel.FindPageListByPage(l.ctx, builder, req.Page, req.PageSize, "create_time DESC") if err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询订单列表失败 err: %v", err) } From 323f14e1f5c44fad32f02eae95b4e86f19b48154 Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Thu, 16 Apr 2026 16:56:04 +0800 Subject: [PATCH 2/5] pull --- .../internal/service/verificationService.go | 109 +++++++++--------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/app/main/api/internal/service/verificationService.go b/app/main/api/internal/service/verificationService.go index 8ba5e6e..4132d9e 100644 --- a/app/main/api/internal/service/verificationService.go +++ b/app/main/api/internal/service/verificationService.go @@ -1,26 +1,27 @@ package service import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "ycc-server/app/main/api/internal/config" - tianyuanapi "ycc-server/app/main/api/internal/service/tianyuanapi_sdk" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "ycc-server/app/main/api/internal/config" + tianyuanapi "ycc-server/app/main/api/internal/service/tianyuanapi_sdk" - "github.com/tidwall/gjson" + "github.com/tidwall/gjson" ) + type VerificationService struct { c config.Config - tianyuanapi *tianyuanapi.Client + tianyuanapi *tianyuanapi.Client apiRequestService *ApiRequestService } func NewVerificationService(c config.Config, tianyuanapi *tianyuanapi.Client, apiRequestService *ApiRequestService) *VerificationService { return &VerificationService{ c: c, - tianyuanapi: tianyuanapi, + tianyuanapi: tianyuanapi, apiRequestService: apiRequestService, } } @@ -156,52 +157,52 @@ func (r *VerificationService) ThreeFactorVerification(request ThreeFactorVerific // GetWechatH5OpenID 通过code获取微信H5 OpenID func (r *VerificationService) GetWechatH5OpenID(ctx context.Context, code string) (string, error) { - appID := r.c.WechatH5.AppID - appSecret := r.c.WechatH5.AppSecret - url := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appID, appSecret, code) - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - var data struct { - Openid string `json:"openid"` - } - if err := json.Unmarshal(body, &data); err != nil { - return "", err - } - if data.Openid == "" { - return "", fmt.Errorf("openid为空") - } - return data.Openid, nil + appID := r.c.WechatH5.AppID + appSecret := r.c.WechatH5.AppSecret + url := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appID, appSecret, code) + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + var data struct { + Openid string `json:"openid"` + } + if err := json.Unmarshal(body, &data); err != nil { + return "", err + } + if data.Openid == "" { + return "", fmt.Errorf("openid为空") + } + return data.Openid, nil } // GetWechatMiniOpenID 通过code获取微信小程序 OpenID func (r *VerificationService) GetWechatMiniOpenID(ctx context.Context, code string) (string, error) { - appID := r.c.WechatMini.AppID - appSecret := r.c.WechatMini.AppSecret - url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appID, appSecret, code) - resp, err := http.Get(url) - if err != nil { - return "", err - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - var data struct { - Openid string `json:"openid"` - } - if err := json.Unmarshal(body, &data); err != nil { - return "", err - } - if data.Openid == "" { - return "", fmt.Errorf("openid为空") - } - return data.Openid, nil + appID := r.c.WechatMini.AppID + appSecret := r.c.WechatMini.AppSecret + url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appID, appSecret, code) + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + var data struct { + Openid string `json:"openid"` + } + if err := json.Unmarshal(body, &data); err != nil { + return "", err + } + if data.Openid == "" { + return "", fmt.Errorf("openid为空") + } + return data.Openid, nil } From 81cd477c62df79e5889059e590f3bbd782d885a4 Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Fri, 8 May 2026 16:43:34 +0800 Subject: [PATCH 3/5] f --- app/main/api/desc/admin/admin_query.api | 20 +++---- .../admingetquerydetailbyorderidlogic.go | 52 +++++++++++++++---- app/main/api/internal/types/types.go | 20 +++---- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/app/main/api/desc/admin/admin_query.api b/app/main/api/desc/admin/admin_query.api index 904104f..bdfe3d2 100644 --- a/app/main/api/desc/admin/admin_query.api +++ b/app/main/api/desc/admin/admin_query.api @@ -42,15 +42,17 @@ service main { } type AdminGetQueryDetailByOrderIdResp { - Id string `json:"id"` // 主键ID - OrderId string `json:"order_id"` // 订单ID - UserId string `json:"user_id"` // 用户ID - ProductName string `json:"product_name"` // 产品ID - QueryParams map[string]interface{} `json:"query_params"` - QueryData []AdminQueryItem `json:"query_data"` - CreateTime string `json:"create_time"` // 创建时间 - UpdateTime string `json:"update_time"` // 更新时间 - QueryState string `json:"query_state"` // 查询状态 + Id string `json:"id"` // 主键ID + OrderId string `json:"order_id"` // 订单ID + UserId string `json:"user_id"` // 用户ID + ProductName string `json:"product_name"` // 产品名称 + QueryParams map[string]interface{} `json:"query_params"` + QueryData []AdminQueryItem `json:"query_data"` + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + QueryState string `json:"query_state"` // 查询状态 + AgentUserName string `json:"agent_user_name"` // 代理用户姓名(非代理单时为空) + AgentUserMobile string `json:"agent_user_mobile"` // 代理用户手机号(非代理单时为空) } type AdminQueryItem { diff --git a/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go b/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go index cdb48cc..2a0e31b 100644 --- a/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go +++ b/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go @@ -72,16 +72,50 @@ func (l *AdminGetQueryDetailByOrderIdLogic) AdminGetQueryDetailByOrderId(req *ty return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err) } query.ProductName = product.ProductName + + // 查询代理订单信息,判断是否是代理单 + var agentUserName string + var agentUserMobile string + agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(l.ctx, queryModel.OrderId) + if err == nil && agentOrder != nil { + // 是代理单,查询代理实名信息获取姓名 + realNameInfo, realNameErr := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentOrder.AgentId) + if realNameErr == nil && realNameInfo != nil { + agentUserName = realNameInfo.Name + // 解密实名认证中的手机号 + if realNameInfo.Mobile != "" { + key2, keyErr := hex.DecodeString(l.svcCtx.Config.Encrypt.SecretKey) + if keyErr == nil { + decryptedMobile, decryptErr := crypto.AesDecrypt(realNameInfo.Mobile, key2) + if decryptErr == nil { + agentUserMobile = string(decryptedMobile) + } + } + } + } else { + // 代理没有实名认证信息,尝试从Agent表获取手机号 + agentInfo, agentErr := l.svcCtx.AgentModel.FindOne(l.ctx, agentOrder.AgentId) + if agentErr == nil && agentInfo != nil { + decryptedMobile, decryptErr := crypto.DecryptMobile(agentInfo.Mobile, l.svcCtx.Config.Encrypt.SecretKey) + if decryptErr == nil { + agentUserMobile = decryptedMobile + } + } + } + } + return &types.AdminGetQueryDetailByOrderIdResp{ - Id: query.Id, - OrderId: query.OrderId, - UserId: query.UserId, - ProductName: query.ProductName, - QueryParams: query.QueryParams, - QueryData: query.QueryData, - CreateTime: query.CreateTime, - UpdateTime: query.UpdateTime, - QueryState: query.QueryState, + Id: query.Id, + OrderId: query.OrderId, + UserId: query.UserId, + ProductName: query.ProductName, + QueryParams: query.QueryParams, + QueryData: query.QueryData, + CreateTime: query.CreateTime, + UpdateTime: query.UpdateTime, + QueryState: query.QueryState, + AgentUserName: agentUserName, + AgentUserMobile: agentUserMobile, }, nil } diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index f62f93e..8575660 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -693,15 +693,17 @@ type AdminGetQueryDetailByOrderIdReq struct { } type AdminGetQueryDetailByOrderIdResp struct { - Id string `json:"id"` // 主键ID - OrderId string `json:"order_id"` // 订单ID - UserId string `json:"user_id"` // 用户ID - ProductName string `json:"product_name"` // 产品ID - QueryParams map[string]interface{} `json:"query_params"` - QueryData []AdminQueryItem `json:"query_data"` - CreateTime string `json:"create_time"` // 创建时间 - UpdateTime string `json:"update_time"` // 更新时间 - QueryState string `json:"query_state"` // 查询状态 + Id string `json:"id"` // 主键ID + OrderId string `json:"order_id"` // 订单ID + UserId string `json:"user_id"` // 用户ID + ProductName string `json:"product_name"` // 产品名称 + QueryParams map[string]interface{} `json:"query_params"` + QueryData []AdminQueryItem `json:"query_data"` + CreateTime string `json:"create_time"` // 创建时间 + UpdateTime string `json:"update_time"` // 更新时间 + QueryState string `json:"query_state"` // 查询状态 + AgentUserName string `json:"agent_user_name"` // 代理用户姓名(非代理单时为空) + AgentUserMobile string `json:"agent_user_mobile"` // 代理用户手机号(非代理单时为空) } type AdminGetRoleApiListReq struct { From 7aa4fb60f696ea78cf3d2b5a06699b0671fb11ec Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Fri, 8 May 2026 16:52:47 +0800 Subject: [PATCH 4/5] f --- .../admingetquerydetailbyorderidlogic.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go b/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go index 2a0e31b..f67c0bb 100644 --- a/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go +++ b/app/main/api/internal/logic/admin_query/admingetquerydetailbyorderidlogic.go @@ -78,22 +78,20 @@ func (l *AdminGetQueryDetailByOrderIdLogic) AdminGetQueryDetailByOrderId(req *ty var agentUserMobile string agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(l.ctx, queryModel.OrderId) if err == nil && agentOrder != nil { - // 是代理单,查询代理实名信息获取姓名 + // 是代理单,查询代理实名信息获取姓名和手机号 realNameInfo, realNameErr := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentOrder.AgentId) if realNameErr == nil && realNameInfo != nil { agentUserName = realNameInfo.Name - // 解密实名认证中的手机号 + // 解密实名认证中的手机号(ECB加密,使用 DecryptMobile) if realNameInfo.Mobile != "" { - key2, keyErr := hex.DecodeString(l.svcCtx.Config.Encrypt.SecretKey) - if keyErr == nil { - decryptedMobile, decryptErr := crypto.AesDecrypt(realNameInfo.Mobile, key2) - if decryptErr == nil { - agentUserMobile = string(decryptedMobile) - } + decryptedMobile, decryptErr := crypto.DecryptMobile(realNameInfo.Mobile, l.svcCtx.Config.Encrypt.SecretKey) + if decryptErr == nil { + agentUserMobile = decryptedMobile } } - } else { - // 代理没有实名认证信息,尝试从Agent表获取手机号 + } + // 如果实名认证中没有手机号,回退到Agent表获取 + if agentUserMobile == "" { agentInfo, agentErr := l.svcCtx.AgentModel.FindOne(l.ctx, agentOrder.AgentId) if agentErr == nil && agentInfo != nil { decryptedMobile, decryptErr := crypto.DecryptMobile(agentInfo.Mobile, l.svcCtx.Config.Encrypt.SecretKey) From 93886e2e906601aa1c60bf50b4480a25642d151c Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Sat, 9 May 2026 12:15:25 +0800 Subject: [PATCH 5/5] f --- app/main/api/desc/admin/order.api | 4 + app/main/api/internal/handler/routes.go | 10 +- .../admin_order/admingetorderlistlogic.go | 42 +++++ .../api/internal/pkg/querysubject/extract.go | 60 +++++++ .../api/internal/queue/paySuccessNotify.go | 54 +++++- app/main/api/internal/svc/servicecontext.go | 3 + app/main/api/internal/types/types.go | 60 +++---- app/main/model/querySubjectIndexModel.go | 27 +++ app/main/model/querySubjectIndexModel_gen.go | 155 ++++++++++++++++++ deploy/sql/query_subject_index_migration.sql | 26 +++ pkg/lzkit/crypto/ecb.go | 12 ++ 11 files changed, 413 insertions(+), 40 deletions(-) create mode 100644 app/main/api/internal/pkg/querysubject/extract.go create mode 100644 app/main/model/querySubjectIndexModel.go create mode 100644 app/main/model/querySubjectIndexModel_gen.go create mode 100644 deploy/sql/query_subject_index_migration.sql diff --git a/app/main/api/desc/admin/order.api b/app/main/api/desc/admin/order.api index 47c7d1f..416c9cf 100644 --- a/app/main/api/desc/admin/order.api +++ b/app/main/api/desc/admin/order.api @@ -59,6 +59,10 @@ type ( PayTimeEnd string `form:"pay_time_end,optional"` // 支付时间结束 RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始 RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束 + // 被查询人(与 query_subject_index 密文一致,多条件为 AND) + QuerySubjectName string `form:"query_subject_name,optional"` // 姓名(明文,服务端转密文查询) + QuerySubjectMobile string `form:"query_subject_mobile,optional"` // 手机号 + QuerySubjectIdCard string `form:"query_subject_id_card,optional"` // 身份证 } // 列表响应 AdminGetOrderListResp { diff --git a/app/main/api/internal/handler/routes.go b/app/main/api/internal/handler/routes.go index 3301740..4fe80b8 100644 --- a/app/main/api/internal/handler/routes.go +++ b/app/main/api/internal/handler/routes.go @@ -79,6 +79,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/list", Handler: admin_agent.AdminGetAgentListHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/mobile/update", + Handler: admin_agent.AdminUpdateAgentMobileHandler(serverCtx), + }, { Method: http.MethodGet, Path: "/order/list", @@ -94,11 +99,6 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/product_config/update", Handler: admin_agent.AdminUpdateAgentProductConfigHandler(serverCtx), }, - { - Method: http.MethodPost, - Path: "/mobile/update", - Handler: admin_agent.AdminUpdateAgentMobileHandler(serverCtx), - }, { Method: http.MethodGet, Path: "/real_name/list", diff --git a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go index 9084458..9298923 100644 --- a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go +++ b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go @@ -2,12 +2,16 @@ package admin_order import ( "context" + "encoding/hex" + "strings" "sync" "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" "ycc-server/app/main/model" + "ycc-server/common/globalkey" "ycc-server/common/xerr" + "ycc-server/pkg/lzkit/crypto" "github.com/Masterminds/squirrel" "github.com/pkg/errors" @@ -72,6 +76,44 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR if req.RefundTimeEnd != "" { builder = builder.Where("refund_time <= ?", req.RefundTimeEnd) } + if req.QuerySubjectName != "" || req.QuerySubjectMobile != "" || req.QuerySubjectIdCard != "" { + key, decodeErr := hex.DecodeString(l.svcCtx.Config.Encrypt.SecretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "AdminGetOrderList, AES密钥解析失败 err: %v", decodeErr) + } + conds := []string{"del_state = ?"} + args := []interface{}{globalkey.DelStateNo} + if req.QuerySubjectName != "" { + enc, _, encErr := crypto.EncryptDeterministicOptional(req.QuerySubjectName, key) + if encErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "AdminGetOrderList, 加密筛选姓名失败 err: %v", encErr) + } + conds = append(conds, "enc_real_name = ?") + args = append(args, enc) + } + if req.QuerySubjectMobile != "" { + enc, encErr := crypto.EncryptMobile(req.QuerySubjectMobile, l.svcCtx.Config.Encrypt.SecretKey) + if encErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "AdminGetOrderList, 加密筛选手机号失败 err: %v", encErr) + } + conds = append(conds, "enc_mobile = ?") + args = append(args, enc) + } + if req.QuerySubjectIdCard != "" { + enc, encErr := crypto.EncryptIDCard(req.QuerySubjectIdCard, key) + if encErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), + "AdminGetOrderList, 加密筛选身份证失败 err: %v", encErr) + } + conds = append(conds, "enc_id_card = ?") + args = append(args, enc) + } + subSQL := "id IN (SELECT order_id FROM query_subject_index WHERE " + strings.Join(conds, " AND ") + ")" + builder = builder.Where(subSQL, args...) + } // 并发获取总数和列表 var total int64 diff --git a/app/main/api/internal/pkg/querysubject/extract.go b/app/main/api/internal/pkg/querysubject/extract.go new file mode 100644 index 0000000..b05a1da --- /dev/null +++ b/app/main/api/internal/pkg/querysubject/extract.go @@ -0,0 +1,60 @@ +package querysubject + +import ( + "strings" +) + +func isNameField(key string) bool { + key = strings.ToLower(key) + if isPhoneField(key) || isIDCardField(key) { + return false + } + return strings.Contains(key, "name") || strings.Contains(key, "姓名") || + strings.Contains(key, "owner") || strings.Contains(key, "main") +} + +func isIDCardField(key string) bool { + key = strings.ToLower(key) + return strings.Contains(key, "idcard") || strings.Contains(key, "id_card") || + strings.Contains(key, "身份证") || strings.Contains(key, "证件号") +} + +func isPhoneField(key string) bool { + key = strings.ToLower(key) + return strings.Contains(key, "phone") || strings.Contains(key, "mobile") || + strings.Contains(key, "手机") || strings.Contains(key, "电话") +} + +// ExtractPlainSubject 从解密后的 query 参数 map 中提取被查询人姓名、手机、身份证(首次匹配优先) +func ExtractPlainSubject(m map[string]interface{}) (name, mobile, idCard string) { + walkMap(m, &name, &mobile, &idCard) + return name, mobile, idCard +} + +func walkMap(m map[string]interface{}, name, mobile, idCard *string) { + for k, v := range m { + switch val := v.(type) { + case string: + assignSubjectField(k, val, name, mobile, idCard) + case map[string]interface{}: + walkMap(val, name, mobile, idCard) + } + } +} + +func assignSubjectField(key, val string, name, mobile, idCard *string) { + if val == "" { + return + } + if *idCard == "" && isIDCardField(key) { + *idCard = val + return + } + if *mobile == "" && isPhoneField(key) { + *mobile = val + return + } + if *name == "" && isNameField(key) { + *name = val + } +} diff --git a/app/main/api/internal/queue/paySuccessNotify.go b/app/main/api/internal/queue/paySuccessNotify.go index bc42276..4f545a3 100644 --- a/app/main/api/internal/queue/paySuccessNotify.go +++ b/app/main/api/internal/queue/paySuccessNotify.go @@ -2,15 +2,18 @@ package queue import ( "context" + "database/sql" "encoding/hex" "encoding/json" "fmt" "os" "regexp" "strings" + "ycc-server/app/main/api/internal/pkg/querysubject" "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" "ycc-server/app/main/model" + "ycc-server/common/globalkey" "ycc-server/pkg/lzkit/crypto" "ycc-server/pkg/lzkit/lzUtils" @@ -73,6 +76,11 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq. return fmt.Errorf("解密参数失败: %+v", aesdecryptErr) } + var userInfo map[string]interface{} + if err := json.Unmarshal(decryptData, &userInfo); err != nil { + return fmt.Errorf("解析用户信息失败: %+v", err) + } + query := &model.Query{ Id: uuid.NewString(), OrderId: order.Id, @@ -86,6 +94,10 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq. return fmt.Errorf("保存查询失败: %+v", insertQueryErr) } + if err := l.insertQuerySubjectIndex(ctx, query.Id, order.Id, userInfo, key); err != nil { + logx.Errorf("写入被查询人密文索引失败 order=%s query=%s err=%v", order.Id, query.Id, err) + } + // 插入后使用预生成的查询ID queryId := query.Id @@ -95,12 +107,6 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq. return fmt.Errorf("获取插入后的查询记录失败: %+v", err) } - // 解析解密后的参数获取用户信息 - var userInfo map[string]interface{} - if err := json.Unmarshal(decryptData, &userInfo); err != nil { - return fmt.Errorf("解析用户信息失败: %+v", err) - } - // 生成授权书 authDoc, err := l.svcCtx.AuthorizationService.GenerateAuthorizationDocument( ctx, order.UserId, order.Id, queryId, userInfo, @@ -262,6 +268,42 @@ func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error return asynq.SkipRetry } +func (l *PaySuccessNotifyUserHandler) insertQuerySubjectIndex( + ctx context.Context, + queryId, orderId string, + userInfo map[string]interface{}, + aesKey []byte, +) error { + name, mobile, idCard := querysubject.ExtractPlainSubject(userInfo) + if name == "" && mobile == "" && idCard == "" { + return nil + } + row := &model.QuerySubjectIndex{ + Id: uuid.New().String(), + QueryId: queryId, + OrderId: orderId, + DelState: globalkey.DelStateNo, + Version: 0, + } + if enc, ok, err := crypto.EncryptDeterministicOptional(name, aesKey); err != nil { + return err + } else if ok { + row.EncRealName = sql.NullString{String: enc, Valid: true} + } + if enc, ok, err := crypto.EncryptDeterministicOptional(mobile, aesKey); err != nil { + return err + } else if ok { + row.EncMobile = sql.NullString{String: enc, Valid: true} + } + if enc, ok, err := crypto.EncryptDeterministicOptional(idCard, aesKey); err != nil { + return err + } else if ok { + row.EncIdCard = sql.NullString{String: enc, Valid: true} + } + _, err := l.svcCtx.QuerySubjectIndexModel.Insert(ctx, row) + return err +} + // desensitizeParams 对敏感数据进行脱敏处理 func (l *PaySuccessNotifyUserHandler) desensitizeParams(data []byte) ([]byte, error) { // 解析JSON数据到map diff --git a/app/main/api/internal/svc/servicecontext.go b/app/main/api/internal/svc/servicecontext.go index 4af114c..f9151ec 100644 --- a/app/main/api/internal/svc/servicecontext.go +++ b/app/main/api/internal/svc/servicecontext.go @@ -47,6 +47,7 @@ type ServiceContext struct { OrderModel model.OrderModel OrderRefundModel model.OrderRefundModel QueryModel model.QueryModel + QuerySubjectIndexModel model.QuerySubjectIndexModel QueryCleanupLogModel model.QueryCleanupLogModel QueryCleanupDetailModel model.QueryCleanupDetailModel QueryCleanupConfigModel model.QueryCleanupConfigModel @@ -143,6 +144,7 @@ func NewServiceContext(c config.Config) *ServiceContext { // ============================== 订单相关模型 ============================== orderModel := model.NewOrderModel(db, cacheConf) queryModel := model.NewQueryModel(db, cacheConf) + querySubjectIndexModel := model.NewQuerySubjectIndexModel(db, cacheConf) orderRefundModel := model.NewOrderRefundModel(db, cacheConf) queryCleanupLogModel := model.NewQueryCleanupLogModel(db, cacheConf) queryCleanupDetailModel := model.NewQueryCleanupDetailModel(db, cacheConf) @@ -261,6 +263,7 @@ func NewServiceContext(c config.Config) *ServiceContext { // 订单相关模型 OrderModel: orderModel, QueryModel: queryModel, + QuerySubjectIndexModel: querySubjectIndexModel, OrderRefundModel: orderRefundModel, QueryCleanupLogModel: queryCleanupLogModel, QueryCleanupDetailModel: queryCleanupDetailModel, diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index 8575660..f6b5728 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -559,21 +559,24 @@ type AdminGetOrderDetailResp struct { } type AdminGetOrderListReq struct { - Page int64 `form:"page,default=1"` // 页码 - PageSize int64 `form:"pageSize,default=20"` // 每页数量 - OrderNo string `form:"order_no,optional"` // 商户订单号 - PlatformOrderId string `form:"platform_order_id,optional"` // 支付订单号 - ProductName string `form:"product_name,optional"` // 产品名称 - PaymentPlatform string `form:"payment_platform,optional"` // 支付方式 - PaymentScene string `form:"payment_scene,optional"` // 支付平台 - Amount float64 `form:"amount,optional"` // 金额 - Status string `form:"status,optional"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 - CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始 - CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 - PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始 - PayTimeEnd string `form:"pay_time_end,optional"` // 支付时间结束 - RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始 - RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束 + Page int64 `form:"page,default=1"` // 页码 + PageSize int64 `form:"pageSize,default=20"` // 每页数量 + OrderNo string `form:"order_no,optional"` // 商户订单号 + PlatformOrderId string `form:"platform_order_id,optional"` // 支付订单号 + ProductName string `form:"product_name,optional"` // 产品名称 + PaymentPlatform string `form:"payment_platform,optional"` // 支付方式 + PaymentScene string `form:"payment_scene,optional"` // 支付平台 + Amount float64 `form:"amount,optional"` // 金额 + Status string `form:"status,optional"` // 支付状态:pending-待支付,paid-已支付,refunded-已退款,closed-已关闭,failed-支付失败 + CreateTimeStart string `form:"create_time_start,optional"` // 创建时间开始 + CreateTimeEnd string `form:"create_time_end,optional"` // 创建时间结束 + PayTimeStart string `form:"pay_time_start,optional"` // 支付时间开始 + PayTimeEnd string `form:"pay_time_end,optional"` // 支付时间结束 + RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始 + RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束 + QuerySubjectName string `form:"query_subject_name,optional"` // 姓名(明文,服务端转密文查询) + QuerySubjectMobile string `form:"query_subject_mobile,optional"` // 手机号 + QuerySubjectIdCard string `form:"query_subject_id_card,optional"` // 身份证 } type AdminGetOrderListResp struct { @@ -693,10 +696,10 @@ type AdminGetQueryDetailByOrderIdReq struct { } type AdminGetQueryDetailByOrderIdResp struct { - Id string `json:"id"` // 主键ID - OrderId string `json:"order_id"` // 订单ID - UserId string `json:"user_id"` // 用户ID - ProductName string `json:"product_name"` // 产品名称 + Id string `json:"id"` // 主键ID + OrderId string `json:"order_id"` // 订单ID + UserId string `json:"user_id"` // 用户ID + ProductName string `json:"product_name"` // 产品名称 QueryParams map[string]interface{} `json:"query_params"` QueryData []AdminQueryItem `json:"query_data"` CreateTime string `json:"create_time"` // 创建时间 @@ -872,6 +875,15 @@ type AdminUpdateAgentConfigResp struct { Success bool `json:"success"` } +type AdminUpdateAgentMobileReq struct { + AgentId string `json:"agent_id"` // 代理ID + Mobile string `json:"mobile"` // 新手机号 +} + +type AdminUpdateAgentMobileResp struct { + Success bool `json:"success"` +} + type AdminUpdateAgentProductConfigReq struct { Id string `json:"id"` // 主键 BasePrice float64 `json:"base_price"` // 基础底价 @@ -884,16 +896,6 @@ type AdminUpdateAgentProductConfigResp struct { Success bool `json:"success"` } -// 代理手机号修改 -type AdminUpdateAgentMobileReq struct { - AgentId string `json:"agent_id"` // 代理ID - Mobile string `json:"mobile"` // 新手机号 -} - -type AdminUpdateAgentMobileResp struct { - Success bool `json:"success"` -} - type AdminUpdateApiReq struct { Id string `path:"id"` ApiName string `json:"api_name"` diff --git a/app/main/model/querySubjectIndexModel.go b/app/main/model/querySubjectIndexModel.go new file mode 100644 index 0000000..6d56739 --- /dev/null +++ b/app/main/model/querySubjectIndexModel.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ QuerySubjectIndexModel = (*customQuerySubjectIndexModel)(nil) + +type ( + // QuerySubjectIndexModel is an interface to be customized, add more methods here, + // and implement the added methods in customQuerySubjectIndexModel. + QuerySubjectIndexModel interface { + querySubjectIndexModel + } + + customQuerySubjectIndexModel struct { + *defaultQuerySubjectIndexModel + } +) + +// NewQuerySubjectIndexModel returns a model for the database table. +func NewQuerySubjectIndexModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) QuerySubjectIndexModel { + return &customQuerySubjectIndexModel{ + defaultQuerySubjectIndexModel: newQuerySubjectIndexModel(conn, c, opts...), + } +} diff --git a/app/main/model/querySubjectIndexModel_gen.go b/app/main/model/querySubjectIndexModel_gen.go new file mode 100644 index 0000000..0aacdbf --- /dev/null +++ b/app/main/model/querySubjectIndexModel_gen.go @@ -0,0 +1,155 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.8.4 + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + querySubjectIndexFieldNames = builder.RawFieldNames(&QuerySubjectIndex{}) + querySubjectIndexRows = strings.Join(querySubjectIndexFieldNames, ",") + querySubjectIndexRowsExpectAutoSet = strings.Join(stringx.Remove(querySubjectIndexFieldNames, "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + querySubjectIndexRowsWithPlaceHolder = strings.Join(stringx.Remove(querySubjectIndexFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" + + cacheQuerySubjectIndexIdPrefix = "cache:querySubjectIndex:id:" + cacheQuerySubjectIndexQueryIdPrefix = "cache:querySubjectIndex:queryId:" +) + +type ( + querySubjectIndexModel interface { + Insert(ctx context.Context, data *QuerySubjectIndex) (sql.Result, error) + FindOne(ctx context.Context, id string) (*QuerySubjectIndex, error) + FindOneByQueryId(ctx context.Context, queryId string) (*QuerySubjectIndex, error) + Update(ctx context.Context, data *QuerySubjectIndex) error + Delete(ctx context.Context, id string) error + } + + defaultQuerySubjectIndexModel struct { + sqlc.CachedConn + table string + } + + QuerySubjectIndex struct { + Id string `db:"id"` // UUID主键 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` // 删除状态:0=未删除,1=已删除 + Version int64 `db:"version"` // 版本号 + QueryId string `db:"query_id"` // query 表主键 + OrderId string `db:"order_id"` // 订单ID + EncRealName sql.NullString `db:"enc_real_name"` // 被查询人姓名密文(Base64) + EncMobile sql.NullString `db:"enc_mobile"` // 手机号密文(Base64) + EncIdCard sql.NullString `db:"enc_id_card"` // 身份证密文(Base64) + } +) + +func newQuerySubjectIndexModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultQuerySubjectIndexModel { + return &defaultQuerySubjectIndexModel{ + CachedConn: sqlc.NewConn(conn, c, opts...), + table: "`query_subject_index`", + } +} + +func (m *defaultQuerySubjectIndexModel) Delete(ctx context.Context, id string) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + querySubjectIndexIdKey := fmt.Sprintf("%s%v", cacheQuerySubjectIndexIdPrefix, id) + querySubjectIndexQueryIdKey := fmt.Sprintf("%s%v", cacheQuerySubjectIndexQueryIdPrefix, data.QueryId) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + return conn.ExecCtx(ctx, query, id) + }, querySubjectIndexIdKey, querySubjectIndexQueryIdKey) + return err +} + +func (m *defaultQuerySubjectIndexModel) FindOne(ctx context.Context, id string) (*QuerySubjectIndex, error) { + querySubjectIndexIdKey := fmt.Sprintf("%s%v", cacheQuerySubjectIndexIdPrefix, id) + var resp QuerySubjectIndex + err := m.QueryRowCtx(ctx, &resp, querySubjectIndexIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", querySubjectIndexRows, m.table) + return conn.QueryRowCtx(ctx, v, query, id) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultQuerySubjectIndexModel) FindOneByQueryId(ctx context.Context, queryId string) (*QuerySubjectIndex, error) { + querySubjectIndexQueryIdKey := fmt.Sprintf("%s%v", cacheQuerySubjectIndexQueryIdPrefix, queryId) + var resp QuerySubjectIndex + err := m.QueryRowIndexCtx(ctx, &resp, querySubjectIndexQueryIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) { + query := fmt.Sprintf("select %s from %s where `query_id` = ? limit 1", querySubjectIndexRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, queryId); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultQuerySubjectIndexModel) Insert(ctx context.Context, data *QuerySubjectIndex) (sql.Result, error) { + querySubjectIndexIdKey := fmt.Sprintf("%s%v", cacheQuerySubjectIndexIdPrefix, data.Id) + querySubjectIndexQueryIdKey := fmt.Sprintf("%s%v", cacheQuerySubjectIndexQueryIdPrefix, data.QueryId) + ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, querySubjectIndexRowsExpectAutoSet) + return conn.ExecCtx(ctx, query, data.Id, data.DeleteTime, data.DelState, data.Version, data.QueryId, data.OrderId, data.EncRealName, data.EncMobile, data.EncIdCard) + }, querySubjectIndexIdKey, querySubjectIndexQueryIdKey) + return ret, err +} + +func (m *defaultQuerySubjectIndexModel) Update(ctx context.Context, newData *QuerySubjectIndex) error { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return err + } + + querySubjectIndexIdKey := fmt.Sprintf("%s%v", cacheQuerySubjectIndexIdPrefix, data.Id) + querySubjectIndexQueryIdKey := fmt.Sprintf("%s%v", cacheQuerySubjectIndexQueryIdPrefix, data.QueryId) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, querySubjectIndexRowsWithPlaceHolder) + return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.QueryId, newData.OrderId, newData.EncRealName, newData.EncMobile, newData.EncIdCard, newData.Id) + }, querySubjectIndexIdKey, querySubjectIndexQueryIdKey) + return err +} + +func (m *defaultQuerySubjectIndexModel) formatPrimary(primary any) string { + return fmt.Sprintf("%s%v", cacheQuerySubjectIndexIdPrefix, primary) +} + +func (m *defaultQuerySubjectIndexModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", querySubjectIndexRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary) +} + +func (m *defaultQuerySubjectIndexModel) tableName() string { + return m.table +} diff --git a/deploy/sql/query_subject_index_migration.sql b/deploy/sql/query_subject_index_migration.sql new file mode 100644 index 0000000..5a78239 --- /dev/null +++ b/deploy/sql/query_subject_index_migration.sql @@ -0,0 +1,26 @@ +-- ============================================ +-- 被查询人密文索引表:用于管理后台按姓名/手机/身份证筛选订单(字段均为 AES-ECB 确定性密文) +-- ============================================ + +CREATE TABLE IF NOT EXISTS `query_subject_index` ( + `id` CHAR(36) NOT NULL COMMENT 'UUID主键', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态:0=未删除,1=已删除', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + + `query_id` CHAR(36) NOT NULL COMMENT 'query 表主键', + `order_id` CHAR(36) NOT NULL COMMENT '订单ID', + + `enc_real_name` varchar(768) DEFAULT NULL COMMENT '被查询人姓名密文(Base64)', + `enc_mobile` varchar(768) DEFAULT NULL COMMENT '手机号密文(Base64)', + `enc_id_card` varchar(768) DEFAULT NULL COMMENT '身份证密文(Base64)', + + PRIMARY KEY (`id`), + UNIQUE KEY `uk_query_id` (`query_id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_enc_mobile` (`enc_mobile`), + KEY `idx_enc_id_card` (`enc_id_card`), + KEY `idx_enc_real_name` (`enc_real_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='被查询人密文索引(仅用于后台筛选)'; diff --git a/pkg/lzkit/crypto/ecb.go b/pkg/lzkit/crypto/ecb.go index 3fed15f..c472171 100644 --- a/pkg/lzkit/crypto/ecb.go +++ b/pkg/lzkit/crypto/ecb.go @@ -214,6 +214,18 @@ func EncryptIDCard(idCard string, key []byte) (string, error) { return AesEcbEncrypt([]byte(idCard), key) } +// EncryptDeterministicOptional 对非空明文做 AES-ECB 确定性加密;空串表示不建索引列,返回 ok=false +func EncryptDeterministicOptional(plain string, key []byte) (cipher string, ok bool, err error) { + if plain == "" { + return "", false, nil + } + cipher, err = AesEcbEncrypt([]byte(plain), key) + if err != nil { + return "", false, err + } + return cipher, true, nil +} + // DecryptIDCard 解密身份证号 func DecryptIDCard(encryptedIDCard string, key []byte) (string, error) { if encryptedIDCard == "" {