diff --git a/app/main/api/desc/admin/order.api b/app/main/api/desc/admin/order.api index 3a7b470..523a21f 100644 --- a/app/main/api/desc/admin/order.api +++ b/app/main/api/desc/admin/order.api @@ -77,6 +77,9 @@ type ( RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始 RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束 SalesCost float64 `form:"sales_cost,optional"` // 成本价 + QueriedName string `form:"queried_name,optional"` // 被查询人姓名(明文,后端加密后匹配 query_user_record) + QueriedIdCard string `form:"queried_id_card,optional"` // 被查询人身份证(明文) + QueriedMobile string `form:"queried_mobile,optional"` // 被查询人手机号(明文) } // 列表响应 AdminGetOrderListResp { diff --git a/app/main/api/etc/main.dev.yaml b/app/main/api/etc/main.dev.yaml index 84d08e4..569a458 100644 --- a/app/main/api/etc/main.dev.yaml +++ b/app/main/api/etc/main.dev.yaml @@ -20,16 +20,15 @@ VerifyCode: Encrypt: SecretKey: "ff83609b2b24fc73196aac3d3dfb874f" Alipay: - AppID: "2021006121698606" - PrivateKey: "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCx3TqaCUgZIMfG8Lu8fqkKqKLzONB9NBp4qCM2CAgxkUK1fd48XZ3ANDzfzNoeIbLbR/VL3ZUgyX/F8jO7Cit71JS58X9b1ngeRyBdaiBhLZgHb7Ngg0f1zDJuq8H6/i3Tiu9PaH0iOFKeaFCCpkakFyXjp//rqkcj2bPfa6lX0bD4YcILz8DC3iPy/cXHrHwaBryhYouYA3bKFROHykfF5D+tS2PZdR13BiDVElwDuquZCMdRdISfTw8snp5HSxA0TyRZIiGrtU3WvSKxYi2s8smZl/uYN1uj4w4GOivnTrkBdLy6DBqvgqRsCeYDDV5UyjtbAhvksZH60EWS3HTfAgMBAAECggEAK4dv+xjAa13pZpet6nC5ICGrV4kVBT9GJzdG/scycicRw2cdh3qFy+884qy4yN0Ib8AJmVqOT6rguWoQHPtdLv4Us/kVaT1wwkA3/ISnjgDhjxhYNwuKBe7GfO1OGQYx4u7CqJVy4ngUSC5RXdghu7Dqle+co2lV5cE20zv/Ar2nYo4p9fxcl/XAttqdRyby3ge6zJZP46Ru4CHzFzyUsrJYC575R3Jq68Zrr/+v0BmpEm/wAmoQE8W0ElMMH+Jw0twj+4l6PaUcq9oUVIL9wzl7ay1B6dKyxEOyinGYrmd65NzsTu8HhEpFdvQ+1O27XyahLsNWpSamJlV23ns/EQKBgQDd2UiQQRrj+itfa1/Zehy8MstEICw73LMDk129yNG9cU8U4y5vaTJkAcDTfme08B3uVVhsIUhJsFdgnh4ayyYW3jsB5BBBgszhwYQciH7f/3nE3rcX/TIs6yQAQkPhQi2RNSdn3SrCIhxgu9TwrZIDyQYJ6kk/9qEmZd93RrhOBwKBgQDNPpS1jFt4l08kzCHzfTvTbIu5z/6NPxmRSpcrdkoOAUfzlKSkRnKJuzkFSJEmWpFXILfNiza1dBYncg60Lu58yXpaGlLbVmxeFem+DdQm55lgl28d8Ssg6nlCTqE/tjB5LZ6qn8KEkWSj6gXH37pU1XFZh0A3+EIytnL7NnrsaQKBgGMaGUw3iSemLZHmiV7BKez4U80PAjOLl3xVbF7HQsp5v3X5NlkWiSgbkGPp57HwQa6h+Wn0RDKGz8GdYJ1fephklb92fbyGDbgblkSYxPSTT3Yed3QD61IdiGuFLoWF5o0jTYMcTWmDi2G7BpitMLj4J/Zt7mLgbYSVpYnG0bYpAoGAS7Tfya/CNdMqQFqD03rITI5nY9zS+mriFXO8Gy4A1vWmArU7ndTWfvNubwJ7d/hEUC0jX1AQmBH/8gDiZ5hAJAt1dDLtiTZxtqrCk3YqYUdgjf6N4C+LRxL2M30pgYTEkI5BTpKrf5bZ1pSGGVnvM0egDfQTvhF26ZnfA8buxLECgYAssWTLQ2Ou2NJ8N3HJx54kpKLwf218ulINsH6opcot22hbVfj0rc7lAcMe9FTGgmSgrbZG9QqAC2BpSHDeSC4C57iSYhG86tq/jwvNsV1miPV9RRj3CvwZkbHzCwqhRvCPS/QUgTzG3Z9pljYLkZjuMkb9jp814GbwEGPeoucefw==" + AppID: "2021004161631930" + PrivateKey: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCRrZNr8DNs4LhPSulTLEg4RLREWVSFGS+Nl5Q2FxQ8DgkUYV+p3kfi4XmB2W/Ruz4egPxEB0V/xj75OktVphVKY8rI6OaNnVoFVe5NqGa5MTj3wLwBIv/hMHA1VAru2KLIv9R1FR7LpWmreHSkpJ65CD2mZqYuMCekOfzMQZIGgSagEU4my0bLbFWw7M3qZz4vm2KUtm4Ew28OUJDkqygjPzXgS5l5niYQvqPjiQNdnTtoIcNcHo07tS8lmf/hdgq9EtVfY7Y0brESfgvOoVJeg1hTHEj0hyWnnWPeA4HD2izANP/5ObRX4ZVqpVju+7PSpbeFd71fxbR1blAVnrqTAgMBAAECggEASpkwHN3r9507xJ7/zG+oq+fCyB1WgrHbAA7W/rviyL4HOECE1F/XP/9mUXAfKq9PqB81D0EJ/dxu8wE/AqUB0g44EZnyNiKVrpXKakoKEFt8aKJxo8NgdNhxHV3kG1skQNi62xntoysZaY1NbeI+xVHLACMghhZytk5bfd02Ac3rMBz3X8Cl1R+3mgU0zFc5f476VRxywiRQM+QNJIaHDNB4vw1TKI0K92mEKD8lOuNZD7d5TCBZi3r08t8FFAkMjIMDiFvFRFmAqMg3NyaIGUkLVDU2zUP0Vlzmo9ghCV9hluqDqeP4RhxQydOw+rxGBk+crYQBhPyYOI/I9PFXAQKBgQDHSRRTPqYbCfztmwk3AIH7VN6izyU3FljEXAsdf+UVJpRa8429J3e+sB96jxhiwVlCzX4CDjsa/Pu0iQQx22a0AZs5GTE0MJ1FVydfGlyqF6/hRS4TswSkklW3be7/KDAjgj2+/wap+mN7rRmDkdvxgCJG6MiWuRAthhg/g16wIwKBgQC7Iu7D4yQXRKheL6p6pbMtE+oD58/EJ2vO8ZUz3LiPc9pZ6+bp4nkBP6JOuYiB5jkzWQifKe6hsXpv06kWzaBEzz4f4SUpWDmdBchNoct3pB/k66FaxHLO/pG4RV86hqscqTdutmdC62bbwM6yCtJ+3rS5rlCxDGQkGJbM+wM60QKBgH5nQyYeCbwC1NRdTzX883VYerLoEyHi4cEC5OX8NnD4/IbIDzJYc2KXUhAp7XzOSPDPaMqi/ih7KKh1dByvnnA0yKEp8oS5BThzNHzlOruEtMF9YOGL3jkIvKfRahOcCRSsyr94AWEVeb57qEBE5y5CaPtzMbAwiCtn779xc0DjAoGAZwEGXWokDm6rIhSoiJO2OQSyFW4+LSDptWHCF2bRa5yAPmiblHck1awaAa0b1yxKpdnG5hzljbirxOvDMZsDMXzFHDUICGbYZ3asVxbMcNE1AQM1sElbTFZRDRWaIhPIEaGOsnDSC8KYvjK1UsikLlMVNPMe1SUV5cxnDPLJR1ECgYEAw8M09uLylPtfGq7oyE2R6xC2kUA8EJ6aapJgUs/UZ6dtjtvudbYzUo0Cgnb12hpN3hfLc5O0/P4nRzZ72Hm43cMiRNLJi4BYCa0m/mCxq+RcoBWYQTIraHnR17yIQhxt5IBRVjgbvYCnryx5Jd5wjOvv7DdnGFJLepzSJwlGqeU=" AlipayPublicKey: "" - AppCertPath: "etc/merchant/appCertPublicKey_2021006121698606.crt" + AppCertPath: "etc/merchant/appCertPublicKey_2021004161631930.crt" AlipayCertPath: "etc/merchant/alipayCertPublicKey_RSA2.crt" AlipayRootCertPath: "etc/merchant/alipayRootCert.crt" IsProduction: true - NotifyUrl: "https://6m4685017o.goho.co/api/v1/pay/alipay/callback" - ReturnURL: "http://localhost:5678/inquire" - + NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/alipay/callback" + ReturnURL: "https://www.tianyuancha.cn/payment/result" Wxpay: AppID: "wxa581992dc74d860e" MchID: "1682635136" @@ -39,12 +38,12 @@ Wxpay: MchPublicKeyID: "PUB_KEY_ID_0116826351362025060900382267001601" MchPublicKeyPath: "etc/merchant/pub_key.pem" MchPlatformRAS: "1FFEC3F62E31885FAB4C91ADCB8D7557E9488781" - NotifyUrl: "https://6m4685017o.goho.co/api/v1/pay/wechat/callback" - RefundNotifyUrl: "https://6m4685017o.goho.co/api/v1/wechat/refund_callback" + NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/wechat/callback" + RefundNotifyUrl: "https://www.tianyuancha.cn/api/v1/wechat/refund_callback" Applepay: ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt" SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt" - Sandbox: false + Sandbox: true BundleID: "com.allinone.check" IssuerID: "bf828d85-5269-4914-9660-c066e09cd6ef" KeyID: "LAY65829DQ" diff --git a/app/main/api/internal/logic/admin_auth/adminloginlogic.go b/app/main/api/internal/logic/admin_auth/adminloginlogic.go index ae47bca..8b241e5 100644 --- a/app/main/api/internal/logic/admin_auth/adminloginlogic.go +++ b/app/main/api/internal/logic/admin_auth/adminloginlogic.go @@ -2,6 +2,7 @@ package admin_auth import ( "context" + "os" "tyc-server/app/main/api/internal/svc" "tyc-server/app/main/api/internal/types" @@ -30,8 +31,8 @@ func NewAdminLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminL } func (l *AdminLoginLogic) AdminLogin(req *types.AdminLoginReq) (resp *types.AdminLoginResp, err error) { - // 1. 验证验证码 - if !req.Captcha { + // 1. 验证验证码(开发环境可跳过) + if os.Getenv("ENV") != "development" && !req.Captcha { return nil, errors.Wrapf(xerr.NewErrMsg("验证码错误"), "用户登录, 验证码错误, 验证码: %v", req.Captcha) } diff --git a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go index e1d1416..83e01e7 100644 --- a/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go +++ b/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go @@ -2,6 +2,7 @@ package admin_order import ( "context" + "encoding/hex" "sync" "tyc-server/app/main/api/internal/svc" @@ -9,6 +10,7 @@ import ( "tyc-server/app/main/model" "tyc-server/common/globalkey" "tyc-server/common/xerr" + "tyc-server/pkg/lzkit/crypto" "github.com/Masterminds/squirrel" "github.com/pkg/errors" @@ -77,6 +79,67 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR builder = builder.Where("refund_time <= ?", req.RefundTimeEnd) } + // 按被查询人姓名/身份证/手机号过滤:通过 query_user_record 表加密匹配得到 order_id 列表 + if req.QueriedName != "" || req.QueriedIdCard != "" || req.QueriedMobile != "" { + key, err := hex.DecodeString(l.svcCtx.Config.Encrypt.SecretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "AdminGetOrderList, 解密密钥无效 err: %v", err) + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + var orConds []squirrel.Eq + if req.QueriedName != "" { + encName, err := crypto.AesEcbEncrypt([]byte(req.QueriedName), key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "AdminGetOrderList, 姓名加密失败 err: %v", err) + } + orConds = append(orConds, squirrel.Eq{"name": encName}) + } + if req.QueriedIdCard != "" { + encIdCard, err := crypto.EncryptIDCard(req.QueriedIdCard, key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "AdminGetOrderList, 身份证加密失败 err: %v", err) + } + orConds = append(orConds, squirrel.Eq{"id_card": encIdCard}) + } + if req.QueriedMobile != "" { + encMobile, err := crypto.EncryptMobile(req.QueriedMobile, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "AdminGetOrderList, 手机号加密失败 err: %v", err) + } + orConds = append(orConds, squirrel.Eq{"mobile": encMobile}) + } + if len(orConds) == 0 { + return &types.AdminGetOrderListResp{Total: 0, Items: []types.OrderListItem{}}, nil + } + qb := l.svcCtx.QueryUserRecordModel.SelectBuilder(). + Columns("order_id"). + Where(squirrel.Gt{"order_id": 0}) + // squirrel Or 需要多个 predicate,用 Or 拼接 + var orPred squirrel.Or + for _, eq := range orConds { + orPred = append(orPred, eq) + } + qb = qb.Where(orPred) + records, err := l.svcCtx.QueryUserRecordModel.FindAll(l.ctx, qb, "id DESC") + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 查询被查询人记录失败 err: %v", err) + } + orderIdSet := make(map[int64]struct{}) + for _, rec := range records { + if rec.OrderId > 0 { + orderIdSet[rec.OrderId] = struct{}{} + } + } + if len(orderIdSet) == 0 { + return &types.AdminGetOrderListResp{Total: 0, Items: []types.OrderListItem{}}, nil + } + orderIds := make([]int64, 0, len(orderIdSet)) + for id := range orderIdSet { + orderIds = append(orderIds, id) + } + builder = builder.Where(squirrel.Eq{"id": orderIds}) + } + // 并发获取总数和列表 var total int64 var orders []*model.Order diff --git a/app/main/api/internal/logic/agent/agentrealnamelogic.go b/app/main/api/internal/logic/agent/agentrealnamelogic.go index de7b069..6ada7be 100644 --- a/app/main/api/internal/logic/agent/agentrealnamelogic.go +++ b/app/main/api/internal/logic/agent/agentrealnamelogic.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "os" "time" "tyc-server/app/main/api/internal/service" @@ -43,17 +44,19 @@ func (l *AgentRealNameLogic) AgentRealName(req *types.AgentRealNameReq) (resp *t if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理实名, 加密手机号失败: %v", err) } - // 检查手机号是否在一分钟内已发送过验证码 - redisKey := fmt.Sprintf("%s:%s", "realName", encryptedMobile) - cacheCode, err := l.svcCtx.Redis.Get(redisKey) - if err != nil { - if errors.Is(err, redis.Nil) { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理实名, 验证码过期: %s", encryptedMobile) + // 开发环境可跳过验证码校验 + if os.Getenv("ENV") != "development" { + redisKey := fmt.Sprintf("%s:%s", "realName", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(redisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理实名, 验证码过期: %s", encryptedMobile) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理实名, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) + } + if cacheCode != req.Code { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理实名, 验证码不正确: %s", encryptedMobile) } - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理实名, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) - } - if cacheCode != req.Code { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理实名, 验证码不正确: %s", encryptedMobile) } agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) if err != nil { diff --git a/app/main/api/internal/logic/agent/applyforagentlogic.go b/app/main/api/internal/logic/agent/applyforagentlogic.go index ebce2e6..8915fa2 100644 --- a/app/main/api/internal/logic/agent/applyforagentlogic.go +++ b/app/main/api/internal/logic/agent/applyforagentlogic.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "os" "time" "tyc-server/app/main/model" "tyc-server/common/ctxdata" @@ -44,8 +45,8 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err) } - if req.Mobile != "18889793585" { - // 校验验证码 + // 开发环境可跳过验证码校验 + if os.Getenv("ENV") != "development" { redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile) cacheCode, err := l.svcCtx.Redis.Get(redisKey) if err != nil { diff --git a/app/main/api/internal/logic/pay/alipaycallbacklogic.go b/app/main/api/internal/logic/pay/alipaycallbacklogic.go index 19d1a63..2265528 100644 --- a/app/main/api/internal/logic/pay/alipaycallbacklogic.go +++ b/app/main/api/internal/logic/pay/alipaycallbacklogic.go @@ -94,6 +94,14 @@ func (l *AlipayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, not return nil } + // 更新 query_user_record 的 platform_order_id(见 query_user_record.sql 说明 3) + qb := l.svcCtx.QueryUserRecordModel.SelectBuilder().Where("query_no = ?", notification.OutTradeNo) + records, _ := l.svcCtx.QueryUserRecordModel.FindAll(l.ctx, qb, "") + for _, rec := range records { + rec.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo) + _, _ = l.svcCtx.QueryUserRecordModel.Update(l.ctx, nil, rec) + } + if order.Status == "paid" { if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil { logx.Errorf("异步任务调度失败: %v", asyncErr) diff --git a/app/main/api/internal/logic/pay/paymentlogic.go b/app/main/api/internal/logic/pay/paymentlogic.go index af6471a..9b6bf9c 100644 --- a/app/main/api/internal/logic/pay/paymentlogic.go +++ b/app/main/api/internal/logic/pay/paymentlogic.go @@ -2,9 +2,12 @@ package pay import ( "context" + "database/sql" "encoding/hex" "encoding/json" "fmt" + "os" + "time" "tyc-server/app/main/api/internal/svc" "tyc-server/app/main/api/internal/types" "tyc-server/app/main/model" @@ -12,6 +15,7 @@ import ( "tyc-server/common/xerr" "tyc-server/pkg/lzkit/crypto" + "github.com/Masterminds/squirrel" "github.com/pkg/errors" "github.com/redis/go-redis/v9" "github.com/zeromicro/go-zero/core/logx" @@ -27,6 +31,7 @@ type PaymentTypeResp struct { amount float64 outTradeNo string description string + orderID int64 } func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic { @@ -60,6 +65,16 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, } } + // 开发环境测试支付模式:仅当 pay_method=test 时跳过实际支付,直接返回 test_payment_success + // 支付宝/微信在开发环境下仍走真实支付流程(跳转沙箱),支付成功后由回调更新订单 + // 注意:订单状态更新在事务外进行,避免在事务中查询不到订单的问题 + isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test" + if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != 0 { + prepayData = "test_payment_success" + logx.Infof("开发环境测试支付模式:订单 %s (ID: %d) 将在事务提交后更新状态", paymentTypeResp.outTradeNo, paymentTypeResp.orderID) + return nil + } + var createOrderErr error if req.PayMethod == "wechat" { prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo) @@ -76,6 +91,47 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, if err != nil { return nil, err } + + // 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程(仅 pay_method=test 且 query 类型 orderID>0) + isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test" + if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != 0 { + go func() { + time.Sleep(200 * time.Millisecond) + + finalOrderID := paymentTypeResp.orderID + order, findOrderErr := l.svcCtx.OrderModel.FindOne(context.Background(), finalOrderID) + if findOrderErr != nil { + logx.Errorf("开发测试模式,查找订单失败,订单ID: %d, 错误: %v", finalOrderID, findOrderErr) + return + } + order.Status = "paid" + now := time.Now() + order.PayTime = sql.NullTime{Time: now, Valid: true} + + // 空报告模式:在 PaymentPlatform 标记为 "test",在 paySuccessNotify.ProcessTask 中通过 + // order.PaymentPlatform == "test" 识别(isEmptyReportMode),并生成空报告、跳过 API 调用 + isEmptyReportMode := req.PayMethod == "test" + if isEmptyReportMode { + order.PaymentPlatform = "test" + logx.Infof("开发环境空报告模式:订单 %s (ID: %d) 已标记为空报告模式", paymentTypeResp.outTradeNo, finalOrderID) + } + updateErr := l.svcCtx.OrderModel.UpdateWithVersion(context.Background(), nil, order) + if updateErr != nil { + logx.Errorf("开发测试模式,更新订单状态失败,订单ID: %d, 错误: %v", finalOrderID, updateErr) + return + } + + if enqErr := l.svcCtx.AsynqService.SendQueryTask(finalOrderID); enqErr != nil { + logx.Errorf("开发测试模式,入队生成报告失败,订单ID: %d, 错误: %v", finalOrderID, enqErr) + } + + logx.Infof("开发测试模式,订单状态已更新为已支付并已入队生成报告,订单ID: %d", finalOrderID) + + // 再次短暂延迟,确保订单状态更新已提交 + time.Sleep(100 * time.Millisecond) + }() + } + switch v := prepayData.(type) { case string: // 如果 prepayData 是字符串类型,直接返回 @@ -149,6 +205,16 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses } orderID = insertedOrderID + // 更新 query_user_record 的 order_id,用于关联订单(见 query_user_record.sql 说明 2) + qb := l.svcCtx.QueryUserRecordModel.SelectBuilder().Where(squirrel.Eq{"query_no": outTradeNo}) + records, findRecErr := l.svcCtx.QueryUserRecordModel.FindAll(l.ctx, qb, "") + if findRecErr == nil && len(records) > 0 { + for _, rec := range records { + rec.OrderId = orderID + _, _ = l.svcCtx.QueryUserRecordModel.Update(l.ctx, session, rec) + } + } + if data.AgentIdentifier != "" { agent, parsingErr := l.agentParsing(data.AgentIdentifier) if parsingErr != nil { @@ -162,7 +228,7 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert) } } - return &PaymentTypeResp{amount: amount, outTradeNo: outTradeNo, description: product.ProductName}, nil + return &PaymentTypeResp{amount: amount, outTradeNo: outTradeNo, description: product.ProductName, orderID: orderID}, nil } func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) { userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx) diff --git a/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go b/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go index faa29b8..af08b4f 100644 --- a/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go +++ b/app/main/api/internal/logic/pay/wechatpaycallbacklogic.go @@ -91,6 +91,16 @@ func (l *WechatPayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, return nil } + // 更新 query_user_record 的 platform_order_id(见 query_user_record.sql 说明 3) + if notification.OutTradeNo != nil { + qb := l.svcCtx.QueryUserRecordModel.SelectBuilder().Where("query_no = ?", *notification.OutTradeNo) + records, _ := l.svcCtx.QueryUserRecordModel.FindAll(l.ctx, qb, "") + for _, rec := range records { + rec.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId) + _, _ = l.svcCtx.QueryUserRecordModel.Update(l.ctx, nil, rec) + } + } + if order.Status == "paid" { if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil { logx.Errorf("异步任务调度失败: %v", asyncErr) diff --git a/app/main/api/internal/logic/query/queryservicelogic.go b/app/main/api/internal/logic/query/queryservicelogic.go index d29f7c7..666d330 100644 --- a/app/main/api/internal/logic/query/queryservicelogic.go +++ b/app/main/api/internal/logic/query/queryservicelogic.go @@ -2,13 +2,16 @@ package query import ( "context" + "database/sql" "encoding/hex" "encoding/json" "fmt" + "os" "time" "tyc-server/app/main/api/internal/service" "tyc-server/app/main/model" "tyc-server/common/ctxdata" + "tyc-server/common/globalkey" "tyc-server/common/xerr" "tyc-server/pkg/lzkit/crypto" "tyc-server/pkg/lzkit/validator" @@ -739,8 +742,11 @@ func (l *QueryServiceLogic) DecryptData(data string) ([]byte, error) { return decryptData, nil } -// 校验验证码 +// 校验验证码(开发环境 ENV=development 可跳过) func (l *QueryServiceLogic) VerifyCode(mobile string, code string) error { + if os.Getenv("ENV") == "development" { + return nil + } secretKey := l.svcCtx.Config.Encrypt.SecretKey encryptedMobile, err := crypto.EncryptMobile(mobile, secretKey) if err != nil { @@ -765,8 +771,11 @@ func (l *QueryServiceLogic) IsAgentQuery() bool { return agentID != "" } -// 二要素验证(仅姓名+身份证号) +// 二要素验证(仅姓名+身份证号)(开发环境不调用验证 API) func (l *QueryServiceLogic) VerifyTwo(Name string, IDCard string) error { + if os.Getenv("ENV") == "development" { + return nil + } twoVerification := service.TwoFactorVerificationRequest{ Name: Name, IDCard: IDCard, @@ -781,8 +790,11 @@ func (l *QueryServiceLogic) VerifyTwo(Name string, IDCard string) error { return nil } -// 按代理/非代理切换要素验证:代理走三要素;非代理走二要素 +// 按代理/非代理切换要素验证:代理走三要素;非代理走二要素(开发环境不调用验证 API) func (l *QueryServiceLogic) Verify(Name string, IDCard string, Mobile string) error { + if os.Getenv("ENV") == "development" { + return nil + } if !l.IsAgentQuery() { twoVerification := service.TwoFactorVerificationRequest{ Name: Name, @@ -847,6 +859,46 @@ func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product str if cacheErr != nil { return "", cacheErr } + + // 写入 query_user_record,用于后台按被查询人姓名/身份证/手机号追溯订单(见 query_user_record.sql 说明 1) + nameStr, _ := params["name"].(string) + idCardStr, _ := params["id_card"].(string) + mobileStr, _ := params["mobile"].(string) + if nameStr != "" || idCardStr != "" || mobileStr != "" { + var encName, encIdCard, encMobile string + if nameStr != "" { + encName, _ = crypto.AesEcbEncrypt([]byte(nameStr), key) + } + if idCardStr != "" { + encIdCard, _ = crypto.EncryptIDCard(idCardStr, key) + } + if mobileStr != "" { + encMobile, _ = crypto.EncryptMobile(mobileStr, secretKey) + } + agentIdent := sql.NullString{} + if agentIdentifier != "" { + agentIdent = sql.NullString{String: agentIdentifier, Valid: true} + } + rec := &model.QueryUserRecord{ + DeleteTime: sql.NullTime{}, + DelState: globalkey.DelStateNo, + Version: 0, + UserId: userID, + Name: encName, + IdCard: encIdCard, + Mobile: encMobile, + Product: Product, + QueryNo: outTradeNo, + OrderId: 0, + PlatformOrderId: sql.NullString{}, + AgentIdentifier: agentIdent, + } + _, insertErr := l.svcCtx.QueryUserRecordModel.Insert(l.ctx, nil, rec) + if insertErr != nil { + logx.WithContext(l.ctx).Errorf("CacheData 写入 query_user_record 失败: %v", insertErr) + } + } + return outTradeNo, nil } diff --git a/app/main/api/internal/logic/user/bindmobilelogic.go b/app/main/api/internal/logic/user/bindmobilelogic.go index d21dea7..43670e6 100644 --- a/app/main/api/internal/logic/user/bindmobilelogic.go +++ b/app/main/api/internal/logic/user/bindmobilelogic.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "os" "time" "tyc-server/app/main/api/internal/svc" @@ -42,8 +43,8 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 加密手机号失败: %v", err) } - if req.Mobile != "18889793585" { - // 检查手机号是否在一分钟内已发送过验证码 + // 开发环境可跳过验证码校验 + if os.Getenv("ENV") != "development" { redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile) cacheCode, err := l.svcCtx.Redis.Get(redisKey) if err != nil { diff --git a/app/main/api/internal/logic/user/mobilecodeloginlogic.go b/app/main/api/internal/logic/user/mobilecodeloginlogic.go index abbcefe..41a4da7 100644 --- a/app/main/api/internal/logic/user/mobilecodeloginlogic.go +++ b/app/main/api/internal/logic/user/mobilecodeloginlogic.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "os" "time" "tyc-server/app/main/api/internal/svc" "tyc-server/app/main/api/internal/types" @@ -37,17 +38,19 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err) } - // 检查手机号是否在一分钟内已发送过验证码 - redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile) - cacheCode, err := l.svcCtx.Redis.Get(redisKey) - if err != nil { - if errors.Is(err, redis.Nil) { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机登录, 验证码过期: %s", encryptedMobile) + // 检查验证码(开发环境可跳过) + if os.Getenv("ENV") != "development" { + redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(redisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机登录, 验证码过期: %s", encryptedMobile) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) + } + if cacheCode != req.Code { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile) } - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) - } - if cacheCode != req.Code { - return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile) } var userID int64 user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true}) diff --git a/app/main/api/internal/queue/paySuccessNotify.go b/app/main/api/internal/queue/paySuccessNotify.go index 8ea8388..ced491c 100644 --- a/app/main/api/internal/queue/paySuccessNotify.go +++ b/app/main/api/internal/queue/paySuccessNotify.go @@ -11,6 +11,7 @@ import ( "regexp" "strings" paylogic "tyc-server/app/main/api/internal/logic/pay" + "tyc-server/app/main/api/internal/service" "tyc-server/app/main/api/internal/svc" "tyc-server/app/main/api/internal/types" "tyc-server/app/main/model" @@ -142,10 +143,19 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq. } } - // 调用API请求服务 - responseData, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id) - if err != nil { - return l.handleError(ctx, err, order, query) + // 调用API请求服务(开发环境下不调用其它产品,使用默认空报告;测试支付 order.PaymentPlatform=="test" 也走空报告) + var responseData []service.APIResponseData + isEmptyReportMode := env == "development" || order.PaymentPlatform == "test" + if isEmptyReportMode { + // 开发环境 / 测试支付:生成仅包含基本信息的默认空报告,不调用外部 API + logx.Infof("空报告模式:订单 %s (ID: %d) 跳过API调用,生成空报告", order.OrderNo, order.Id) + responseData = []service.APIResponseData{} + } else { + var processErr error + responseData, processErr = l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id) + if processErr != nil { + return l.handleError(ctx, processErr, order, query) + } } // 计算成功模块的总成本价 diff --git a/app/main/api/internal/svc/servicecontext.go b/app/main/api/internal/svc/servicecontext.go index 702559a..284fef0 100644 --- a/app/main/api/internal/svc/servicecontext.go +++ b/app/main/api/internal/svc/servicecontext.go @@ -42,6 +42,7 @@ type ServiceContext struct { QueryCleanupLogModel model.QueryCleanupLogModel QueryCleanupDetailModel model.QueryCleanupDetailModel QueryCleanupConfigModel model.QueryCleanupConfigModel + QueryUserRecordModel model.QueryUserRecordModel // 代理相关模型 AgentModel model.AgentModel @@ -132,6 +133,7 @@ func NewServiceContext(c config.Config) *ServiceContext { queryCleanupLogModel := model.NewQueryCleanupLogModel(db, cacheConf) queryCleanupDetailModel := model.NewQueryCleanupDetailModel(db, cacheConf) queryCleanupConfigModel := model.NewQueryCleanupConfigModel(db, cacheConf) + queryUserRecordModel := model.NewQueryUserRecordModel(db, cacheConf) // ============================== 代理相关模型 ============================== agentModel := model.NewAgentModel(db, cacheConf) @@ -244,6 +246,7 @@ func NewServiceContext(c config.Config) *ServiceContext { QueryCleanupLogModel: queryCleanupLogModel, QueryCleanupDetailModel: queryCleanupDetailModel, QueryCleanupConfigModel: queryCleanupConfigModel, + QueryUserRecordModel: queryUserRecordModel, // 代理相关模型 AgentModel: agentModel, diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index e1416b0..f9d9e2d 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -533,6 +533,9 @@ type AdminGetOrderListReq struct { RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始 RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束 SalesCost float64 `form:"sales_cost,optional"` // 成本价 + QueriedName string `form:"queried_name,optional"` // 被查询人姓名(明文) + QueriedIdCard string `form:"queried_id_card,optional"` // 被查询人身份证(明文) + QueriedMobile string `form:"queried_mobile,optional"` // 被查询人手机号(明文) } type AdminGetOrderListResp struct { diff --git a/app/main/model/queryUserRecordModel.go b/app/main/model/queryUserRecordModel.go new file mode 100644 index 0000000..9f2d824 --- /dev/null +++ b/app/main/model/queryUserRecordModel.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 _ QueryUserRecordModel = (*customQueryUserRecordModel)(nil) + +type ( + // QueryUserRecordModel is an interface to be customized, add more methods here, + // and implement the added methods in customQueryUserRecordModel. + QueryUserRecordModel interface { + queryUserRecordModel + } + + customQueryUserRecordModel struct { + *defaultQueryUserRecordModel + } +) + +// NewQueryUserRecordModel returns a model for the database table. +func NewQueryUserRecordModel(conn sqlx.SqlConn, c cache.CacheConf) QueryUserRecordModel { + return &customQueryUserRecordModel{ + defaultQueryUserRecordModel: newQueryUserRecordModel(conn, c), + } +} diff --git a/app/main/model/queryUserRecordModel_gen.go b/app/main/model/queryUserRecordModel_gen.go new file mode 100644 index 0000000..d199f16 --- /dev/null +++ b/app/main/model/queryUserRecordModel_gen.go @@ -0,0 +1,375 @@ +// Code generated by goctl. DO NOT EDIT! + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "time" + + "github.com/Masterminds/squirrel" + "github.com/pkg/errors" + "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" + "tyc-server/common/globalkey" +) + +var ( + queryUserRecordFieldNames = builder.RawFieldNames(&QueryUserRecord{}) + queryUserRecordRows = strings.Join(queryUserRecordFieldNames, ",") + queryUserRecordRowsExpectAutoSet = strings.Join(stringx.Remove(queryUserRecordFieldNames, "`id`", "`create_time`", "`update_time`"), ",") + queryUserRecordRowsWithPlaceHolder = strings.Join(stringx.Remove(queryUserRecordFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?" + + cacheTycQueryUserRecordIdPrefix = "cache:tyc:queryUserRecord:id:" +) + +type ( + queryUserRecordModel interface { + Insert(ctx context.Context, session sqlx.Session, data *QueryUserRecord) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*QueryUserRecord, error) + Update(ctx context.Context, session sqlx.Session, data *QueryUserRecord) (sql.Result, error) + UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryUserRecord) error + Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error + SelectBuilder() squirrel.SelectBuilder + DeleteSoft(ctx context.Context, session sqlx.Session, data *QueryUserRecord) error + FindSum(ctx context.Context, sumBuilder squirrel.SelectBuilder, field string) (float64, error) + FindCount(ctx context.Context, countBuilder squirrel.SelectBuilder, field string) (int64, error) + FindAll(ctx context.Context, rowBuilder squirrel.SelectBuilder, orderBy string) ([]*QueryUserRecord, error) + FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryUserRecord, error) + FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryUserRecord, int64, error) + FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryUserRecord, error) + FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryUserRecord, error) + Delete(ctx context.Context, session sqlx.Session, id int64) error + } + + defaultQueryUserRecordModel struct { + sqlc.CachedConn + table string + } + + QueryUserRecord struct { + Id int64 `db:"id"` + CreateTime time.Time `db:"create_time"` + UpdateTime time.Time `db:"update_time"` + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` + Version int64 `db:"version"` // 版本号 + UserId int64 `db:"user_id"` // 用户ID + Name string `db:"name"` // 姓名密文(AES-ECB+Base64) + IdCard string `db:"id_card"` // 身份证号密文(AES-ECB+Base64) + Mobile string `db:"mobile"` // 手机号密文(AES-ECB+Base64) + Product string `db:"product"` // 产品类型,如 marriage/homeservice/riskassessment 等 + QueryNo string `db:"query_no"` // 查询单号(与 order.order_no 一致,如 Q_xxx),用户提交查询时生成 + OrderId int64 `db:"order_id"` // 订单ID,关联 order 表,用户发起支付并创建订单后写入 + PlatformOrderId sql.NullString `db:"platform_order_id"` // 支付平台订单号(支付宝/微信),支付成功后由回调写入 + AgentIdentifier sql.NullString `db:"agent_identifier"` // 代理标识,代理渠道时有值 + } +) + +func newQueryUserRecordModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultQueryUserRecordModel { + return &defaultQueryUserRecordModel{ + CachedConn: sqlc.NewConn(conn, c), + table: "`query_user_record`", + } +} + +func (m *defaultQueryUserRecordModel) Insert(ctx context.Context, session sqlx.Session, data *QueryUserRecord) (sql.Result, error) { + data.DelState = globalkey.DelStateNo + tycQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTycQueryUserRecordIdPrefix, data.Id) + return 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, queryUserRecordRowsExpectAutoSet) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier) + }, tycQueryUserRecordIdKey) +} + +func (m *defaultQueryUserRecordModel) FindOne(ctx context.Context, id int64) (*QueryUserRecord, error) { + tycQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTycQueryUserRecordIdPrefix, id) + var resp QueryUserRecord + err := m.QueryRowCtx(ctx, &resp, tycQueryUserRecordIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", queryUserRecordRows, m.table) + return conn.QueryRowCtx(ctx, v, query, id, globalkey.DelStateNo) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) Update(ctx context.Context, session sqlx.Session, data *QueryUserRecord) (sql.Result, error) { + tycQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTycQueryUserRecordIdPrefix, data.Id) + return 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, queryUserRecordRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier, data.Id) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier, data.Id) + }, tycQueryUserRecordIdKey) +} + +func (m *defaultQueryUserRecordModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, data *QueryUserRecord) error { + + oldVersion := data.Version + data.Version += 1 + + var sqlResult sql.Result + var err error + + tycQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTycQueryUserRecordIdPrefix, data.Id) + sqlResult, 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` = ? and version = ? ", m.table, queryUserRecordRowsWithPlaceHolder) + if session != nil { + return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier, data.Id, oldVersion) + } + return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.Name, data.IdCard, data.Mobile, data.Product, data.QueryNo, data.OrderId, data.PlatformOrderId, data.AgentIdentifier, data.Id, oldVersion) + }, tycQueryUserRecordIdKey) + if err != nil { + return err + } + updateCount, err := sqlResult.RowsAffected() + if err != nil { + return err + } + if updateCount == 0 { + return ErrNoRowsUpdate + } + + return nil +} + +func (m *defaultQueryUserRecordModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *QueryUserRecord) error { + data.DelState = globalkey.DelStateYes + data.DeleteTime = sql.NullTime{Time: time.Now(), Valid: true} + if err := m.UpdateWithVersion(ctx, session, data); err != nil { + return errors.Wrapf(errors.New("delete soft failed "), "QueryUserRecordModel delete err : %+v", err) + } + return nil +} + +func (m *defaultQueryUserRecordModel) FindSum(ctx context.Context, builder squirrel.SelectBuilder, field string) (float64, error) { + + if len(field) == 0 { + return 0, errors.Wrapf(errors.New("FindSum Least One Field"), "FindSum Least One Field") + } + + builder = builder.Columns("IFNULL(SUM(" + field + "),0)") + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return 0, err + } + + var resp float64 + err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return 0, err + } +} + +func (m *defaultQueryUserRecordModel) FindCount(ctx context.Context, builder squirrel.SelectBuilder, field string) (int64, error) { + + if len(field) == 0 { + return 0, errors.Wrapf(errors.New("FindCount Least One Field"), "FindCount Least One Field") + } + + builder = builder.Columns("COUNT(" + field + ")") + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return 0, err + } + + var resp int64 + err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return 0, err + } +} + +func (m *defaultQueryUserRecordModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*QueryUserRecord, error) { + + builder = builder.Columns(queryUserRecordRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql() + if err != nil { + return nil, err + } + + var resp []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryUserRecord, error) { + + builder = builder.Columns(queryUserRecordRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + if page < 1 { + page = 1 + } + offset := (page - 1) * pageSize + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*QueryUserRecord, int64, error) { + + total, err := m.FindCount(ctx, builder, "id") + if err != nil { + return nil, 0, err + } + + builder = builder.Columns(queryUserRecordRows) + + if orderBy == "" { + builder = builder.OrderBy("id DESC") + } else { + builder = builder.OrderBy(orderBy) + } + + if page < 1 { + page = 1 + } + offset := (page - 1) * pageSize + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, total, err + } + + var resp []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, total, nil + default: + return nil, total, err + } +} + +func (m *defaultQueryUserRecordModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*QueryUserRecord, error) { + + builder = builder.Columns(queryUserRecordRows) + + if preMinId > 0 { + builder = builder.Where(" id < ? ", preMinId) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id DESC").Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*QueryUserRecord, error) { + + builder = builder.Columns(queryUserRecordRows) + + if preMaxId > 0 { + builder = builder.Where(" id > ? ", preMaxId) + } + + query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id ASC").Limit(uint64(pageSize)).ToSql() + if err != nil { + return nil, err + } + + var resp []*QueryUserRecord + err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...) + switch err { + case nil: + return resp, nil + default: + return nil, err + } +} + +func (m *defaultQueryUserRecordModel) Trans(ctx context.Context, fn func(ctx context.Context, session sqlx.Session) error) error { + + return m.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error { + return fn(ctx, session) + }) + +} + +func (m *defaultQueryUserRecordModel) SelectBuilder() squirrel.SelectBuilder { + return squirrel.Select().From(m.table) +} +func (m *defaultQueryUserRecordModel) Delete(ctx context.Context, session sqlx.Session, id int64) error { + tycQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTycQueryUserRecordIdPrefix, id) + _, 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) + if session != nil { + return session.ExecCtx(ctx, query, id) + } + return conn.ExecCtx(ctx, query, id) + }, tycQueryUserRecordIdKey) + return err +} +func (m *defaultQueryUserRecordModel) formatPrimary(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheTycQueryUserRecordIdPrefix, primary) +} +func (m *defaultQueryUserRecordModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error { + query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", queryUserRecordRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo) +} + +func (m *defaultQueryUserRecordModel) tableName() string { + return m.table +} diff --git a/deploy/script/gen_models.ps1 b/deploy/script/gen_models.ps1 index 5eac4f2..655ad9c 100644 --- a/deploy/script/gen_models.ps1 +++ b/deploy/script/gen_models.ps1 @@ -1,7 +1,7 @@ # 设置输出编码为UTF-8 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # 数据库连接信息 - 修改了URL格式 -$DB_URL = "tyc:5vg67b3UNHu8@(127.0.0.1:21001)/tyc" +$DB_URL = "tyc:5vg67b3UNHu8@tcp(127.0.0.1:22001)/tyc" $OUTPUT_DIR = "./model" $TEMPLATE_DIR = "../template" @@ -11,7 +11,7 @@ $tables = @( # "agent_active_stat", # "agent_audit", # "agent_closure", - "agent_commission" + # "agent_commission" # "agent_wallet_transaction" # "agent_commission_deduction" # "agent_link", @@ -54,10 +54,11 @@ $tables = @( # "admin_promotion_link_stats_total" # "admin_promotion_link_stats_history" # "admin_promotion_order" + "query_user_record" ) # 为每个表生成模型 foreach ($table in $tables) { - goctl model mysql datasource -url="tyc:5vg67b3UNHu8@tcp(127.0.0.1:21001)/tyc" -table="$table" -dir="./model" --home="../template" -cache=true --style=goZero + goctl model mysql datasource -url="tyc:5vg67b3UNHu8@tcp(127.0.0.1:22001)/tyc" -table="$table" -dir="./model" --home="../template" -cache=true --style=goZero } diff --git a/deploy/sql/query_user_record.sql b/deploy/sql/query_user_record.sql new file mode 100644 index 0000000..edf1278 --- /dev/null +++ b/deploy/sql/query_user_record.sql @@ -0,0 +1,42 @@ +-- 查询用户记录表 +-- 用途:记录用户查询时输入的姓名、身份证、手机号,以及支付订单号等,用于通过查询条件追溯订单信息 +-- 执行说明:在目标数据库执行此脚本创建表 +-- +-- 使用说明(需在业务代码中接入): +-- 1. 用户提交查询时(queryservicelogic.CacheData 之后):INSERT 记录 name, id_card, mobile(已 AES-ECB+Base64 加密), product, query_no, user_id, agent_identifier +-- 2. 用户发起支付并创建 order 时(paymentlogic.QueryOrderPayment 中 Insert order 之后):UPDATE 本表 SET order_id WHERE query_no=outTradeNo +-- 3. 支付回调成功更新 order 后(alipaycallbacklogic/wechatpaycallbacklogic):UPDATE 本表 SET platform_order_id WHERE query_no=orderNo +-- +-- 敏感字段加密:name、id_card、mobile 使用 pkg/lzkit/crypto 的 AES-ECB+Base64 加密后入库,密钥为 config.Encrypt.SecretKey(hex)。 +-- 解密:姓名 crypto.AesEcbDecrypt(rec.Name, key);身份证 crypto.DecryptIDCard(rec.IdCard, key);手机 crypto.DecryptMobile(rec.Mobile, secretKey)。 + +CREATE TABLE `query_user_record` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `del_state` tinyint NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', + + /* 业务字段 - 用户查询输入(name、id_card、mobile 为 AES-ECB+Base64 密文) */ + `user_id` bigint NOT NULL DEFAULT '0' COMMENT '用户ID', + `name` varchar(256) NOT NULL DEFAULT '' COMMENT '姓名密文(AES-ECB+Base64)', + `id_card` varchar(128) NOT NULL DEFAULT '' COMMENT '身份证号密文(AES-ECB+Base64)', + `mobile` varchar(128) NOT NULL DEFAULT '' COMMENT '手机号密文(AES-ECB+Base64)', + `product` varchar(50) NOT NULL DEFAULT '' COMMENT '产品类型,如 marriage/homeservice/riskassessment 等', + + /* 关联字段 - 查询单号与订单 */ + `query_no` varchar(64) NOT NULL DEFAULT '' COMMENT '查询单号(与 order.order_no 一致,如 Q_xxx),用户提交查询时生成', + `order_id` bigint NOT NULL DEFAULT '0' COMMENT '订单ID,关联 order 表,用户发起支付并创建订单后写入', + `platform_order_id` varchar(64) DEFAULT NULL COMMENT '支付平台订单号(支付宝/微信),支付成功后由回调写入', + `agent_identifier` varchar(255) DEFAULT NULL COMMENT '代理标识,代理渠道时有值', + + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_id_card` (`id_card`), + KEY `idx_mobile` (`mobile`), + KEY `idx_query_no` (`query_no`), + KEY `idx_order_id` (`order_id`), + KEY `idx_platform_order_id` (`platform_order_id`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='查询用户记录表:姓名、身份证、手机号、支付订单号等,用于通过查询信息追溯订单';