f
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -20,3 +20,11 @@ data/*
|
|||||||
/tmp/
|
/tmp/
|
||||||
|
|
||||||
/app/api
|
/app/api
|
||||||
|
|
||||||
|
# debug binary (Delve)
|
||||||
|
_debug_bin.*
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# authorization documents (PDF files)
|
||||||
|
app/main/api/data/authorization_docs/
|
||||||
|
**/authorization_docs/**/*.pdf
|
||||||
@@ -77,6 +77,9 @@ type (
|
|||||||
RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始
|
RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始
|
||||||
RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束
|
RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束
|
||||||
SalesCost float64 `form:"sales_cost,optional"` // 成本价
|
SalesCost float64 `form:"sales_cost,optional"` // 成本价
|
||||||
|
QueryName string `form:"query_name,optional"` // 被查询人姓名(通过 query_user_record 表追溯订单)
|
||||||
|
QueryIdCard string `form:"query_id_card,optional"` // 被查询人身份证(通过 query_user_record 表追溯订单)
|
||||||
|
QueryMobile string `form:"query_mobile,optional"` // 被查询人手机号(通过 query_user_record 表追溯订单)
|
||||||
}
|
}
|
||||||
// 列表响应
|
// 列表响应
|
||||||
AdminGetOrderListResp {
|
AdminGetOrderListResp {
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ Ali:
|
|||||||
SystemConfig:
|
SystemConfig:
|
||||||
ThreeVerify: false
|
ThreeVerify: false
|
||||||
CommissionSafeMode: false # 佣金安全防御模式:true-冻结模式,false-直接结算模式
|
CommissionSafeMode: false # 佣金安全防御模式:true-冻结模式,false-直接结算模式
|
||||||
|
SkipVerifyCode: true # 开发环境下跳过验证码验证
|
||||||
|
SkipVerify: true # 空报告模式:开发环境跳过二/三要素验证(避免未授权IP调用天元API失败)
|
||||||
WechatH5:
|
WechatH5:
|
||||||
AppID: "wxa581992dc74d860e"
|
AppID: "wxa581992dc74d860e"
|
||||||
AppSecret: "4de1fbf521712247542d49907fcd5dbf"
|
AppSecret: "4de1fbf521712247542d49907fcd5dbf"
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ type YushanConfig struct {
|
|||||||
type SystemConfig struct {
|
type SystemConfig struct {
|
||||||
ThreeVerify bool // 是否开启三级实名认证
|
ThreeVerify bool // 是否开启三级实名认证
|
||||||
CommissionSafeMode bool // 佣金安全防御模式:true-冻结模式(status=1,进入frozen_balance),false-直接结算(status=0,进入balance)
|
CommissionSafeMode bool // 佣金安全防御模式:true-冻结模式(status=1,进入frozen_balance),false-直接结算(status=0,进入balance)
|
||||||
|
SkipVerifyCode bool // 开发环境下跳过验证码验证
|
||||||
|
SkipVerify bool // 空报告模式:开发环境下跳过二/三要素验证(避免未授权IP调用天元API失败)
|
||||||
}
|
}
|
||||||
type WechatH5Config struct {
|
type WechatH5Config struct {
|
||||||
AppID string
|
AppID string
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package admin_order
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"tydata-server/app/main/api/internal/svc"
|
"tydata-server/app/main/api/internal/svc"
|
||||||
@@ -9,6 +11,7 @@ import (
|
|||||||
"tydata-server/app/main/model"
|
"tydata-server/app/main/model"
|
||||||
"tydata-server/common/globalkey"
|
"tydata-server/common/globalkey"
|
||||||
"tydata-server/common/xerr"
|
"tydata-server/common/xerr"
|
||||||
|
"tydata-server/pkg/lzkit/crypto"
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -77,6 +80,19 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
|||||||
builder = builder.Where("refund_time <= ?", req.RefundTimeEnd)
|
builder = builder.Where("refund_time <= ?", req.RefundTimeEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按被查询人(query_user_record 表)过滤:姓名、身份证、手机号(库中为密文,需解密后匹配)
|
||||||
|
if req.QueryName != "" || req.QueryIdCard != "" || req.QueryMobile != "" {
|
||||||
|
orderIds, filterErr := l.filterOrderIdsByQueryUserRecord(req.QueryName, req.QueryIdCard, req.QueryMobile)
|
||||||
|
if filterErr != nil {
|
||||||
|
return nil, filterErr
|
||||||
|
}
|
||||||
|
if len(orderIds) == 0 {
|
||||||
|
builder = builder.Where("1 = 0") // 无匹配时返回空
|
||||||
|
} else {
|
||||||
|
builder = builder.Where(squirrel.Eq{"id": orderIds})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 并发获取总数和列表
|
// 并发获取总数和列表
|
||||||
var total int64
|
var total int64
|
||||||
var orders []*model.Order
|
var orders []*model.Order
|
||||||
@@ -294,3 +310,71 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
|
|||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterOrderIdsByQueryUserRecord 根据姓名、身份证、手机号(明文)从 query_user_record 解密后匹配,返回符合条件的 order_id 列表
|
||||||
|
func (l *AdminGetOrderListLogic) filterOrderIdsByQueryUserRecord(queryName, queryIdCard, queryMobile string) ([]int64, error) {
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
key, keyErr := hex.DecodeString(secretKey)
|
||||||
|
if keyErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "Encrypt.SecretKey 解析失败: %v", keyErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
qb := l.svcCtx.QueryUserRecordModel.SelectBuilder().Where("order_id > ?", 0)
|
||||||
|
recs, err := l.svcCtx.QueryUserRecordModel.FindAll(l.ctx, qb, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询 query_user_record 失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
orderIds := make([]int64, 0, len(recs))
|
||||||
|
for _, rec := range recs {
|
||||||
|
match := true
|
||||||
|
|
||||||
|
if queryName != "" {
|
||||||
|
var decName string
|
||||||
|
if rec.Name != "" {
|
||||||
|
bs, e := crypto.AesEcbDecrypt(rec.Name, key)
|
||||||
|
if e != nil {
|
||||||
|
match = false
|
||||||
|
} else {
|
||||||
|
decName = string(bs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if match && !strings.Contains(decName, queryName) {
|
||||||
|
match = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match && queryIdCard != "" {
|
||||||
|
var decIdCard string
|
||||||
|
if rec.IdCard != "" {
|
||||||
|
var e error
|
||||||
|
decIdCard, e = crypto.DecryptIDCard(rec.IdCard, key)
|
||||||
|
if e != nil {
|
||||||
|
match = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if match && decIdCard != queryIdCard {
|
||||||
|
match = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match && queryMobile != "" {
|
||||||
|
var decMobile string
|
||||||
|
if rec.Mobile != "" {
|
||||||
|
var e error
|
||||||
|
decMobile, e = crypto.DecryptMobile(rec.Mobile, secretKey)
|
||||||
|
if e != nil {
|
||||||
|
match = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if match && decMobile != queryMobile {
|
||||||
|
match = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
orderIds = append(orderIds, rec.OrderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orderIds, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,17 +43,20 @@ func (l *AgentRealNameLogic) AgentRealName(req *types.AgentRealNameReq) (resp *t
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理实名, 加密手机号失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理实名, 加密手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
// 检查手机号是否在一分钟内已发送过验证码
|
// 开发环境下跳过验证码验证
|
||||||
redisKey := fmt.Sprintf("%s:%s", "realName", encryptedMobile)
|
if !l.svcCtx.Config.SystemConfig.SkipVerifyCode {
|
||||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
// 检查手机号是否在一分钟内已发送过验证码
|
||||||
if err != nil {
|
redisKey := fmt.Sprintf("%s:%s", "realName", encryptedMobile)
|
||||||
if errors.Is(err, redis.Nil) {
|
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理实名, 验证码过期: %s", encryptedMobile)
|
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)
|
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
"tydata-server/app/main/model"
|
"tydata-server/app/main/model"
|
||||||
"tydata-server/common/ctxdata"
|
"tydata-server/common/ctxdata"
|
||||||
"tydata-server/common/xerr"
|
"tydata-server/common/xerr"
|
||||||
"tydata-server/pkg/lzkit/crypto"
|
"tydata-server/pkg/lzkit/crypto"
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
@@ -44,7 +44,8 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
if req.Mobile != "18889793585" {
|
// 开发环境下跳过验证码验证,或者特定手机号跳过(保留原有逻辑)
|
||||||
|
if !l.svcCtx.Config.SystemConfig.SkipVerifyCode && req.Mobile != "18889793585" {
|
||||||
// 校验验证码
|
// 校验验证码
|
||||||
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
||||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ func (l *AlipayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, not
|
|||||||
logx.Errorf("支付宝支付回调,修改订单信息失败: %+v", updateErr)
|
logx.Errorf("支付宝支付回调,修改订单信息失败: %+v", updateErr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if order.Status == "paid" {
|
if order.Status == "paid" {
|
||||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||||
logx.Errorf("异步任务调度失败: %v", asyncErr)
|
logx.Errorf("异步任务调度失败: %v", asyncErr)
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ package pay
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"tydata-server/app/main/api/internal/svc"
|
"tydata-server/app/main/api/internal/svc"
|
||||||
"tydata-server/app/main/api/internal/types"
|
"tydata-server/app/main/api/internal/types"
|
||||||
"tydata-server/app/main/model"
|
"tydata-server/app/main/model"
|
||||||
@@ -27,6 +31,7 @@ type PaymentTypeResp struct {
|
|||||||
amount float64
|
amount float64
|
||||||
outTradeNo string
|
outTradeNo string
|
||||||
description string
|
description string
|
||||||
|
orderID int64 // 仅 query 类型有值;agent_vip 为 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
|
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
|
||||||
@@ -40,6 +45,7 @@ func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLo
|
|||||||
func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, err error) {
|
func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, err error) {
|
||||||
var paymentTypeResp *PaymentTypeResp
|
var paymentTypeResp *PaymentTypeResp
|
||||||
var prepayData interface{}
|
var prepayData interface{}
|
||||||
|
|
||||||
l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||||
switch req.PayType {
|
switch req.PayType {
|
||||||
case "agent_vip":
|
case "agent_vip":
|
||||||
@@ -55,6 +61,17 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅 wechat/alipay/appleiap 调起真实支付;test 仅在上面 isDevTestPayment 分支处理
|
||||||
var createOrderErr error
|
var createOrderErr error
|
||||||
if req.PayMethod == "wechat" {
|
if req.PayMethod == "wechat" {
|
||||||
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
||||||
@@ -62,6 +79,13 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
|||||||
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
|
||||||
} else if req.PayMethod == "appleiap" {
|
} else if req.PayMethod == "appleiap" {
|
||||||
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo)
|
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo)
|
||||||
|
} else if req.PayMethod == "test" {
|
||||||
|
if os.Getenv("ENV") != "development" {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "开发环境测试支付仅在开发环境可用")
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "开发环境测试支付仅支持 query 类型订单")
|
||||||
|
} else {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.REUQEST_PARAM_ERROR), "不支持的支付方式: %s", req.PayMethod)
|
||||||
}
|
}
|
||||||
if createOrderErr != nil {
|
if createOrderErr != nil {
|
||||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 创建支付订单失败: %+v", createOrderErr)
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 创建支付订单失败: %+v", createOrderErr)
|
||||||
@@ -71,6 +95,46 @@ func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
switch v := prepayData.(type) {
|
||||||
case string:
|
case string:
|
||||||
// 如果 prepayData 是字符串类型,直接返回
|
// 如果 prepayData 是字符串类型,直接返回
|
||||||
@@ -124,6 +188,42 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
|||||||
if user.Inside == 1 {
|
if user.Inside == 1 {
|
||||||
amount = 0.01
|
amount = 0.01
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查72小时内身份证查询次数限制
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
key, decodeErr := hex.DecodeString(secretKey)
|
||||||
|
if decodeErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取AES密钥失败: %+v", decodeErr)
|
||||||
|
}
|
||||||
|
// 解密缓存中的参数
|
||||||
|
decryptedParams, decryptErr := crypto.AesDecrypt(data.Params, key)
|
||||||
|
if decryptErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解密缓存参数失败: %v", decryptErr)
|
||||||
|
}
|
||||||
|
var params map[string]interface{}
|
||||||
|
if unmarshalErr := json.Unmarshal(decryptedParams, ¶ms); unmarshalErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析解密参数失败: %v", unmarshalErr)
|
||||||
|
}
|
||||||
|
// 获取身份证号
|
||||||
|
idCard, ok := params["id_card"].(string)
|
||||||
|
if !ok || idCard == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取身份证号失败")
|
||||||
|
}
|
||||||
|
// 加密身份证号用于查询
|
||||||
|
encryptedIdCard, encryptErr := crypto.EncryptIDCard(idCard, key)
|
||||||
|
if encryptErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 加密身份证号失败: %v", encryptErr)
|
||||||
|
}
|
||||||
|
// 查询72小时内的查询次数
|
||||||
|
queryCount, countErr := l.svcCtx.QueryUserRecordModel.CountByEncryptedIdCardIn72Hours(l.ctx, encryptedIdCard)
|
||||||
|
if countErr != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 查询记录失败: %v", countErr)
|
||||||
|
}
|
||||||
|
// 如果72小时内查询次数大于等于2次,禁止支付(当前这次是第3次)
|
||||||
|
if queryCount >= 2 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrMsg("查询受限通知:检测到您72小时内已完成2次报告查询,系统已自动暂停服务。如需紧急查询,请联系客服申请临时额度。"), "生成订单, 查询次数超限: %d", queryCount)
|
||||||
|
}
|
||||||
|
|
||||||
var orderID int64
|
var orderID int64
|
||||||
order := model.Order{
|
order := model.Order{
|
||||||
OrderNo: outTradeNo,
|
OrderNo: outTradeNo,
|
||||||
@@ -144,6 +244,12 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
|||||||
}
|
}
|
||||||
orderID = insertedOrderID
|
orderID = insertedOrderID
|
||||||
|
|
||||||
|
// 更新查询用户记录表的 order_id,便于通过查询信息追溯订单
|
||||||
|
if rec, findErr := l.svcCtx.QueryUserRecordModel.FindOneByQueryNo(l.ctx, outTradeNo); findErr == nil {
|
||||||
|
rec.OrderId = orderID
|
||||||
|
_, _ = l.svcCtx.QueryUserRecordModel.Update(l.ctx, session, rec)
|
||||||
|
}
|
||||||
|
|
||||||
if data.AgentIdentifier != "" {
|
if data.AgentIdentifier != "" {
|
||||||
agent, parsingErr := l.agentParsing(data.AgentIdentifier)
|
agent, parsingErr := l.agentParsing(data.AgentIdentifier)
|
||||||
if parsingErr != nil {
|
if parsingErr != nil {
|
||||||
@@ -157,7 +263,7 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert)
|
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) {
|
func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
|
||||||
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
|
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ func (l *WechatPayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter,
|
|||||||
logx.Errorf("微信支付回调,更新订单失败%+v", updateErr)
|
logx.Errorf("微信支付回调,更新订单失败%+v", updateErr)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// 更新查询用户记录表的 platform_order_id
|
||||||
|
if rec, findErr := l.svcCtx.QueryUserRecordModel.FindOneByQueryNo(l.ctx, *notification.OutTradeNo); findErr == nil {
|
||||||
|
rec.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId)
|
||||||
|
_, _ = l.svcCtx.QueryUserRecordModel.Update(l.ctx, nil, rec)
|
||||||
|
}
|
||||||
|
|
||||||
if order.Status == "paid" {
|
if order.Status == "paid" {
|
||||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package query
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -106,6 +107,7 @@ func (l *QueryServiceLogic) ProcessMarriageLogic(req *types.QueryServiceReq) (*t
|
|||||||
if cacheDataErr != nil {
|
if cacheDataErr != nil {
|
||||||
return nil, cacheDataErr
|
return nil, cacheDataErr
|
||||||
}
|
}
|
||||||
|
l.recordQueryUserRecord(params, "marriage", userID, cacheNo)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
@@ -166,7 +168,7 @@ func (l *QueryServiceLogic) ProcessHomeServiceLogic(req *types.QueryServiceReq)
|
|||||||
if cacheDataErr != nil {
|
if cacheDataErr != nil {
|
||||||
return nil, cacheDataErr
|
return nil, cacheDataErr
|
||||||
}
|
}
|
||||||
|
l.recordQueryUserRecord(params, "homeservice", userID, cacheNo)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
@@ -227,7 +229,7 @@ func (l *QueryServiceLogic) ProcessRiskAssessmentLogic(req *types.QueryServiceRe
|
|||||||
if cacheDataErr != nil {
|
if cacheDataErr != nil {
|
||||||
return nil, cacheDataErr
|
return nil, cacheDataErr
|
||||||
}
|
}
|
||||||
|
l.recordQueryUserRecord(params, "riskassessment", userID, cacheNo)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
@@ -287,7 +289,7 @@ func (l *QueryServiceLogic) ProcessCompanyInfoLogic(req *types.QueryServiceReq)
|
|||||||
if cacheDataErr != nil {
|
if cacheDataErr != nil {
|
||||||
return nil, cacheDataErr
|
return nil, cacheDataErr
|
||||||
}
|
}
|
||||||
|
l.recordQueryUserRecord(params, "companyinfo", userID, cacheNo)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
@@ -348,7 +350,7 @@ func (l *QueryServiceLogic) ProcessRentalInfoLogic(req *types.QueryServiceReq) (
|
|||||||
if cacheDataErr != nil {
|
if cacheDataErr != nil {
|
||||||
return nil, cacheDataErr
|
return nil, cacheDataErr
|
||||||
}
|
}
|
||||||
|
l.recordQueryUserRecord(params, "rentalinfo", userID, cacheNo)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
@@ -409,7 +411,7 @@ func (l *QueryServiceLogic) ProcessPreLoanBackgroundCheckLogic(req *types.QueryS
|
|||||||
if cacheDataErr != nil {
|
if cacheDataErr != nil {
|
||||||
return nil, cacheDataErr
|
return nil, cacheDataErr
|
||||||
}
|
}
|
||||||
|
l.recordQueryUserRecord(params, "preloanbackgroundcheck", userID, cacheNo)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
@@ -469,7 +471,7 @@ func (l *QueryServiceLogic) ProcessBackgroundCheckLogic(req *types.QueryServiceR
|
|||||||
if cacheDataErr != nil {
|
if cacheDataErr != nil {
|
||||||
return nil, cacheDataErr
|
return nil, cacheDataErr
|
||||||
}
|
}
|
||||||
|
l.recordQueryUserRecord(params, "backgroundcheck", userID, cacheNo)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
@@ -527,7 +529,7 @@ func (l *QueryServiceLogic) ProcessPersonalDataLogic(req *types.QueryServiceReq)
|
|||||||
if cacheDataErr != nil {
|
if cacheDataErr != nil {
|
||||||
return nil, cacheDataErr
|
return nil, cacheDataErr
|
||||||
}
|
}
|
||||||
|
l.recordQueryUserRecord(params, "personalData", userID, cacheNo)
|
||||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
|
||||||
@@ -558,6 +560,10 @@ func (l *QueryServiceLogic) DecryptData(data string) ([]byte, error) {
|
|||||||
|
|
||||||
// 校验验证码
|
// 校验验证码
|
||||||
func (l *QueryServiceLogic) VerifyCode(mobile string, code string) error {
|
func (l *QueryServiceLogic) VerifyCode(mobile string, code string) error {
|
||||||
|
// 开发环境下跳过验证码验证
|
||||||
|
if l.svcCtx.Config.SystemConfig.SkipVerifyCode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
encryptedMobile, err := crypto.EncryptMobile(mobile, secretKey)
|
encryptedMobile, err := crypto.EncryptMobile(mobile, secretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -579,6 +585,10 @@ func (l *QueryServiceLogic) VerifyCode(mobile string, code string) error {
|
|||||||
|
|
||||||
// 二、三要素验证
|
// 二、三要素验证
|
||||||
func (l *QueryServiceLogic) Verify(Name string, IDCard string, Mobile string) error {
|
func (l *QueryServiceLogic) Verify(Name string, IDCard string, Mobile string) error {
|
||||||
|
// 空报告模式:开发环境下跳过二/三要素验证
|
||||||
|
if l.svcCtx.Config.SystemConfig.SkipVerify {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if !l.svcCtx.Config.SystemConfig.ThreeVerify {
|
if !l.svcCtx.Config.SystemConfig.ThreeVerify {
|
||||||
twoVerification := service.TwoFactorVerificationRequest{
|
twoVerification := service.TwoFactorVerificationRequest{
|
||||||
Name: Name,
|
Name: Name,
|
||||||
@@ -643,6 +653,76 @@ func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product str
|
|||||||
return outTradeNo, nil
|
return outTradeNo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recordQueryUserRecord 写入查询用户记录表,用于通过姓名/身份证/手机号追溯订单
|
||||||
|
// 重要:name、id_card、mobile 必须以 AES-ECB+Base64 密文入库,禁止写入明文
|
||||||
|
func (l *QueryServiceLogic) recordQueryUserRecord(params map[string]interface{}, product string, userID int64, queryNo string) {
|
||||||
|
getStr := func(k string) string {
|
||||||
|
if v, ok := params[k]; ok {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||||
|
if secretKey == "" {
|
||||||
|
l.Errorf("查询用户记录表加密失败, Encrypt.SecretKey 未配置,拒绝写入明文 queryNo=%s", queryNo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key, keyErr := hex.DecodeString(secretKey)
|
||||||
|
if keyErr != nil {
|
||||||
|
l.Errorf("查询用户记录表加密失败, 密钥解析错误 queryNo=%s err=%v", queryNo, keyErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 以下三字段仅使用加密后的值赋值,不得使用 getStr 的明文
|
||||||
|
encName := ""
|
||||||
|
if name := getStr("name"); name != "" {
|
||||||
|
if s, err := crypto.AesEcbEncrypt([]byte(name), key); err != nil {
|
||||||
|
l.Errorf("查询用户记录表姓名加密失败 queryNo=%s err=%v", queryNo, err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
encName = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encIdCard := ""
|
||||||
|
if idCard := getStr("id_card"); idCard != "" {
|
||||||
|
if s, err := crypto.EncryptIDCard(idCard, key); err != nil {
|
||||||
|
l.Errorf("查询用户记录表身份证加密失败 queryNo=%s err=%v", queryNo, err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
encIdCard = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encMobile := ""
|
||||||
|
if mobile := getStr("mobile"); mobile != "" {
|
||||||
|
if s, err := crypto.EncryptMobile(mobile, secretKey); err != nil {
|
||||||
|
l.Errorf("查询用户记录表手机号加密失败 queryNo=%s err=%v", queryNo, err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
encMobile = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
agentIdentifier := sql.NullString{}
|
||||||
|
if v, ok := l.ctx.Value("agentIdentifier").(string); ok && v != "" {
|
||||||
|
agentIdentifier = sql.NullString{String: v, Valid: true}
|
||||||
|
}
|
||||||
|
// rec 的 Name、IdCard、Mobile 仅使用密文 encName、encIdCard、encMobile
|
||||||
|
rec := &model.QueryUserRecord{
|
||||||
|
UserId: userID,
|
||||||
|
Name: encName,
|
||||||
|
IdCard: encIdCard,
|
||||||
|
Mobile: encMobile,
|
||||||
|
Product: product,
|
||||||
|
QueryNo: queryNo,
|
||||||
|
OrderId: 0,
|
||||||
|
PlatformOrderId: sql.NullString{},
|
||||||
|
AgentIdentifier: agentIdentifier,
|
||||||
|
}
|
||||||
|
if _, err := l.svcCtx.QueryUserRecordModel.Insert(l.ctx, nil, rec); err != nil {
|
||||||
|
l.Errorf("查询用户记录表写入失败 queryNo=%s err=%v", queryNo, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetOrCreateUser 获取或创建用户
|
// GetOrCreateUser 获取或创建用户
|
||||||
// 1. 如果上下文中已有用户ID,直接返回
|
// 1. 如果上下文中已有用户ID,直接返回
|
||||||
// 2. 如果是代理查询或APP请求,创建新用户
|
// 2. 如果是代理查询或APP请求,创建新用户
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 加密手机号失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 加密手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
if req.Mobile != "18889793585" {
|
// 开发环境下跳过验证码验证,或者特定手机号跳过(保留原有逻辑)
|
||||||
|
if !l.svcCtx.Config.SystemConfig.SkipVerifyCode && req.Mobile != "18889793585" {
|
||||||
// 检查手机号是否在一分钟内已发送过验证码
|
// 检查手机号是否在一分钟内已发送过验证码
|
||||||
redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile)
|
redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile)
|
||||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||||
|
|||||||
@@ -37,17 +37,20 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err)
|
||||||
}
|
}
|
||||||
// 检查手机号是否在一分钟内已发送过验证码
|
// 开发环境下跳过验证码验证
|
||||||
redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile)
|
if !l.svcCtx.Config.SystemConfig.SkipVerifyCode {
|
||||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
// 检查手机号是否在一分钟内已发送过验证码
|
||||||
if err != nil {
|
redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile)
|
||||||
if errors.Is(err, redis.Nil) {
|
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机登录, 验证码过期: %s", encryptedMobile)
|
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
|
var userID int64
|
||||||
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
paylogic "tydata-server/app/main/api/internal/logic/pay"
|
paylogic "tydata-server/app/main/api/internal/logic/pay"
|
||||||
|
"tydata-server/app/main/api/internal/service"
|
||||||
"tydata-server/app/main/api/internal/svc"
|
"tydata-server/app/main/api/internal/svc"
|
||||||
"tydata-server/app/main/api/internal/types"
|
"tydata-server/app/main/api/internal/types"
|
||||||
"tydata-server/app/main/model"
|
"tydata-server/app/main/model"
|
||||||
@@ -45,12 +46,13 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("无效的订单ID: %d, %v", payload.OrderID, err)
|
return fmt.Errorf("无效的订单ID: %d, %v", payload.OrderID, err)
|
||||||
}
|
}
|
||||||
env := os.Getenv("ENV")
|
// 必须已支付才处理:仅支付宝/微信/苹果回调或 pay_method=test 的异步流程会将订单标为 paid,此处不再按 ENV 放宽
|
||||||
if order.Status != "paid" && env != "development" {
|
if order.Status != "paid" {
|
||||||
err = fmt.Errorf("无效的订单: %d", payload.OrderID)
|
err = fmt.Errorf("无效的订单状态(非已支付): orderID=%d, status=%s", payload.OrderID, order.Status)
|
||||||
logx.Errorf("处理任务失败,原因: %v", err)
|
logx.Errorf("处理任务失败,原因: %v", err)
|
||||||
return asynq.SkipRetry
|
return asynq.SkipRetry
|
||||||
}
|
}
|
||||||
|
env := os.Getenv("ENV")
|
||||||
product, err := l.svcCtx.ProductModel.FindOne(ctx, order.ProductId)
|
product, err := l.svcCtx.ProductModel.FindOne(ctx, order.ProductId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("找不到相关产品: orderID: %d, productID: %d", payload.OrderID, order.ProductId)
|
return fmt.Errorf("找不到相关产品: orderID: %d, productID: %d", payload.OrderID, order.ProductId)
|
||||||
@@ -141,11 +143,20 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
|
|||||||
decryptData = updatedDecryptData
|
decryptData = updatedDecryptData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 调用API请求服务(开发环境下不调用其它产品,使用默认空报告)
|
||||||
// 调用API请求服务
|
var responseData []service.APIResponseData
|
||||||
responseData, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id)
|
if env == "development" {
|
||||||
if err != nil {
|
// 开发环境:生成仅包含基本信息的默认空报告,不调用外部 API
|
||||||
return l.handleError(ctx, err, order, query)
|
// 空报告模式:生成空的报告数据,跳过API调用
|
||||||
|
logx.Infof("空报告模式:订单 %s (ID: %s) 跳过API调用,生成空报告", order.OrderNo, order.Id)
|
||||||
|
// 空数组,表示没有数据;与 json.Marshal 配合得到 []
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算成功模块的总成本价
|
// 计算成功模块的总成本价
|
||||||
|
|||||||
@@ -1434,6 +1434,7 @@ func (a *ApiRequestService) ProcessIVYZ3P9MRequest(params []byte) ([]byte, error
|
|||||||
|
|
||||||
return convertTianyuanResponse(resp)
|
return convertTianyuanResponse(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessFLXG7E8FRequest 个人涉诉
|
// ProcessFLXG7E8FRequest 个人涉诉
|
||||||
func (a *ApiRequestService) ProcessFLXG7E8FRequest(params []byte) ([]byte, error) {
|
func (a *ApiRequestService) ProcessFLXG7E8FRequest(params []byte) ([]byte, error) {
|
||||||
idCard := gjson.GetBytes(params, "id_card")
|
idCard := gjson.GetBytes(params, "id_card")
|
||||||
@@ -1444,9 +1445,9 @@ func (a *ApiRequestService) ProcessFLXG7E8FRequest(params []byte) ([]byte, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp, err := a.tianyuanapi.CallInterface("FLXG7E8F", map[string]interface{}{
|
resp, err := a.tianyuanapi.CallInterface("FLXG7E8F", map[string]interface{}{
|
||||||
"id_card": idCard.String(),
|
"id_card": idCard.String(),
|
||||||
"name": name.String(),
|
"name": name.String(),
|
||||||
"mobile_no": mobile.String(),
|
"mobile_no": mobile.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type ServiceContext struct {
|
|||||||
OrderModel model.OrderModel
|
OrderModel model.OrderModel
|
||||||
OrderRefundModel model.OrderRefundModel
|
OrderRefundModel model.OrderRefundModel
|
||||||
QueryModel model.QueryModel
|
QueryModel model.QueryModel
|
||||||
|
QueryUserRecordModel model.QueryUserRecordModel
|
||||||
QueryCleanupLogModel model.QueryCleanupLogModel
|
QueryCleanupLogModel model.QueryCleanupLogModel
|
||||||
QueryCleanupDetailModel model.QueryCleanupDetailModel
|
QueryCleanupDetailModel model.QueryCleanupDetailModel
|
||||||
QueryCleanupConfigModel model.QueryCleanupConfigModel
|
QueryCleanupConfigModel model.QueryCleanupConfigModel
|
||||||
@@ -128,6 +129,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
// ============================== 订单相关模型 ==============================
|
// ============================== 订单相关模型 ==============================
|
||||||
orderModel := model.NewOrderModel(db, cacheConf)
|
orderModel := model.NewOrderModel(db, cacheConf)
|
||||||
queryModel := model.NewQueryModel(db, cacheConf)
|
queryModel := model.NewQueryModel(db, cacheConf)
|
||||||
|
queryUserRecordModel := model.NewQueryUserRecordModel(db, cacheConf)
|
||||||
orderRefundModel := model.NewOrderRefundModel(db, cacheConf)
|
orderRefundModel := model.NewOrderRefundModel(db, cacheConf)
|
||||||
queryCleanupLogModel := model.NewQueryCleanupLogModel(db, cacheConf)
|
queryCleanupLogModel := model.NewQueryCleanupLogModel(db, cacheConf)
|
||||||
queryCleanupDetailModel := model.NewQueryCleanupDetailModel(db, cacheConf)
|
queryCleanupDetailModel := model.NewQueryCleanupDetailModel(db, cacheConf)
|
||||||
@@ -238,6 +240,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
// 订单相关模型
|
// 订单相关模型
|
||||||
OrderModel: orderModel,
|
OrderModel: orderModel,
|
||||||
QueryModel: queryModel,
|
QueryModel: queryModel,
|
||||||
|
QueryUserRecordModel: queryUserRecordModel,
|
||||||
OrderRefundModel: orderRefundModel,
|
OrderRefundModel: orderRefundModel,
|
||||||
QueryCleanupLogModel: queryCleanupLogModel,
|
QueryCleanupLogModel: queryCleanupLogModel,
|
||||||
QueryCleanupDetailModel: queryCleanupDetailModel,
|
QueryCleanupDetailModel: queryCleanupDetailModel,
|
||||||
|
|||||||
@@ -533,6 +533,9 @@ type AdminGetOrderListReq struct {
|
|||||||
RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始
|
RefundTimeStart string `form:"refund_time_start,optional"` // 退款时间开始
|
||||||
RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束
|
RefundTimeEnd string `form:"refund_time_end,optional"` // 退款时间结束
|
||||||
SalesCost float64 `form:"sales_cost,optional"` // 成本价
|
SalesCost float64 `form:"sales_cost,optional"` // 成本价
|
||||||
|
QueryName string `form:"query_name,optional"` // 被查询人姓名(通过 query_user_record 表追溯订单)
|
||||||
|
QueryIdCard string `form:"query_id_card,optional"` // 被查询人身份证(通过 query_user_record 表追溯订单)
|
||||||
|
QueryMobile string `form:"query_mobile,optional"` // 被查询人手机号(通过 query_user_record 表追溯订单)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminGetOrderListResp struct {
|
type AdminGetOrderListResp struct {
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import (
|
|||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tydata-server/common/globalkey"
|
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/stores/builder"
|
"github.com/zeromicro/go-zero/core/stores/builder"
|
||||||
@@ -19,6 +17,7 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/stores/sqlc"
|
"github.com/zeromicro/go-zero/core/stores/sqlc"
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
"github.com/zeromicro/go-zero/core/stringx"
|
"github.com/zeromicro/go-zero/core/stringx"
|
||||||
|
"tydata-server/common/globalkey"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tydata-server/common/globalkey"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
"tydata-server/common/globalkey"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
|||||||
76
app/main/model/queryUserRecordModel.go
Normal file
76
app/main/model/queryUserRecordModel.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tydata-server/common/globalkey"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlc"
|
||||||
|
"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
|
||||||
|
FindOneByQueryNo(ctx context.Context, queryNo string) (*QueryUserRecord, error)
|
||||||
|
CountByEncryptedIdCardIn72Hours(ctx context.Context, encryptedIdCard string) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
customQueryUserRecordModel struct {
|
||||||
|
*defaultQueryUserRecordModel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// FindOneByQueryNo 根据 query_no 查询一条记录(query_no 与 order.order_no 一致)
|
||||||
|
func (m *customQueryUserRecordModel) FindOneByQueryNo(ctx context.Context, queryNo string) (*QueryUserRecord, error) {
|
||||||
|
query := fmt.Sprintf("select %s from %s where `query_no` = ? and del_state = ? limit 1", queryUserRecordRows, m.table)
|
||||||
|
var resp QueryUserRecord
|
||||||
|
err := m.QueryRowNoCacheCtx(ctx, &resp, query, queryNo, globalkey.DelStateNo)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return &resp, nil
|
||||||
|
case sqlc.ErrNotFound:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountByEncryptedIdCardIn72Hours 查询72小时内某个加密身份证号的已支付查询次数
|
||||||
|
func (m *customQueryUserRecordModel) CountByEncryptedIdCardIn72Hours(ctx context.Context, encryptedIdCard string) (int64, error) {
|
||||||
|
// 计算72小时前的时间
|
||||||
|
seventyTwoHoursAgo := time.Now().Add(-72 * time.Hour)
|
||||||
|
|
||||||
|
// 关联 order 表,只统计已支付的订单
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
select count(*)
|
||||||
|
from %s qur
|
||||||
|
inner join `+"`order`"+` o on qur.order_id = o.id
|
||||||
|
where qur.id_card = ?
|
||||||
|
and qur.create_time >= ?
|
||||||
|
and qur.del_state = ?
|
||||||
|
and qur.order_id > 0
|
||||||
|
and o.status = 'paid'
|
||||||
|
and o.del_state = ?
|
||||||
|
`, m.table)
|
||||||
|
var count int64
|
||||||
|
err := m.QueryRowNoCacheCtx(ctx, &count, query, encryptedIdCard, seventyTwoHoursAgo, globalkey.DelStateNo, globalkey.DelStateNo)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQueryUserRecordModel returns a model for the database table.
|
||||||
|
func NewQueryUserRecordModel(conn sqlx.SqlConn, c cache.CacheConf) QueryUserRecordModel {
|
||||||
|
return &customQueryUserRecordModel{
|
||||||
|
defaultQueryUserRecordModel: newQueryUserRecordModel(conn, c),
|
||||||
|
}
|
||||||
|
}
|
||||||
376
app/main/model/queryUserRecordModel_gen.go
Normal file
376
app/main/model/queryUserRecordModel_gen.go
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
// Code generated by goctl. DO NOT EDIT!
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tydata-server/common/globalkey"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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`"), "=?,") + "=?"
|
||||||
|
|
||||||
|
cacheTydataQueryUserRecordIdPrefix = "cache:tydata: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
|
||||||
|
tydataQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTydataQueryUserRecordIdPrefix, 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)
|
||||||
|
}, tydataQueryUserRecordIdKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultQueryUserRecordModel) FindOne(ctx context.Context, id int64) (*QueryUserRecord, error) {
|
||||||
|
tydataQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTydataQueryUserRecordIdPrefix, id)
|
||||||
|
var resp QueryUserRecord
|
||||||
|
err := m.QueryRowCtx(ctx, &resp, tydataQueryUserRecordIdKey, 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) {
|
||||||
|
tydataQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTydataQueryUserRecordIdPrefix, 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)
|
||||||
|
}, tydataQueryUserRecordIdKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
tydataQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTydataQueryUserRecordIdPrefix, 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)
|
||||||
|
}, tydataQueryUserRecordIdKey)
|
||||||
|
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 {
|
||||||
|
tydataQueryUserRecordIdKey := fmt.Sprintf("%s%v", cacheTydataQueryUserRecordIdPrefix, 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)
|
||||||
|
}, tydataQueryUserRecordIdKey)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (m *defaultQueryUserRecordModel) formatPrimary(primary interface{}) string {
|
||||||
|
return fmt.Sprintf("%s%v", cacheTydataQueryUserRecordIdPrefix, 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
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ $tables = @(
|
|||||||
# "product",
|
# "product",
|
||||||
# "product_feature"
|
# "product_feature"
|
||||||
# "query",
|
# "query",
|
||||||
"query_user_record" # 查询用户记录表:姓名、身份证、手机号、支付订单号等
|
# "query_user_record" # 查询用户记录表:姓名、身份证、手机号、支付订单号等
|
||||||
# "query_cleanup_log"
|
# "query_cleanup_log"
|
||||||
# "query_cleanup_detail"
|
# "query_cleanup_detail"
|
||||||
# "query_cleanup_config"
|
# "query_cleanup_config"
|
||||||
|
|||||||
42
deploy/sql/query_user_record.sql
Normal file
42
deploy/sql/query_user_record.sql
Normal file
@@ -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='查询用户记录表:姓名、身份证、手机号、支付订单号等,用于通过查询信息追溯订单';
|
||||||
Reference in New Issue
Block a user