feat(all): v1.0

This commit is contained in:
liangzai 2024-11-21 12:14:34 +08:00
parent d09e7a08cf
commit 6ce1a303f1
61 changed files with 3106 additions and 1046 deletions

View File

@ -0,0 +1,24 @@
syntax = "v1"
info (
title: "支付服务"
desc: "支付服务"
author: "Liangzai"
email: "2440983361@qq.com"
version: "v1"
)
@server (
prefix: api/v1
group: pay
)
service main {
// 微信支付回调
@handler WechatPayCallback
post /pay/wechat/callback
// 支付宝支付回调
@handler AlipayCallback
post /pay/alipay/callback
}

View File

@ -0,0 +1,5 @@
syntax = "proto3";
option go_package = "./pb";
package pb;

View File

@ -0,0 +1,17 @@
syntax = "proto3";
option go_package = "./pb";
package pb;
message StreamReq {
string name = 1;
}
message StreamResp {
string greet = 1;
}
service StreamGreeter {
rpc greet(StreamReq) returns (StreamResp);
}

View File

@ -1,6 +0,0 @@
root = "."
[build]
cmd = "go build -o ./tmp/main ./app/user/cmd/api/user.go" # 指定 user-api 的入口文件
bin = "./tmp/main"
include_ext = ["go", "tmpl", "html"]
exclude_dir = ["tmp", "deploy", "data"]

View File

@ -10,7 +10,7 @@ info (
type ( type (
sendSmsReq { sendSmsReq {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
ActionType string `json:"actionType" validate:"required,oneof=loginCode registerCode QueryCode"` ActionType string `json:"actionType" validate:"required,oneof=login register query"`
} }
) )

View File

@ -0,0 +1,14 @@
syntax = "v1"
info (
title: "单体服务中心"
desc: "单体服务中心"
author: "Liangzai"
email: "2440983361@qq.com"
version: "v1"
)
import "user.api"
import "query.api"
import "pay.api"
import "product.api"

View File

@ -0,0 +1,28 @@
syntax = "v1"
info (
title: "支付服务"
desc: "支付服务"
author: "Liangzai"
email: "2440983361@qq.com"
version: "v1"
)
@server (
prefix: api/v1
group: pay
)
service main {
// 微信支付回调
@handler WechatPayCallback
post /pay/wechat/callback
// 支付宝支付回调
@handler AlipayCallback
post /pay/alipay/callback
// 微信退款回调
@handler WechatPayRefundCallback
post /pay/wechat/refund_callback
}

View File

@ -0,0 +1,25 @@
syntax = "v1"
info (
title: "产品服务"
desc: "产品服务"
author: "Liangzai"
email: "2440983361@qq.com"
version: "v1"
)
import (
"product/product.api"
)
@server (
prefix: api/v1/product
group: product
jwt: JwtAuth
)
service main {
@handler GetProductByID
get /:id (GetProductByIDRequest) returns (ProductResponse)
@handler GetProductByEn
get /en/:product_en (GetProductByEnRequest) returns (ProductResponse)
}

View File

@ -0,0 +1,36 @@
syntax = "v1"
info (
title: "产品查询服务"
desc: "产品查询服务"
author: "Liangzai"
email: "2440983361@qq.com"
)
type Product {
ProductName string `json:"product_name"`
ProductEn string `json:"product_en"`
Description string `json:"description"`
Notes string `json:"notes,optional"`
SellPrice float64 `json:"sell_price"`
Features []Feature `json:"features"` // 关联功能列表
}
type Feature {
ID int64 `json:"id"` // 功能ID
ApiID string `json:"api_id"` // API标识
Name string `json:"name"` // 功能描述
}
type GetProductByIDRequest {
Id int64 `path:"id"`
}
type GetProductByEnRequest {
ProductEn string `path:"product_en"`
}
type ProductResponse {
Product
}

View File

@ -0,0 +1,84 @@
syntax = "v1"
info (
title: "产品查询服务"
desc: "产品查询服务"
author: "Liangzai"
email: "2440983361@qq.com"
version: "v1"
)
import (
"query/query.api"
)
//============================> query v1 <============================
@server (
prefix: api/v1
group: query
jwt: JwtAuth
)
service main {
@doc "query marriage"
@handler marriage
post /query/marriage (QueryReq) returns (QueryResp)
// 家政服务查询
@doc "query home service"
@handler homeService
post /query/homeService (QueryReq) returns (QueryResp)
// 风险评估查询
@doc "query risk assessment"
@handler riskAssessment
post /query/riskAssessment (QueryReq) returns (QueryResp)
// 企业信息查询
@doc "query company info"
@handler companyInfo
post /query/companyInfo (QueryReq) returns (QueryResp)
// 租赁信息查询
@doc "query rental info"
@handler rentalInfo
post /query/rentalInfo (QueryReq) returns (QueryResp)
// 贷前背景调查
@doc "query pre-loan background check"
@handler preLoanBackgroundCheck
post /query/preLoanBackgroundCheck (QueryReq) returns (QueryResp)
// 一般背景调查
@doc "query general background check"
@handler backgroundCheck
post /query/backgroundCheck (QueryReq) returns (QueryResp)
}
@server (
prefix: api/v1
group: query
jwt: JwtAuth
)
service main {
@doc "查询示例"
@handler queryExample
get /query/example (QueryExampleReq) returns (QueryExampleResp)
@doc "查询列表"
@handler queryList
get /query/list (QueryListReq) returns (QueryListResp)
@doc "查询详情 按订单号 付款查询时"
@handler queryDetailByOrderId
get /query/orderId/:order_id (QueryDetailByOrderIdReq) returns (QueryDetailByOrderIdResp)
@doc "查询详情"
@handler queryDetail
get /query/:id (QueryDetailReq) returns (QueryDetailResp)
@doc "重试查询"
@handler queryRetry
post /query/retry/:id (QueryRetryReq) returns (QueryRetryResp)
}

View File

@ -0,0 +1,75 @@
syntax = "v1"
info (
title: "产品查询服务"
desc: "产品查询服务"
author: "Liangzai"
email: "2440983361@qq.com"
)
type (
QueryReq {
Data string `json:"data" validate:"required"`
}
QueryResp {
prepayID string `json:"prepay_id"`
OrderID int64 `json:"order_id"`
}
)
type Query {
Id int64 `json:"id"` // 主键ID
OrderId int64 `json:"order_id"` // 订单ID
UserId int64 `json:"user_id"` // 用户ID
ProductId int64 `json:"product_id"` // 产品ID
QueryData []map[string]interface{} `json:"query_data"`
CreateTime string `json:"create_time"` // 创建时间
UpdateTime string `json:"update_time"` // 更新时间
QueryState string `json:"query_state"` // 查询状态
}
type (
QueryListReq {
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数据量
}
QueryListResp {
Total int64 `json:"total"` // 总记录数
List []Query `json:"list"` // 查询列表
}
)
type (
QueryExampleReq {
feature string `form:"feature"`
}
QueryExampleResp {
Query
}
)
type (
QueryDetailReq {
Id int64 `path:"id"`
}
QueryDetailResp {
Query
}
)
type (
QueryDetailByOrderIdReq {
OrderId int64 `path:"order_id"`
}
QueryDetailByOrderIdResp {
Query
}
)
type (
QueryRetryReq {
Id int64 `path:"id"`
}
QueryRetryResp {}
)

View File

@ -16,10 +16,10 @@ import (
//============================> user v1 <============================ //============================> user v1 <============================
//no need login //no need login
@server ( @server (
prefix: user/v1 prefix: api/v1
group: user group: user
) )
service user { service main {
@doc "register" @doc "register"
@handler register @handler register
post /user/register (RegisterReq) returns (RegisterResp) post /user/register (RegisterReq) returns (RegisterResp)
@ -35,14 +35,14 @@ service user {
//need login //need login
@server ( @server (
prefix: user/v1 prefix: api/v1
group: user group: user
jwt: JwtAuth jwt: JwtAuth
) )
service user { service main {
@doc "get user info" @doc "get user info"
@handler detail @handler detail
post /user/detail (UserInfoReq) returns (UserInfoResp) get /user/detail returns (UserInfoResp)
@doc "wechat mini auth" @doc "wechat mini auth"
@handler wxMiniAuth @handler wxMiniAuth
@ -51,10 +51,10 @@ service user {
//============================> auth v1 <============================ //============================> auth v1 <============================
@server ( @server (
prefix: auth/v1 prefix: api/v1
group: auth group: auth
) )
service user { service main {
@doc "get mobile verify code" @doc "get mobile verify code"
@handler sendSms @handler sendSms
post /auth/sendSms (sendSmsReq) post /auth/sendSms (sendSmsReq)

View File

@ -64,7 +64,6 @@ type (
) )
type ( type (
UserInfoReq {}
UserInfoResp { UserInfoResp {
UserInfo User `json:"userInfo"` UserInfo User `json:"userInfo"`
} }

View File

@ -1,19 +0,0 @@
Name: user
Host: 0.0.0.0
Port: 8888
DataSource: "qnc:5vg67b3UNHu8@tcp(127.0.0.1:20001)/qnc?charset=utf8mb4&parseTime=True&loc=Local"
CacheRedis:
- Host: "127.0.0.1:20002"
Pass: "3m3WsgyCKWqz" # Redis 密码,如果未设置则留空
Type: "node" # 单节点模式
JwtAuth:
AccessSecret: "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA"
AccessExpire: 86400 # JWT过期时间
RefreshAfter: 43200 # 更新时间
VerifyCode:
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
EndpointURL: "dysmsapi.aliyuncs.com"
SignName: "天远数据"
TemplateCode: "SMS_474525324"
ValidTime: 300

View File

@ -11,6 +11,11 @@ type Config struct {
CacheRedis cache.CacheConf CacheRedis cache.CacheConf
JwtAuth JwtAuth // JWT 鉴权相关配置 JwtAuth JwtAuth // JWT 鉴权相关配置
VerifyCode VerifyCode VerifyCode VerifyCode
Encrypt Encrypt
Alipay AlipayConfig
Wxpay WxpayConfig
Ali AliConfig
WestConfig WestConfig
} }
// JwtAuth 用于 JWT 鉴权配置 // JwtAuth 用于 JWT 鉴权配置
@ -27,3 +32,32 @@ type VerifyCode struct {
TemplateCode string TemplateCode string
ValidTime int ValidTime int
} }
type Encrypt struct {
SecretKey string
}
type AlipayConfig struct {
AppID string
PrivateKey string
AlipayPublicKey string
IsProduction bool
NotifyUrl string
}
type WxpayConfig struct {
AppID string
MchID string
MchCertificateSerialNumber string
MchApiv3Key string
MchPrivateKeyPath string
NotifyUrl string
RefundNotifyUrl string
}
type AliConfig struct {
Code string
}
type WestConfig struct {
Url string
Key string
SecretId string
SecretSecondId string
}

View File

@ -5,6 +5,9 @@ import (
"net/http" "net/http"
auth "qnc-server/app/user/cmd/api/internal/handler/auth" auth "qnc-server/app/user/cmd/api/internal/handler/auth"
pay "qnc-server/app/user/cmd/api/internal/handler/pay"
product "qnc-server/app/user/cmd/api/internal/handler/product"
query "qnc-server/app/user/cmd/api/internal/handler/query"
user "qnc-server/app/user/cmd/api/internal/handler/user" user "qnc-server/app/user/cmd/api/internal/handler/user"
"qnc-server/app/user/cmd/api/internal/svc" "qnc-server/app/user/cmd/api/internal/svc"
@ -21,7 +24,131 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Handler: auth.SendSmsHandler(serverCtx), Handler: auth.SendSmsHandler(serverCtx),
}, },
}, },
rest.WithPrefix("/auth/v1"), rest.WithPrefix("/api/v1"),
)
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodPost,
Path: "/pay/alipay/callback",
Handler: pay.AlipayCallbackHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/pay/wechat/callback",
Handler: pay.WechatPayCallbackHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/pay/wechat/refund_callback",
Handler: pay.WechatPayRefundCallbackHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1"),
)
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/:id",
Handler: product.GetProductByIDHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/en/:product_en",
Handler: product.GetProductByEnHandler(serverCtx),
},
},
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
rest.WithPrefix("/api/v1/product"),
)
server.AddRoutes(
[]rest.Route{
{
// query general background check
Method: http.MethodPost,
Path: "/query/backgroundCheck",
Handler: query.BackgroundCheckHandler(serverCtx),
},
{
// query company info
Method: http.MethodPost,
Path: "/query/companyInfo",
Handler: query.CompanyInfoHandler(serverCtx),
},
{
// query home service
Method: http.MethodPost,
Path: "/query/homeService",
Handler: query.HomeServiceHandler(serverCtx),
},
{
// query marriage
Method: http.MethodPost,
Path: "/query/marriage",
Handler: query.MarriageHandler(serverCtx),
},
{
// query pre-loan background check
Method: http.MethodPost,
Path: "/query/preLoanBackgroundCheck",
Handler: query.PreLoanBackgroundCheckHandler(serverCtx),
},
{
// query rental info
Method: http.MethodPost,
Path: "/query/rentalInfo",
Handler: query.RentalInfoHandler(serverCtx),
},
{
// query risk assessment
Method: http.MethodPost,
Path: "/query/riskAssessment",
Handler: query.RiskAssessmentHandler(serverCtx),
},
},
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
rest.WithPrefix("/api/v1"),
)
server.AddRoutes(
[]rest.Route{
{
// 查询详情
Method: http.MethodGet,
Path: "/query/:id",
Handler: query.QueryDetailHandler(serverCtx),
},
{
// 查询示例
Method: http.MethodGet,
Path: "/query/example",
Handler: query.QueryExampleHandler(serverCtx),
},
{
// 查询列表
Method: http.MethodGet,
Path: "/query/list",
Handler: query.QueryListHandler(serverCtx),
},
{
// 查询详情 按订单号 付款查询时
Method: http.MethodGet,
Path: "/query/orderId/:order_id",
Handler: query.QueryDetailByOrderIdHandler(serverCtx),
},
{
// 重试查询
Method: http.MethodPost,
Path: "/query/retry/:id",
Handler: query.QueryRetryHandler(serverCtx),
},
},
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
rest.WithPrefix("/api/v1"),
) )
server.AddRoutes( server.AddRoutes(
@ -45,14 +172,14 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Handler: user.RegisterHandler(serverCtx), Handler: user.RegisterHandler(serverCtx),
}, },
}, },
rest.WithPrefix("/user/v1"), rest.WithPrefix("/api/v1"),
) )
server.AddRoutes( server.AddRoutes(
[]rest.Route{ []rest.Route{
{ {
// get user info // get user info
Method: http.MethodPost, Method: http.MethodGet,
Path: "/user/detail", Path: "/user/detail",
Handler: user.DetailHandler(serverCtx), Handler: user.DetailHandler(serverCtx),
}, },
@ -64,6 +191,6 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
}, },
}, },
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
rest.WithPrefix("/user/v1"), rest.WithPrefix("/api/v1"),
) )
} }

View File

@ -34,8 +34,8 @@ func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLo
func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error { func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
// 检查手机号是否在一分钟内已发送过验证码 // 检查手机号是否在一分钟内已发送过验证码
redisKey := fmt.Sprintf("%s:%s", req.ActionType, req.Mobile) limitCodeKey := fmt.Sprintf("limit:%s:%s", req.ActionType, req.Mobile)
exists, err := l.svcCtx.Redis.Exists(redisKey) exists, err := l.svcCtx.Redis.Exists(limitCodeKey)
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 读取redis缓存失败: %s", req.Mobile) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 读取redis缓存失败: %s", req.Mobile)
} }
@ -55,14 +55,14 @@ func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
if *smsResp.Body.Code != "OK" { if *smsResp.Body.Code != "OK" {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 阿里客户端响应失败: %s", *smsResp.Body.Message) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 阿里客户端响应失败: %s", *smsResp.Body.Message)
} }
codeKey := fmt.Sprintf("%s:%s", req.ActionType, req.Mobile)
// 将验证码保存到 Redis设置过期时间 // 将验证码保存到 Redis设置过期时间
err = l.svcCtx.Redis.Setex(req.Mobile, code, l.svcCtx.Config.VerifyCode.ValidTime) // 验证码有效期5分钟 err = l.svcCtx.Redis.Setex(codeKey, code, l.svcCtx.Config.VerifyCode.ValidTime) // 验证码有效期5分钟
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置过期时间失败: %+v", err) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置过期时间失败: %+v", err)
} }
// 在 Redis 中设置 1 分钟的标记,限制重复请求 // 在 Redis 中设置 1 分钟的标记,限制重复请求
err = l.svcCtx.Redis.Setex(redisKey, code, 60) // 标记 1 分钟内不能重复请求 err = l.svcCtx.Redis.Setex(limitCodeKey, code, 60) // 标记 1 分钟内不能重复请求
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置限制重复请求失败: %+v", err) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置限制重复请求失败: %+v", err)
} }

View File

@ -26,7 +26,7 @@ func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogi
} }
} }
func (l *DetailLogic) Detail(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) { func (l *DetailLogic) Detail() (resp *types.UserInfoResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx) userID, err := ctxdata.GetUidFromCtx(l.ctx)
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)

View File

@ -12,6 +12,7 @@ import (
jwtx "qnc-server/common/jwt" jwtx "qnc-server/common/jwt"
"qnc-server/common/tool" "qnc-server/common/tool"
"qnc-server/common/xerr" "qnc-server/common/xerr"
"qnc-server/pkg/lzkit/lzUtils"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@ -32,7 +33,7 @@ func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Register
func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterResp, err error) { func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterResp, err error) {
// 检查手机号是否在一分钟内已发送过验证码 // 检查手机号是否在一分钟内已发送过验证码
redisKey := fmt.Sprintf("%s:%s", "registerCode", req.Mobile) redisKey := fmt.Sprintf("%s:%s", "register", req.Mobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey) cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil { if err != nil {
if errors.Is(err, redis.Nil) { if errors.Is(err, redis.Nil) {
@ -58,7 +59,7 @@ func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterRe
user.Nickname = req.Mobile user.Nickname = req.Mobile
} }
if len(req.Password) > 0 { if len(req.Password) > 0 {
user.Password = tool.Md5ByString(req.Password) user.Password = lzUtils.StringToNullString(tool.Md5ByString(req.Password))
} }
insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user) insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user)
if userInsertErr != nil { if userInsertErr != nil {

View File

@ -0,0 +1,179 @@
package queue
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/hibiken/asynq"
"github.com/zeromicro/go-zero/core/logx"
"qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types"
"qnc-server/app/user/model"
"qnc-server/pkg/lzkit/crypto"
"qnc-server/pkg/lzkit/lzUtils"
)
type PaySuccessNotifyUserHandler struct {
svcCtx *svc.ServiceContext
}
func NewPaySuccessNotifyUserHandler(svcCtx *svc.ServiceContext) *PaySuccessNotifyUserHandler {
return &PaySuccessNotifyUserHandler{
svcCtx: svcCtx,
}
}
var payload struct {
OrderID int64 `json:"order_id"`
}
func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
// 从任务的负载中解码数据
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("解析任务负载失败: %w", err)
}
order, err := l.svcCtx.OrderModel.FindOne(ctx, payload.OrderID)
if err != nil {
return fmt.Errorf("无效的订单ID: %d, %v", payload.OrderID, err)
}
if order.Status != "paid" {
err = fmt.Errorf("无效的订单: %d", payload.OrderID)
logx.Errorf("处理任务失败,原因: %v", err)
return asynq.SkipRetry
}
product, err := l.svcCtx.ProductModel.FindOne(ctx, order.ProductId)
if err != nil {
return fmt.Errorf("找不到相关产品: orderID: %d, productID: %d", payload.OrderID, order.ProductId)
}
query, findQueryErr := l.svcCtx.QueryModel.FindOneByOrderId(ctx, order.Id)
if findQueryErr != nil {
findQueryErr = fmt.Errorf("获取任务请求参数失败: %v", findQueryErr)
logx.Errorf("处理任务失败,原因: %v", findQueryErr)
return asynq.SkipRetry
}
if query.QueryState != "pending" {
err = fmt.Errorf("查询已处理: %d", query.Id)
logx.Errorf("处理任务失败,原因: %v", err)
return asynq.SkipRetry
}
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
err = fmt.Errorf("获取AES密钥失败: %v", decodeErr)
return l.handleError(ctx, err, order, query)
}
decryptData, aesdecryptErr := crypto.AesDecrypt(query.QueryParams, key)
if aesdecryptErr != nil {
aesdecryptErr = fmt.Errorf("加密响应信息失败: %v", aesdecryptErr)
return l.handleError(ctx, aesdecryptErr, order, query)
}
requests, exists := types.WestDexParams[product.ProductEn]
if !exists {
err = fmt.Errorf("未找到有效的参数配置: productEn: %s", product.ProductEn)
return l.handleError(ctx, err, order, query)
}
// 根据产品类型选择结构体类型
var requestData interface{}
switch product.ProductEn {
case "marriage":
requestData = &types.MarriageReq{}
case "homeservice":
requestData = &types.HomeServiceReq{}
case "riskassessment":
requestData = &types.RiskAssessmentReq{}
case "companyinfo":
requestData = &types.CompanyInfoReq{}
case "rentalinfo":
requestData = &types.RentalInfoReq{}
case "preloanbackgroundcheck":
requestData = &types.PreLoanBackgroundCheckReq{}
case "backgroundcheck":
requestData = &types.BackgroundCheckReq{}
default:
err = fmt.Errorf("未支持的产品类型: productEn: %s", product.ProductEn)
return l.handleError(ctx, err, order, query)
}
unmarshalErr := json.Unmarshal(decryptData, &requestData)
if unmarshalErr != nil {
unmarshalErr = fmt.Errorf("解析参数失败: %v", unmarshalErr)
return l.handleError(ctx, unmarshalErr, order, query)
}
combinedResponse, err := l.svcCtx.WestDexService.ProcessRequests(requestData, requests)
if err != nil {
return l.handleError(ctx, err, order, query)
}
// 加密返回响应
encryptData, aesEncryptErr := crypto.AesEncrypt(combinedResponse, key)
if aesEncryptErr != nil {
err = fmt.Errorf("加密响应信息失败: %v", aesEncryptErr)
return l.handleError(ctx, aesEncryptErr, order, query)
}
query.QueryData = lzUtils.StringToNullString(encryptData)
updateErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
if updateErr != nil {
err = fmt.Errorf("保存响应数据失败: %v", updateErr)
return l.handleError(ctx, updateErr, order, query)
}
query.QueryState = "success"
updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
if updateQueryErr != nil {
updateQueryErr = fmt.Errorf("修改查询状态失败: %v", updateQueryErr)
return l.handleError(ctx, updateQueryErr, order, query)
}
return nil
}
// 定义一个中间件函数
func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error, order *model.Order, query *model.Query) error {
logx.Errorf("处理任务失败,原因: %v", err)
if order.Status == "paid" && query.QueryState == "pending" {
// 更新查询状态为失败
query.QueryState = "failed"
updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
if updateQueryErr != nil {
logx.Errorf("更新查询状态失败订单ID: %d, 错误: %v", order.Id, updateQueryErr)
return asynq.SkipRetry
}
// 退款
if order.PaymentPlatform == "wechat" {
refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount)
if refundErr != nil {
logx.Error(refundErr)
return asynq.SkipRetry
}
} else {
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
if refundErr != nil {
logx.Error(refundErr)
return asynq.SkipRetry
}
if refund.IsSuccess() {
logx.Errorf("支付宝退款成功, orderID: %d", order.Id)
// 更新订单状态为退款
order.Status = "refunded"
updateOrderErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order)
if updateOrderErr != nil {
logx.Errorf("更新订单状态失败订单ID: %d, 错误: %v", order.Id, updateOrderErr)
return fmt.Errorf("更新订单状态失败: %v", updateOrderErr)
}
return asynq.SkipRetry
} else {
logx.Errorf("支付宝退款失败:%v", refundErr)
return asynq.SkipRetry
}
// 直接成功
}
}
return asynq.SkipRetry
}

View File

@ -0,0 +1,28 @@
package queue
import (
"context"
"github.com/hibiken/asynq"
"qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types"
)
type CronJob struct {
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCronJob(ctx context.Context, svcCtx *svc.ServiceContext) *CronJob {
return &CronJob{
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CronJob) Register() *asynq.ServeMux {
mux := asynq.NewServeMux()
mux.Handle(types.MsgPaySuccessQuery, NewPaySuccessNotifyUserHandler(l.svcCtx))
return mux
}

View File

@ -0,0 +1,137 @@
package service
import (
"context"
"fmt"
"github.com/smartwalle/alipay/v3"
mathrand "math/rand"
"net/http"
"qnc-server/app/user/cmd/api/internal/config"
"qnc-server/pkg/lzkit/lzUtils"
"strconv"
"time"
)
type AliPayService struct {
config config.AlipayConfig
AlipayClient *alipay.Client
}
// NewAliPayService 是一个构造函数,用于初始化 AliPayService
func NewAliPayService(c config.Config) *AliPayService {
client, err := alipay.New(c.Alipay.AppID, c.Alipay.PrivateKey, c.Alipay.IsProduction)
if err != nil {
panic(fmt.Sprintf("创建支付宝客户端失败: %v", err))
}
// 加载支付宝公钥
err = client.LoadAliPayPublicKey(c.Alipay.AlipayPublicKey)
if err != nil {
panic(fmt.Sprintf("加载支付宝公钥失败: %v", err))
}
return &AliPayService{
config: c.Alipay,
AlipayClient: client,
}
}
func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, outTradeNo string) (string, error) {
client := a.AlipayClient
totalAmount := lzUtils.ToAlipayAmount(amount)
// 构造移动支付请求
p := alipay.TradeAppPay{
Trade: alipay.Trade{
Subject: subject,
OutTradeNo: outTradeNo,
TotalAmount: totalAmount,
ProductCode: "QUICK_MSECURITY_PAY", // 移动端支付专用代码
NotifyURL: a.config.NotifyUrl, // 异步回调通知地址
},
}
// 获取APP支付字符串这里会签名
payStr, err := client.TradeAppPay(p)
if err != nil {
return "", fmt.Errorf("创建支付宝订单失败: %v", err)
}
return payStr, nil
}
// AliRefund 发起支付宝退款
func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refundAmount float64) (*alipay.TradeRefundRsp, error) {
refund := alipay.TradeRefund{
OutTradeNo: outTradeNo,
RefundAmount: lzUtils.ToAlipayAmount(refundAmount),
OutRequestNo: fmt.Sprintf("%s-refund", outTradeNo),
}
// 发起退款请求
refundResp, err := a.AlipayClient.TradeRefund(ctx, refund)
if err != nil {
return nil, fmt.Errorf("支付宝退款请求错误:%v", err)
}
return refundResp, nil
}
// HandleAliPaymentNotification 支付宝支付回调
func (a *AliPayService) HandleAliPaymentNotification(r *http.Request) (*alipay.Notification, error) {
// 解析表单
err := r.ParseForm()
if err != nil {
return nil, fmt.Errorf("解析请求表单失败:%v", err)
}
// 解析并验证通知DecodeNotification 会自动验证签名
notification, err := a.AlipayClient.DecodeNotification(r.Form)
if err != nil {
return nil, fmt.Errorf("验证签名失败: %v", err)
}
return notification, nil
}
func (a *AliPayService) QueryOrderStatus(ctx context.Context, outTradeNo string) (*alipay.TradeQueryRsp, error) {
queryRequest := alipay.TradeQuery{
OutTradeNo: outTradeNo,
}
// 发起查询请求
resp, err := a.AlipayClient.TradeQuery(ctx, queryRequest)
if err != nil {
return nil, fmt.Errorf("查询支付宝订单失败: %v", err)
}
// 返回交易状态
if resp.IsSuccess() {
return resp, nil
}
return nil, fmt.Errorf("查询支付宝订单失败: %v", resp.SubMsg)
}
// GenerateOutTradeNo 生成唯一订单号的函数
func (a *AliPayService) GenerateOutTradeNo() string {
length := 16
// 获取当前时间戳
timestamp := time.Now().UnixNano()
// 转换为字符串
timeStr := strconv.FormatInt(timestamp, 10)
// 生成随机数
mathrand.Seed(time.Now().UnixNano())
randomPart := strconv.Itoa(mathrand.Intn(1000000))
// 组合时间戳和随机数
combined := timeStr + randomPart
// 如果长度超出指定值,则截断;如果不够,则填充随机字符
if len(combined) >= length {
return combined[:length]
}
// 如果长度不够填充0
for len(combined) < length {
combined += strconv.Itoa(mathrand.Intn(10)) // 填充随机数
}
return combined
}

View File

@ -0,0 +1,59 @@
// asynq_service.go
package service
import (
"encoding/json"
"github.com/hibiken/asynq"
"github.com/zeromicro/go-zero/core/logx"
"qnc-server/app/user/cmd/api/internal/config"
"qnc-server/app/user/cmd/api/internal/types"
)
type AsynqService struct {
client *asynq.Client
config config.Config
}
// NewAsynqService 创建并初始化 Asynq 客户端
func NewAsynqService(c config.Config) *AsynqService {
client := asynq.NewClient(asynq.RedisClientOpt{
Addr: c.CacheRedis[0].Host,
Password: c.CacheRedis[0].Pass,
})
return &AsynqService{client: client, config: c}
}
// Close 关闭 Asynq 客户端
func (s *AsynqService) Close() error {
return s.client.Close()
}
func (s *AsynqService) SendQueryTask(orderID int64) error {
// 准备任务的 payload
payload := types.MsgPaySuccessQueryPayload{
OrderID: orderID,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
logx.Errorf("发送异步任务失败 (无法编码 payload): %v, 订单号: %d", err, orderID)
return err // 直接返回错误,避免继续执行
}
options := []asynq.Option{
asynq.MaxRetry(5), // 设置最大重试次数
}
// 创建任务
task := asynq.NewTask(types.MsgPaySuccessQuery, payloadBytes, options...)
// 将任务加入队列并获取任务信息
info, err := s.client.Enqueue(task)
if err != nil {
logx.Errorf("发送异步任务失败 (加入队列失败): %+v, 订单号: %d", err, orderID)
return err
}
// 记录成功日志,带上任务 ID 和队列信息
logx.Infof("发送异步任务成功任务ID: %s, 队列: %s, 订单号: %d", info.ID, info.Queue, orderID)
return nil
}

View File

@ -0,0 +1,116 @@
package service
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"qnc-server/app/user/cmd/api/internal/config"
"strings"
)
type VerificationService struct {
c config.Config
}
func NewVerificationService(c config.Config) *VerificationService {
return &VerificationService{
c: c,
}
}
type TwoFactorVerificationRequest struct {
Name string
IDCard string
}
type TwoFactorVerificationResp struct {
Msg string `json:"msg"`
Success bool `json:"success"`
Code int `json:"code"`
Data *TwoFactorVerificationData `json:"data"` //
}
type TwoFactorVerificationData struct {
Birthday string `json:"birthday"`
Result int `json:"result"`
Address string `json:"address"`
OrderNo string `json:"orderNo"`
Sex string `json:"sex"`
Desc string `json:"desc"`
}
// VerificationResult 定义校验结果结构体
type VerificationResult struct {
Passed bool
Err error
}
// ValidationError 定义校验错误类型
type ValidationError struct {
Message string
}
func (e *ValidationError) Error() string {
return e.Message
}
func (r *VerificationService) TwoFactorVerification(request TwoFactorVerificationRequest) (*VerificationResult, error) {
appCode := r.c.Ali.Code
requestUrl := "https://kzidcardv1.market.alicloudapi.com/api-mall/api/id_card/check"
// 构造查询参数
data := url.Values{}
data.Add("name", request.Name)
data.Add("idcard", request.IDCard)
req, err := http.NewRequest(http.MethodPost, requestUrl, strings.NewReader(data.Encode()))
if err != nil {
return nil, fmt.Errorf("创建请求失败: %+v", err)
}
req.Header.Set("Authorization", "APPCODE "+appCode)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("请求失败: %+v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("请求失败, 状态码: %d", resp.StatusCode)
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("响应体读取失败:%v", err)
}
var twoFactorVerificationResp TwoFactorVerificationResp
err = json.Unmarshal(respBody, &twoFactorVerificationResp)
if err != nil {
return nil, fmt.Errorf("二要素解析错误: %v", err)
}
if !twoFactorVerificationResp.Success {
return &VerificationResult{
Passed: false,
Err: &ValidationError{Message: "请输入有效的身份证号码"},
}, nil
}
if twoFactorVerificationResp.Code != 200 {
return &VerificationResult{
Passed: false,
Err: &ValidationError{Message: twoFactorVerificationResp.Msg},
}, nil
}
if twoFactorVerificationResp.Data.Result == 1 {
return &VerificationResult{
Passed: false,
Err: &ValidationError{Message: "姓名与身份证不一致"},
}, nil
}
return &VerificationResult{Passed: true, Err: nil}, nil
}

View File

@ -0,0 +1,188 @@
package service
import (
"context"
"fmt"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/app"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
"github.com/zeromicro/go-zero/core/logx"
"net/http"
"qnc-server/app/user/cmd/api/internal/config"
"qnc-server/pkg/lzkit/lzUtils"
"strconv"
"time"
)
const (
TradeStateSuccess = "SUCCESS" // 支付成功
TradeStateRefund = "REFUND" // 转入退款
TradeStateNotPay = "NOTPAY" // 未支付
TradeStateClosed = "CLOSED" // 已关闭
TradeStateRevoked = "REVOKED" // 已撤销(付款码支付)
TradeStateUserPaying = "USERPAYING" // 用户支付中(付款码支付)
TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败)
)
type WechatPayService struct {
config config.WxpayConfig
wechatClient *core.Client
notifyHandler *notify.Handler
}
// NewWechatPayService 初始化微信支付服务
func NewWechatPayService(c config.Config) *WechatPayService {
// 从配置中加载商户信息
mchID := c.Wxpay.MchID
mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber
mchAPIv3Key := c.Wxpay.MchApiv3Key
// 从文件中加载商户私钥
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(c.Wxpay.MchPrivateKeyPath)
if err != nil {
logx.Errorf("加载商户私钥失败: %v", err)
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
}
// 使用商户私钥和其他参数初始化微信支付客户端
opts := []core.ClientOption{
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
}
client, err := core.NewClient(context.Background(), opts...)
if err != nil {
logx.Errorf("创建微信支付客户端失败: %v", err)
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
}
// 在初始化时获取证书访问器并创建 notifyHandler
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
notifyHandler, err := notify.NewRSANotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
if err != nil {
logx.Errorf("获取证书访问器失败: %v", err)
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
}
return &WechatPayService{
config: c.Wxpay,
wechatClient: client,
notifyHandler: notifyHandler,
}
}
// CreateWechatAppOrder 创建微信APP支付订单
func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount float64, description string, outTradeNo string) (string, error) {
totalAmount := lzUtils.ToWechatAmount(amount)
// 构建支付请求参数
payRequest := app.PrepayRequest{
Appid: core.String(w.config.AppID),
Mchid: core.String(w.config.MchID),
Description: core.String(description),
OutTradeNo: core.String(outTradeNo),
NotifyUrl: core.String(w.config.NotifyUrl),
Amount: &app.Amount{
Total: core.Int64(totalAmount),
},
}
// 初始化 AppApiService
svc := app.AppApiService{Client: w.wechatClient}
// 发起预支付请求
resp, result, err := svc.Prepay(ctx, payRequest)
if err != nil {
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
}
// 返回预支付交易会话标识
return *resp.PrepayId, nil
}
// HandleWechatPayNotification 处理微信支付回调
func (w *WechatPayService) HandleWechatPayNotification(ctx context.Context, req *http.Request) (*payments.Transaction, error) {
transaction := new(payments.Transaction)
_, err := w.notifyHandler.ParseNotifyRequest(ctx, req, transaction)
if err != nil {
return nil, fmt.Errorf("微信支付通知处理失败: %v", err)
}
// 返回交易信息
return transaction, nil
}
// HandleRefundNotification 处理微信退款回调
func (w *WechatPayService) HandleRefundNotification(ctx context.Context, req *http.Request) (*refunddomestic.Refund, error) {
refund := new(refunddomestic.Refund)
_, err := w.notifyHandler.ParseNotifyRequest(ctx, req, refund)
if err != nil {
return nil, fmt.Errorf("微信退款回调通知处理失败: %v", err)
}
return refund, nil
}
// QueryOrderStatus 主动查询订单状态
func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID string) (*payments.Transaction, error) {
svc := jsapi.JsapiApiService{Client: w.wechatClient}
// 调用 QueryOrderById 方法查询订单状态
resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{
TransactionId: core.String(transactionID),
Mchid: core.String(w.config.MchID),
})
if err != nil {
return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode)
}
return resp, nil
}
// WeChatRefund 申请微信退款
func (w *WechatPayService) WeChatRefund(ctx context.Context, outTradeNo string, refundAmount float64, totalAmount float64) error {
// 生成唯一的退款单号
outRefundNo := fmt.Sprintf("%s-refund", outTradeNo)
// 初始化退款服务
svc := refunddomestic.RefundsApiService{Client: w.wechatClient}
// 创建退款请求
resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{
OutTradeNo: core.String(outTradeNo),
OutRefundNo: core.String(outRefundNo),
NotifyUrl: core.String(w.config.RefundNotifyUrl),
Amount: &refunddomestic.AmountReq{
Currency: core.String("CNY"),
Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)),
Total: core.Int64(lzUtils.ToWechatAmount(totalAmount)),
},
})
if err != nil {
return fmt.Errorf("微信订单申请退款错误: %v", err)
}
// 打印退款结果
logx.Infof("退款申请成功,状态码=%d退款单号=%s微信退款单号=%s", result.Response.StatusCode, *resp.OutRefundNo, *resp.RefundId)
return nil
}
// GenerateOutTradeNo 生成唯一订单号
func (w *WechatPayService) GenerateOutTradeNo() string {
length := 16
timestamp := time.Now().UnixNano()
timeStr := strconv.FormatInt(timestamp, 10)
randomPart := strconv.Itoa(int(timestamp % 1e6))
combined := timeStr + randomPart
if len(combined) >= length {
return combined[:length]
}
for len(combined) < length {
combined += strconv.Itoa(int(timestamp % 10))
}
return combined
}

View File

@ -0,0 +1,707 @@
package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/tidwall/gjson"
"github.com/zeromicro/go-zero/core/logx"
"io"
"net/http"
"qnc-server/app/user/cmd/api/internal/config"
"qnc-server/app/user/cmd/api/internal/types"
"qnc-server/pkg/lzkit/crypto"
"reflect"
"strconv"
"sync"
"sync/atomic"
"time"
)
type WestResp struct {
Message string `json:"message"`
Code string `json:"code"`
Data string `json:"data"`
ID string `json:"id"`
ErrorCode *int `json:"error_code"`
Reason string `json:"reason"`
}
type G05HZ01WestResp struct {
Message string `json:"message"`
Code string `json:"code"`
Data json.RawMessage `json:"data"`
ID string `json:"id"`
ErrorCode *int `json:"error_code"`
Reason string `json:"reason"`
}
type WestDexService struct {
config config.WestConfig
}
type APIResponseData struct {
ApiID string `json:"apiID"`
Data json.RawMessage `json:"data"` // 这里用 RawMessage 来存储原始的 data
Success bool `json:"success"`
Timestamp string `json:"timestamp"`
Error string `json:"error,omitempty"`
}
// NewWestDexService 是一个构造函数,用于初始化 WestDexService
func NewWestDexService(c config.Config) *WestDexService {
return &WestDexService{
config: c.WestConfig,
}
}
// CallAPI 调用西部数据的 API
func (w *WestDexService) CallAPI(code string, reqData map[string]interface{}) (resp []byte, err error) {
// 生成当前的13位时间戳
timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
// 构造请求URL
reqUrl := fmt.Sprintf("%s/%s/%s?timestamp=%s", w.config.Url, w.config.SecretId, code, timestamp)
jsonData, marshalErr := json.Marshal(reqData)
if marshalErr != nil {
return nil, marshalErr
}
// 创建HTTP POST请求
req, newRequestErr := http.NewRequest("POST", reqUrl, bytes.NewBuffer(jsonData))
if newRequestErr != nil {
return nil, newRequestErr
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
// 发送请求
client := &http.Client{}
httpResp, clientDoErr := client.Do(req)
if clientDoErr != nil {
return nil, clientDoErr
}
defer func(Body io.ReadCloser) {
closeErr := Body.Close()
if closeErr != nil {
}
}(httpResp.Body)
// 检查请求是否成功
if httpResp.StatusCode == 200 {
// 读取响应体
bodyBytes, ReadErr := io.ReadAll(httpResp.Body)
if ReadErr != nil {
return nil, ReadErr
}
// 手动调用 json.Unmarshal 触发自定义的 UnmarshalJSON 方法
var westDexResp WestResp
UnmarshalErr := json.Unmarshal(bodyBytes, &westDexResp)
if UnmarshalErr != nil {
return nil, UnmarshalErr
}
logx.Infof("西部数据请求响应, code: %s, response: %v", code, westDexResp)
if westDexResp.Code != "00000" {
if westDexResp.Data == "" {
return nil, errors.New(westDexResp.Message)
}
decryptedData, DecryptErr := crypto.WestDexDecrypt(westDexResp.Data, w.config.Key)
if DecryptErr != nil {
return nil, DecryptErr
}
return decryptedData, errors.New(westDexResp.Message)
}
if westDexResp.Data == "" {
return nil, errors.New(westDexResp.Message)
}
// 解密响应数据
decryptedData, DecryptErr := crypto.WestDexDecrypt(westDexResp.Data, w.config.Key)
if DecryptErr != nil {
return nil, DecryptErr
}
// 输出解密后的数据
return decryptedData, nil
}
return nil, fmt.Errorf("西部请求失败Code: %d", httpResp.StatusCode)
}
// CallAPI 调用西部数据的 API
func (w *WestDexService) G05HZ01CallAPI(code string, reqData map[string]interface{}) (resp []byte, err error) {
// 生成当前的13位时间戳
timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
// 构造请求URL
reqUrl := fmt.Sprintf("%s/%s/%s?timestamp=%s", w.config.Url, w.config.SecretSecondId, code, timestamp)
jsonData, marshalErr := json.Marshal(reqData)
if marshalErr != nil {
return nil, marshalErr
}
// 创建HTTP POST请求
req, newRequestErr := http.NewRequest("POST", reqUrl, bytes.NewBuffer(jsonData))
if newRequestErr != nil {
return nil, newRequestErr
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
// 发送请求
client := &http.Client{}
httpResp, clientDoErr := client.Do(req)
if clientDoErr != nil {
return nil, clientDoErr
}
defer func(Body io.ReadCloser) {
closeErr := Body.Close()
if closeErr != nil {
}
}(httpResp.Body)
// 检查请求是否成功
if httpResp.StatusCode == 200 {
// 读取响应体
bodyBytes, ReadErr := io.ReadAll(httpResp.Body)
if ReadErr != nil {
return nil, ReadErr
}
// 手动调用 json.Unmarshal 触发自定义的 UnmarshalJSON 方法
var westDexResp G05HZ01WestResp
UnmarshalErr := json.Unmarshal(bodyBytes, &westDexResp)
if UnmarshalErr != nil {
return nil, UnmarshalErr
}
logx.Infof("西部数据请求响应, code: %s, response: %v", code, westDexResp)
if westDexResp.Code != "0000" {
if westDexResp.Data == nil {
return nil, errors.New(westDexResp.Message)
}
return westDexResp.Data, errors.New(westDexResp.Message)
}
if westDexResp.Data == nil {
return nil, errors.New(westDexResp.Message)
}
return westDexResp.Data, nil
}
return nil, fmt.Errorf("西部请求失败Code: %d", httpResp.StatusCode)
}
// EncryptStructFields 加密字段的函数,处理不同类型,并跳过空值字段
func (w *WestDexService) EncryptStructFields(inputStruct interface{}) (map[string]interface{}, error) {
encryptedFields := make(map[string]interface{})
// 使用反射获取结构体的类型和值
v := reflect.ValueOf(inputStruct)
// 检查并解引用指针类型
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, errors.New("传入的interfact不是struct")
}
// 遍历结构体字段
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
fieldValue := v.Field(i)
// 检查字段的 encrypt 标签是否为 "false"
encryptTag := field.Tag.Get("encrypt")
if encryptTag == "false" {
encryptedFields[field.Name] = fieldValue.Interface()
continue
}
// 如果字段为空值,跳过
if fieldValue.IsZero() {
continue
}
// 将字段的值转换为字符串进行加密
strValue := fmt.Sprintf("%v", fieldValue.Interface())
// 执行加密操作
encryptedValue, err := crypto.WestDexEncrypt(strValue, w.config.Key)
if err != nil {
return nil, err
}
// 将加密后的值存入结果映射
encryptedFields[field.Name] = encryptedValue
}
return encryptedFields, nil
}
// MapStructToAPIRequest 字段映射
func (w *WestDexService) MapStructToAPIRequest(encryptedFields map[string]interface{}, fieldMapping map[string]string, wrapField string) map[string]interface{} {
apiRequest := make(map[string]interface{})
// 遍历字段映射表
for structField, apiField := range fieldMapping {
// 如果加密后的字段存在,才添加到请求
if structField == "InquiredAuth" {
apiRequest[apiField] = GetDateRange()
} else if structField == "TimeRange" {
apiRequest[apiField] = "5"
} else if value, exists := encryptedFields[structField]; exists {
apiRequest[apiField] = value
}
}
// 如果 wrapField 不为空,将 apiRequest 包裹到该字段下
if wrapField != "" {
return map[string]interface{}{
wrapField: apiRequest,
}
}
return apiRequest
}
// ProcessRequests 批量处理
func (w *WestDexService) ProcessRequests(data interface{}, requests []types.WestDexServiceRequestParams) ([]byte, error) {
var (
wg sync.WaitGroup
resultsCh = make(chan APIResponseData, len(requests))
errorsCh = make(chan error, len(requests))
ctx, cancel = context.WithCancel(context.Background())
errorCount int32
errorLimit = 4
)
defer cancel()
for i, req := range requests {
wg.Add(1)
go func(i int, req types.WestDexServiceRequestParams) {
defer wg.Done()
select {
case <-ctx.Done():
return
default:
}
// 请求参数预处理
apiRequest, preprocessErr := w.PreprocessRequestParams(req.ApiID, data)
if preprocessErr != nil {
errorsCh <- fmt.Errorf("请求预处理失败: %v", preprocessErr)
atomic.AddInt32(&errorCount, 1)
if atomic.LoadInt32(&errorCount) >= int32(errorLimit) {
cancel()
}
return
}
var resp []byte
var callApiErr error
if req.ApiID == "G05HZ01" {
resp, callApiErr = w.G05HZ01CallAPI(req.ApiID, apiRequest)
} else {
resp, callApiErr = w.CallAPI(req.ApiID, apiRequest)
}
timestamp := time.Now().Format("2006-01-02 15:04:05")
result := APIResponseData{
ApiID: req.ApiID,
Success: false,
Timestamp: timestamp,
}
if callApiErr != nil {
errorsCh <- fmt.Errorf("西部请求, 请求失败: %+v", callApiErr)
atomic.AddInt32(&errorCount, 1)
if atomic.LoadInt32(&errorCount) >= int32(errorLimit) {
cancel()
}
result.Error = callApiErr.Error()
result.Data = resp
resultsCh <- result
return
}
processedResp, processErr := processResponse(resp, req.ApiID)
if processErr != nil {
errorsCh <- fmt.Errorf("处理响应失败: %v", processErr)
atomic.AddInt32(&errorCount, 1)
if atomic.LoadInt32(&errorCount) >= int32(errorLimit) {
cancel()
}
result.Error = processErr.Error()
} else {
result.Data = processedResp
result.Success = true
}
resultsCh <- result
}(i, req)
}
go func() {
wg.Wait()
close(resultsCh)
close(errorsCh)
}()
// 收集所有结果并合并
var responseData []APIResponseData
for result := range resultsCh {
responseData = append(responseData, result)
}
if atomic.LoadInt32(&errorCount) >= int32(errorLimit) {
var allErrors []error
for err := range errorsCh {
allErrors = append(allErrors, err)
}
return nil, fmt.Errorf("请求失败次数超过 %d 次: %v", errorLimit, allErrors)
}
combinedResponse, err := json.Marshal(responseData)
if err != nil {
return nil, fmt.Errorf("响应数据转 JSON 失败: %+v", err)
}
return combinedResponse, nil
}
// ------------------------------------请求处理器--------------------------
var requestProcessors = map[string]func(*WestDexService, interface{}) (map[string]interface{}, error){
"G09SC02": (*WestDexService).ProcessG09SC02Request,
"G27BJ05": (*WestDexService).ProcessG27BJ05Request,
"G26BJ05": (*WestDexService).ProcessG26BJ05Request,
"G34BJ03": (*WestDexService).ProcessG34BJ03Request,
"G35SC01": (*WestDexService).ProcessG35SC01Request,
"G28BJ05": (*WestDexService).ProcessG28BJ05Request,
"G05HZ01": (*WestDexService).ProcessG05HZ01Request,
}
// PreprocessRequestParams 调用指定的请求处理函数
func (w *WestDexService) PreprocessRequestParams(apiID string, params interface{}) (map[string]interface{}, error) {
if processor, exists := requestProcessors[apiID]; exists {
return processor(w, params) // 调用 WestDexService 方法
}
var request map[string]interface{}
return request, nil
}
// / 将处理函数作为 WestDexService 的方法
func (w *WestDexService) ProcessG09SC02Request(params interface{}) (map[string]interface{}, error) {
encryptedFields, err := w.EncryptStructFields(params)
if err != nil {
return nil, fmt.Errorf("西部请求, 生成请求数据失败: %+v", err)
}
apiRequest := w.MapStructToAPIRequest(encryptedFields, types.G09SC02FieldMapping, "data")
return apiRequest, nil
}
func (w *WestDexService) ProcessG27BJ05Request(params interface{}) (map[string]interface{}, error) {
encryptedFields, err := w.EncryptStructFields(params)
if err != nil {
return nil, fmt.Errorf("西部请求, 生成请求数据失败: %+v", err)
}
apiRequest := w.MapStructToAPIRequest(encryptedFields, types.G27BJ05FieldMapping, "data")
return apiRequest, nil
}
func (w *WestDexService) ProcessG26BJ05Request(params interface{}) (map[string]interface{}, error) {
// 特殊名单 G26BJ05
encryptedFields, err := w.EncryptStructFields(params)
if err != nil {
return nil, fmt.Errorf("西部请求, 生成请求数据失败: %+v", err)
}
apiRequest := w.MapStructToAPIRequest(encryptedFields, types.G26BJ05FieldMapping, "data")
return apiRequest, nil
}
func (w *WestDexService) ProcessG34BJ03Request(params interface{}) (map[string]interface{}, error) {
// 个人不良 G34BJ03
encryptedFields, err := w.EncryptStructFields(params)
if err != nil {
return nil, fmt.Errorf("西部请求, 生成请求数据失败: %+v", err)
}
apiRequest := w.MapStructToAPIRequest(encryptedFields, types.G34BJ03FieldMapping, "data")
return apiRequest, nil
}
func (w *WestDexService) ProcessG35SC01Request(params interface{}) (map[string]interface{}, error) {
// 个人涉诉 G35SC01
encryptedFields, err := w.EncryptStructFields(params)
if err != nil {
return nil, fmt.Errorf("西部请求, 生成请求数据失败: %+v", err)
}
apiRequest := w.MapStructToAPIRequest(encryptedFields, types.G35SC01FieldMapping, "data")
return apiRequest, nil
}
func (w *WestDexService) ProcessG28BJ05Request(params interface{}) (map[string]interface{}, error) {
// 借贷行为 G28BJ05
encryptedFields, err := w.EncryptStructFields(params)
if err != nil {
return nil, fmt.Errorf("西部请求, 生成请求数据失败: %+v", err)
}
apiRequest := w.MapStructToAPIRequest(encryptedFields, types.G28BJ05FieldMapping, "data")
return apiRequest, nil
}
func (w *WestDexService) ProcessG05HZ01Request(params interface{}) (map[string]interface{}, error) {
// 使用 reflect 获取 params 的值和类型
val := reflect.ValueOf(params)
if val.Kind() == reflect.Ptr {
val = val.Elem() // 如果是指针,获取指向的实际值
}
if val.Kind() != reflect.Struct {
return nil, fmt.Errorf("请求参数必须是结构体类型")
}
// 初始化一个 map 来存储加密后的字段
encryptedFields := make(map[string]interface{})
// 遍历结构体字段,将其转换为 map[string]interface{}
valType := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldName := valType.Field(i).Name
// 如果字段名为 "IDCard",对其值进行加密
if fieldName == "IDCard" {
if field.Kind() != reflect.String {
return nil, fmt.Errorf("IDCard 字段不是字符串类型")
}
idCard := field.String()
encryptedIDCard := crypto.Md5Encrypt(idCard)
encryptedFields[fieldName] = encryptedIDCard
} else {
// 否则直接将字段值添加到 map 中
encryptedFields[fieldName] = field.Interface()
}
}
// 使用字段映射表生成最终的 API 请求
apiRequest := w.MapStructToAPIRequest(encryptedFields, types.G05HZ01FieldMapping, "")
return apiRequest, nil
}
// -----------------------------------------------------------------------------
// 响应处理器
var responseProcessors = map[string]func([]byte) ([]byte, error){
"G09SC02": processG09SC02Response, // 单人婚姻
"G27BJ05": processG27BJ05Response, // 借贷意向
"G28BJ05": processG28BJ05Response, // 借贷行为
"G26BJ05": processG26BJ05Response, // 特殊名单
"G05HZ01": processG05HZ01Response, // 股东人企关系
"G34BJ03": processG34BJ03Response, // 个人不良
"G35SC01": processG35SC01Response, // 个人涉诉
}
// processResponse 处理响应数据
func processResponse(resp []byte, apiID string) ([]byte, error) {
if processor, exists := responseProcessors[apiID]; exists {
return processor(resp)
}
return resp, nil
}
func processG09SC02Response(resp []byte) ([]byte, error) {
// 使用 GJSON 递归搜索 "maritalStatus" 字段
result := gjson.GetBytes(resp, "data.0.maritalStatus")
if result.Exists() {
// 如果字段存在,构造包含 "status" 的 JSON 响应
responseMap := map[string]string{"status": result.String()}
jsonResponse, err := json.Marshal(responseMap)
if err != nil {
return nil, err
}
return jsonResponse, nil
} else {
return nil, errors.New("查询为空")
}
}
func processG27BJ05Response(resp []byte) ([]byte, error) {
// 获取 code 字段
codeResult := gjson.GetBytes(resp, "code")
if !codeResult.Exists() {
return nil, fmt.Errorf("code 字段不存在")
}
if codeResult.String() != "00" {
return nil, fmt.Errorf("未匹配到相关结果")
}
// 获取 data 字段
dataResult := gjson.GetBytes(resp, "data")
if !dataResult.Exists() {
return nil, fmt.Errorf("data 字段不存在")
}
// 将 data 字段解析为 map
var dataMap map[string]interface{}
if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil {
return nil, fmt.Errorf("解析 data 字段失败: %v", err)
}
// 删除指定字段
delete(dataMap, "swift_number")
delete(dataMap, "DataStrategy")
// 重新编码为 JSON
modifiedData, err := json.Marshal(dataMap)
if err != nil {
return nil, fmt.Errorf("编码修改后的 data 失败: %v", err)
}
return modifiedData, nil
}
func processG28BJ05Response(resp []byte) ([]byte, error) {
// 处理借贷行为的响应数据
// 获取 code 字段
codeResult := gjson.GetBytes(resp, "code")
if !codeResult.Exists() {
return nil, fmt.Errorf("code 字段不存在")
}
if codeResult.String() != "00" {
return nil, fmt.Errorf("未匹配到相关结果")
}
// 获取 data 字段
dataResult := gjson.GetBytes(resp, "data")
if !dataResult.Exists() {
return nil, fmt.Errorf("data 字段不存在")
}
// 将 data 字段解析为 map
var dataMap map[string]interface{}
if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil {
return nil, fmt.Errorf("解析 data 字段失败: %v", err)
}
// 删除指定字段
delete(dataMap, "swift_number")
delete(dataMap, "DataStrategy")
// 重新编码为 JSON
modifiedData, err := json.Marshal(dataMap)
if err != nil {
return nil, fmt.Errorf("编码修改后的 data 失败: %v", err)
}
return modifiedData, nil
}
func processG26BJ05Response(resp []byte) ([]byte, error) {
// 处理特殊名单的响应数据
// 获取 code 字段
codeResult := gjson.GetBytes(resp, "code")
if !codeResult.Exists() {
return nil, fmt.Errorf("code 字段不存在")
}
if codeResult.String() != "00" {
return nil, fmt.Errorf("未匹配到相关结果")
}
// 获取 data 字段
dataResult := gjson.GetBytes(resp, "data")
if !dataResult.Exists() {
return nil, fmt.Errorf("data 字段不存在")
}
// 将 data 字段解析为 map
var dataMap map[string]interface{}
if err := json.Unmarshal([]byte(dataResult.Raw), &dataMap); err != nil {
return nil, fmt.Errorf("解析 data 字段失败: %v", err)
}
// 删除指定字段
delete(dataMap, "swift_number")
delete(dataMap, "DataStrategy")
// 重新编码为 JSON
modifiedData, err := json.Marshal(dataMap)
if err != nil {
return nil, fmt.Errorf("编码修改后的 data 失败: %v", err)
}
return modifiedData, nil
}
func processG05HZ01Response(resp []byte) ([]byte, error) {
// 处理股东人企关系的响应数据
code := gjson.GetBytes(resp, "code")
if !code.Exists() {
return nil, fmt.Errorf("响应中缺少 code 字段")
}
// 判断 code 是否等于 "0000"
if code.String() == "0000" {
// 获取 data 字段的值
data := gjson.GetBytes(resp, "data")
if !data.Exists() {
return nil, fmt.Errorf("响应中缺少 data 字段")
}
// 返回 data 字段的内容
return []byte(data.Raw), nil
}
// code 不等于 "0000",返回错误
return nil, fmt.Errorf("响应code错误%s", code.String())
}
func processG34BJ03Response(resp []byte) ([]byte, error) {
// 处理个人不良的响应数据
dataResult := gjson.GetBytes(resp, "negative_info.data.risk_level")
if dataResult.Exists() {
// 如果字段存在,构造包含 "status" 的 JSON 响应
responseMap := map[string]string{"risk_level": dataResult.String()}
jsonResponse, err := json.Marshal(responseMap)
if err != nil {
return nil, err
}
return jsonResponse, nil
} else {
return nil, errors.New("查询为空")
}
}
func processG35SC01Response(resp []byte) ([]byte, error) {
// 第一步:提取外层的 data 字段
dataResult := gjson.GetBytes(resp, "data")
if !dataResult.Exists() {
return nil, fmt.Errorf("外层 data 字段不存在")
}
// 第二步:解析外层 data 的 JSON 字符串
var outerDataMap map[string]interface{}
if err := json.Unmarshal([]byte(dataResult.String()), &outerDataMap); err != nil {
return nil, fmt.Errorf("解析外层 data 字段失败: %v", err)
}
// 第三步:提取内层的 data 字段
innerData, ok := outerDataMap["data"].(string)
if !ok {
return nil, fmt.Errorf("内层 data 字段不存在或类型错误")
}
// 第四步:解析内层 data 的 JSON 字符串
var finalDataMap map[string]interface{}
if err := json.Unmarshal([]byte(innerData), &finalDataMap); err != nil {
return nil, fmt.Errorf("解析内层 data 字段失败: %v", err)
}
// 将最终的 JSON 对象编码为字节数组返回
finalDataBytes, err := json.Marshal(finalDataMap)
if err != nil {
return nil, fmt.Errorf("编码最终的 JSON 对象失败: %v", err)
}
return finalDataBytes, nil
}
// GetDateRange 返回今天到明天的日期范围,格式为 "yyyyMMdd-yyyyMMdd"
func GetDateRange() string {
today := time.Now().Format("20060102") // 获取今天的日期
tomorrow := time.Now().Add(24 * time.Hour).Format("20060102") // 获取明天的日期
return fmt.Sprintf("%s-%s", today, tomorrow) // 拼接日期范围并返回
}

View File

@ -1,9 +1,12 @@
package svc package svc
import ( import (
"github.com/hibiken/asynq"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
"qnc-server/app/user/cmd/api/internal/config" "qnc-server/app/user/cmd/api/internal/config"
"qnc-server/app/user/cmd/api/internal/service"
"qnc-server/app/user/model" "qnc-server/app/user/model"
) )
@ -12,19 +15,56 @@ type ServiceContext struct {
Redis *redis.Redis Redis *redis.Redis
UserModel model.UserModel UserModel model.UserModel
UserAuthModel model.UserAuthModel UserAuthModel model.UserAuthModel
ProductModel model.ProductModel
FeatureModel model.FeatureModel
ProductFeatureModel model.ProductFeatureModel
OrderModel model.OrderModel
QueryModel model.QueryModel
AlipayService *service.AliPayService
WechatPayService *service.WechatPayService
WestDexService *service.WestDexService
AsynqServer *asynq.Server // 服务端
AsynqService *service.AsynqService // 客户端
VerificationService *service.VerificationService
} }
func NewServiceContext(c config.Config) *ServiceContext { func NewServiceContext(c config.Config) *ServiceContext {
db := sqlx.NewMysql(c.DataSource) // 创建数据库连接 db := sqlx.NewMysql(c.DataSource)
redisConf := redis.RedisConf{ redisConf := redis.RedisConf{
Host: c.CacheRedis[0].Host, Host: c.CacheRedis[0].Host,
Pass: c.CacheRedis[0].Pass, Pass: c.CacheRedis[0].Pass,
Type: c.CacheRedis[0].Type, // Redis 节点类型,如 "node" Type: c.CacheRedis[0].Type,
} }
asynqServer := asynq.NewServer(
asynq.RedisClientOpt{Addr: c.CacheRedis[0].Host, Password: c.CacheRedis[0].Pass},
asynq.Config{
IsFailure: func(err error) bool {
logx.Errorf("异步任务失败: %+v \n", err)
return true
},
Concurrency: 10,
},
)
return &ServiceContext{ return &ServiceContext{
Config: c, Config: c,
Redis: redis.MustNewRedis(redisConf), Redis: redis.MustNewRedis(redisConf),
AlipayService: service.NewAliPayService(c),
WechatPayService: service.NewWechatPayService(c),
WestDexService: service.NewWestDexService(c),
VerificationService: service.NewVerificationService(c),
AsynqServer: asynqServer,
AsynqService: service.NewAsynqService(c),
UserModel: model.NewUserModel(db, c.CacheRedis), UserModel: model.NewUserModel(db, c.CacheRedis),
UserAuthModel: model.NewUserAuthModel(db, c.CacheRedis), UserAuthModel: model.NewUserAuthModel(db, c.CacheRedis),
ProductModel: model.NewProductModel(db, c.CacheRedis),
OrderModel: model.NewOrderModel(db, c.CacheRedis),
QueryModel: model.NewQueryModel(db, c.CacheRedis),
FeatureModel: model.NewFeatureModel(db, c.CacheRedis),
ProductFeatureModel: model.NewProductFeatureModel(db, c.CacheRedis),
}
}
func (s *ServiceContext) Close() {
if s.AsynqService != nil {
s.AsynqService.Close()
} }
} }

View File

@ -0,0 +1,5 @@
package types
type MsgPaySuccessQueryPayload struct {
OrderID int64 `json:"order_id"`
}

View File

@ -0,0 +1,61 @@
package types
type MarriageReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
PayMethod string `json:"pay_method" validate:"required,payMethod"`
}
type HomeServiceReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
PayMethod string `json:"pay_method" validate:"required,payMethod"`
}
// RiskAssessment 查询请求结构
type RiskAssessmentReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
PayMethod string `json:"pay_method" validate:"required,payMethod"`
}
// CompanyInfo 查询请求结构
type CompanyInfoReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
PayMethod string `json:"pay_method" validate:"required,payMethod"`
}
// RentalInfo 查询请求结构
type RentalInfoReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
PayMethod string `json:"pay_method" validate:"required,payMethod"`
}
// PreLoanBackgroundCheck 查询请求结构
type PreLoanBackgroundCheckReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
PayMethod string `json:"pay_method" validate:"required,payMethod"`
}
// BackgroundCheck 查询请求结构
type BackgroundCheckReq struct {
Name string `json:"name" validate:"required,name"`
IDCard string `json:"id_card" validate:"required,idCard"`
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
PayMethod string `json:"pay_method" validate:"required,payMethod"`
}

View File

@ -0,0 +1,47 @@
package types
// 特殊名单 G26BJ05
var G26BJ05FieldMapping = map[string]string{
"IDCard": "id",
"Name": "name",
"Mobile": "cell",
"TimeRange": "time_range",
}
// 个人不良
var G34BJ03FieldMapping = map[string]string{
"IDCard": "id_card",
"Name": "name",
}
// 个人涉诉 G35SC01
var G35SC01FieldMapping = map[string]string{
"Name": "name",
"IDCard": "idcard",
"InquiredAuth": "inquired_auth",
}
// 单人婚姻 G09SC02
var G09SC02FieldMapping = map[string]string{
"IDCard": "certNumMan",
"Name": "nameMan",
}
// 借贷意向 G27BJ05
var G27BJ05FieldMapping = map[string]string{
"IDCard": "id",
"Name": "name",
"Mobile": "cell",
}
// 借贷行为 G28BJ05
var G28BJ05FieldMapping = map[string]string{
"IDCard": "id",
"Name": "name",
"Mobile": "cell",
}
// 股东人企关系精准版 G05HZ01
var G05HZ01FieldMapping = map[string]string{
"IDCard": "pid",
}

View File

@ -0,0 +1,73 @@
package types
type WestDexServiceRequestParams struct {
FieldMapping map[string]string
ApiID string
}
var WestDexParams = map[string][]WestDexServiceRequestParams{
"marriage": {
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
},
"backgroundcheck": {
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
},
"companyinfo": {
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
},
"homeservice": {
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
},
"preloanbackgroundcheck": {
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
},
"rentalinfo": {
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
},
"riskassessment": {
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
},
}

View File

@ -0,0 +1,3 @@
package types
const MsgPaySuccessQuery = "msg:pay_success:query"

View File

@ -1,6 +1,20 @@
// Code generated by goctl. DO NOT EDIT. // Code generated by goctl. DO NOT EDIT.
package types package types
type Feature struct {
ID int64 `json:"id"` // 功能ID
ApiID string `json:"api_id"` // API标识
Name string `json:"name"` // 功能描述
}
type GetProductByEnRequest struct {
ProductEn string `path:"product_en"`
}
type GetProductByIDRequest struct {
Id int64 `path:"id"`
}
type MobileCodeLoginReq struct { type MobileCodeLoginReq struct {
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
Code string `json:"code" validate:"required"` Code string `json:"code" validate:"required"`
@ -23,6 +37,80 @@ type MobileLoginResp struct {
RefreshAfter int64 `json:"refreshAfter"` RefreshAfter int64 `json:"refreshAfter"`
} }
type Product struct {
ProductName string `json:"product_name"`
ProductEn string `json:"product_en"`
Description string `json:"description"`
Notes string `json:"notes,optional"`
SellPrice float64 `json:"sell_price"`
Features []Feature `json:"features"` // 关联功能列表
}
type ProductResponse struct {
Product
}
type Query struct {
Id int64 `json:"id"` // 主键ID
OrderId int64 `json:"order_id"` // 订单ID
UserId int64 `json:"user_id"` // 用户ID
ProductId int64 `json:"product_id"` // 产品ID
QueryData []map[string]interface{} `json:"query_data"`
CreateTime string `json:"create_time"` // 创建时间
UpdateTime string `json:"update_time"` // 更新时间
QueryState string `json:"query_state"` // 查询状态
}
type QueryDetailByOrderIdReq struct {
OrderId int64 `path:"order_id"`
}
type QueryDetailByOrderIdResp struct {
Query
}
type QueryDetailReq struct {
Id int64 `path:"id"`
}
type QueryDetailResp struct {
Query
}
type QueryExampleReq struct {
Feature string `form:"feature"`
}
type QueryExampleResp struct {
Query
}
type QueryListReq struct {
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数据量
}
type QueryListResp struct {
Total int64 `json:"total"` // 总记录数
List []Query `json:"list"` // 查询列表
}
type QueryReq struct {
Data string `json:"data" validate:"required"`
}
type QueryResp struct {
PrepayID string `json:"prepay_id"`
OrderID int64 `json:"order_id"`
}
type QueryRetryReq struct {
Id int64 `path:"id"`
}
type QueryRetryResp struct {
}
type RegisterReq struct { type RegisterReq struct {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
Password string `json:"password" validate:"required,min=11,max=11,password"` Password string `json:"password" validate:"required,min=11,max=11,password"`
@ -41,9 +129,6 @@ type User struct {
NickName string `json:"nickName"` NickName string `json:"nickName"`
} }
type UserInfoReq struct {
}
type UserInfoResp struct { type UserInfoResp struct {
UserInfo User `json:"userInfo"` UserInfo User `json:"userInfo"`
} }
@ -62,5 +147,5 @@ type WXMiniAuthResp struct {
type SendSmsReq struct { type SendSmsReq struct {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
ActionType string `json:"actionType" validate:"required,oneof=loginCode registerCode QueryCode"` ActionType string `json:"actionType" validate:"required,oneof=login register query"`
} }

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCP6fWm1vXXybH
m3Ne6PjacGrN2+iMrzWZlzdHCZ31udDPqSUYaZ+78b441KZK/CJFQWeSJ/1h//A+
BGsQDKvE/fj2QzN1KkOuQ8WJXNGpixE5uu5bv/QTN/ukurGdA1aO2aFCANumlOmB
HkB/B2so57ii8iQQjwK2xM4r3oOU/IfcFGKL+9/QjLGFFp9PJXCDBCgrxxlZGaj1
3wowlfVOzlaX94gemQsCYVkuAFIYMAnFHs9cKNZQIU80somW/yy2Gy38N6n7NnbD
nvFSaq4GoDROqRgKbRZ5e706d/p7A3aS/2oRqq1jomUIugK8g++LmoHFTgfhfQkI
v1aG/nPzAgMBAAECggEAD2RN31J2J42xm/V0YdviBCUOQXugZK1peN8jkSxw6Myt
gBbuCo4sCw9vvD8VYjGyYXx6QXmLuV03YyKkfSQT5EsflBvlEu6jaEaUe3rwXhfX
6JQoWPrP00oHVZk5g7CFBlK2VW2N+hgonIOSJr6mvhoGZlr7gphiZasYjx9Vm9N3
Pbnfru5ttzplYNniwH3DF6ph8VmdbD1nnbWSKLXvHCsXQT2wBcnsIagIH3vyq6K1
pc5abWsQJrixOPebpI8jD5w0HxHAqVLx58H/OC2zW/roAw1WS2AkueJ1j7dQ7Z0C
mc9Xexz5gcAP0nMAQv+LP7iYqsa/niFhfcTFWfdxkQKBgQD5JkKNmInU2/IVYCwO
c483MCSv1+MnbRXlb7vut8T0IupHTU6hCge6C3q3HsjbKSBn8bRChtPUzvw9JFxK
QWKiQqQDPLDJ08AIKhfQD2JiLtoikkZN0bF6OTL+Soney1yGx51mlfHM194+PcCJ
jF7iWdMVbcBwHbgydNxxIS5cKQKBgQDHlvQ4lw6gvLILpGK494/vNYSJP/Jmd66V
3oSGYi84YRKTSwH4NlbBVVieb3Dv+pPugbsXEuFHBif7WsivbYgNTE9++8Yvt0gh
duB1G4yh7m/ylQeSuipgQU9tozrU/15cWwmcCRV50wWXBGoVEM0kf7mzEKSxmjYk
Qzko/zxSuwKBgQCY6Bc+SViFz3qSDdTcBaXma+CIHsmlH7ipd9px1kzEvEzl95cD
FGHLl1H34qfIgUQHJvrHPXHyEBoT+CW/2MMM7DM2XV/ubctT92ln4pkxwqlTQExv
Y/s1FLesAtj8Z/hgK0/5bprYab9WmZV5lTGCXzhB1XqeFE9AgCHuODv4iQKBgQC8
g2uwd5ytXQydymokYk9klJvWNrvw5GHV1BJAC0Smb6lnzZTSqCBRAxdsrb1yLK7E
u2vGY2K7/qiM1DZw23eBd+4t9gg+0VIjqXBfq+GsoNTDvtckUwnrWER5PY831ut9
N89fvYS3SAUjmlvIAdKBAtKWusWTqiAxJ/05J7oGOQKBgB5PSr5i0LlupIbKui9t
XtXnRqGPxxrZZUpTkyrGOAnlCz/zq2QiwFpBWo/NMHOp0KmxzJpQ8yEY2LWlRZ61
Oc9m0J/HtPw3Ohi1treBosEVG/0NOI9Tq1Obny23N51MVibdW6zEIyGUp/DbFS8h
5DljdOYX9IYIHHn3Ig4GeTGe
-----END PRIVATE KEY-----

View File

@ -1,31 +0,0 @@
package main
import (
"flag"
"fmt"
"qnc-server/app/user/cmd/api/internal/config"
"qnc-server/app/user/cmd/api/internal/handler"
"qnc-server/app/user/cmd/api/internal/svc"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/rest"
)
var configFile = flag.String("f", "etc/user.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

View File

@ -1,27 +0,0 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ UserAuthModel = (*customUserAuthModel)(nil)
type (
// UserAuthModel is an interface to be customized, add more methods here,
// and implement the added methods in customUserAuthModel.
UserAuthModel interface {
userAuthModel
}
customUserAuthModel struct {
*defaultUserAuthModel
}
)
// NewUserAuthModel returns a model for the database table.
func NewUserAuthModel(conn sqlx.SqlConn, c cache.CacheConf) UserAuthModel {
return &customUserAuthModel{
defaultUserAuthModel: newUserAuthModel(conn, c),
}
}

View File

@ -1,435 +0,0 @@
// 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"
"qnc-server/common/globalkey"
)
var (
userAuthFieldNames = builder.RawFieldNames(&UserAuth{})
userAuthRows = strings.Join(userAuthFieldNames, ",")
userAuthRowsExpectAutoSet = strings.Join(stringx.Remove(userAuthFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
userAuthRowsWithPlaceHolder = strings.Join(stringx.Remove(userAuthFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"
cacheUserAuthIdPrefix = "cache:userAuth:id:"
cacheUserAuthAuthTypeAuthKeyPrefix = "cache:userAuth:authType:authKey:"
cacheUserAuthUserIdAuthTypePrefix = "cache:userAuth:userId:authType:"
)
type (
userAuthModel interface {
Insert(ctx context.Context, session sqlx.Session, data *UserAuth) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*UserAuth, error)
FindOneByAuthTypeAuthKey(ctx context.Context, authType string, authKey string) (*UserAuth, error)
FindOneByUserIdAuthType(ctx context.Context, userId int64, authType string) (*UserAuth, error)
Update(ctx context.Context, session sqlx.Session, data *UserAuth) (sql.Result, error)
UpdateWithVersion(ctx context.Context, session sqlx.Session, data *UserAuth) 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 *UserAuth) 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) ([]*UserAuth, error)
FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserAuth, error)
FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserAuth, int64, error)
FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*UserAuth, error)
FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*UserAuth, error)
Delete(ctx context.Context, session sqlx.Session, id int64) error
}
defaultUserAuthModel struct {
sqlc.CachedConn
table string
}
UserAuth struct {
Id int64 `db:"id"`
CreateTime time.Time `db:"create_time"`
UpdateTime time.Time `db:"update_time"`
DeleteTime time.Time `db:"delete_time"`
DelState int64 `db:"del_state"`
Version int64 `db:"version"` // 版本号
UserId int64 `db:"user_id"`
AuthKey string `db:"auth_key"` // 平台唯一id
AuthType string `db:"auth_type"` // 平台类型
}
)
func newUserAuthModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserAuthModel {
return &defaultUserAuthModel{
CachedConn: sqlc.NewConn(conn, c),
table: "`user_auth`",
}
}
func (m *defaultUserAuthModel) Insert(ctx context.Context, session sqlx.Session, data *UserAuth) (sql.Result, error) {
data.DeleteTime = time.Unix(0, 0)
data.DelState = globalkey.DelStateNo
userAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheUserAuthAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey)
userAuthIdKey := fmt.Sprintf("%s%v", cacheUserAuthIdPrefix, data.Id)
userAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheUserAuthUserIdAuthTypePrefix, data.UserId, data.AuthType)
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, userAuthRowsExpectAutoSet)
if session != nil {
return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.AuthKey, data.AuthType)
}
return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.UserId, data.AuthKey, data.AuthType)
}, userAuthAuthTypeAuthKeyKey, userAuthIdKey, userAuthUserIdAuthTypeKey)
}
func (m *defaultUserAuthModel) FindOne(ctx context.Context, id int64) (*UserAuth, error) {
userAuthIdKey := fmt.Sprintf("%s%v", cacheUserAuthIdPrefix, id)
var resp UserAuth
err := m.QueryRowCtx(ctx, &resp, userAuthIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", userAuthRows, 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 *defaultUserAuthModel) FindOneByAuthTypeAuthKey(ctx context.Context, authType string, authKey string) (*UserAuth, error) {
userAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheUserAuthAuthTypeAuthKeyPrefix, authType, authKey)
var resp UserAuth
err := m.QueryRowIndexCtx(ctx, &resp, userAuthAuthTypeAuthKeyKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where `auth_type` = ? and `auth_key` = ? and del_state = ? limit 1", userAuthRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, authType, authKey, globalkey.DelStateNo); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserAuthModel) FindOneByUserIdAuthType(ctx context.Context, userId int64, authType string) (*UserAuth, error) {
userAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheUserAuthUserIdAuthTypePrefix, userId, authType)
var resp UserAuth
err := m.QueryRowIndexCtx(ctx, &resp, userAuthUserIdAuthTypeKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where `user_id` = ? and `auth_type` = ? and del_state = ? limit 1", userAuthRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, userId, authType, globalkey.DelStateNo); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserAuthModel) Update(ctx context.Context, session sqlx.Session, newData *UserAuth) (sql.Result, error) {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return nil, err
}
userAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheUserAuthAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey)
userAuthIdKey := fmt.Sprintf("%s%v", cacheUserAuthIdPrefix, data.Id)
userAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheUserAuthUserIdAuthTypePrefix, data.UserId, data.AuthType)
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, userAuthRowsWithPlaceHolder)
if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.AuthKey, newData.AuthType, newData.Id)
}
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.AuthKey, newData.AuthType, newData.Id)
}, userAuthAuthTypeAuthKeyKey, userAuthIdKey, userAuthUserIdAuthTypeKey)
}
func (m *defaultUserAuthModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *UserAuth) error {
oldVersion := newData.Version
newData.Version += 1
var sqlResult sql.Result
var err error
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
userAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheUserAuthAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey)
userAuthIdKey := fmt.Sprintf("%s%v", cacheUserAuthIdPrefix, data.Id)
userAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheUserAuthUserIdAuthTypePrefix, data.UserId, data.AuthType)
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, userAuthRowsWithPlaceHolder)
if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.AuthKey, newData.AuthType, newData.Id, oldVersion)
}
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.UserId, newData.AuthKey, newData.AuthType, newData.Id, oldVersion)
}, userAuthAuthTypeAuthKeyKey, userAuthIdKey, userAuthUserIdAuthTypeKey)
if err != nil {
return err
}
updateCount, err := sqlResult.RowsAffected()
if err != nil {
return err
}
if updateCount == 0 {
return ErrNoRowsUpdate
}
return nil
}
func (m *defaultUserAuthModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *UserAuth) error {
data.DelState = globalkey.DelStateYes
data.DeleteTime = time.Now()
if err := m.UpdateWithVersion(ctx, session, data); err != nil {
return errors.Wrapf(errors.New("delete soft failed "), "UserAuthModel delete err : %+v", err)
}
return nil
}
func (m *defaultUserAuthModel) 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 *defaultUserAuthModel) 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 *defaultUserAuthModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*UserAuth, error) {
builder = builder.Columns(userAuthRows)
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 []*UserAuth
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserAuthModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserAuth, error) {
builder = builder.Columns(userAuthRows)
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 []*UserAuth
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserAuthModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*UserAuth, int64, error) {
total, err := m.FindCount(ctx, builder, "id")
if err != nil {
return nil, 0, err
}
builder = builder.Columns(userAuthRows)
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 []*UserAuth
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, total, nil
default:
return nil, total, err
}
}
func (m *defaultUserAuthModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*UserAuth, error) {
builder = builder.Columns(userAuthRows)
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 []*UserAuth
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserAuthModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*UserAuth, error) {
builder = builder.Columns(userAuthRows)
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 []*UserAuth
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserAuthModel) 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 *defaultUserAuthModel) SelectBuilder() squirrel.SelectBuilder {
return squirrel.Select().From(m.table)
}
func (m *defaultUserAuthModel) Delete(ctx context.Context, session sqlx.Session, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
userAuthAuthTypeAuthKeyKey := fmt.Sprintf("%s%v:%v", cacheUserAuthAuthTypeAuthKeyPrefix, data.AuthType, data.AuthKey)
userAuthIdKey := fmt.Sprintf("%s%v", cacheUserAuthIdPrefix, id)
userAuthUserIdAuthTypeKey := fmt.Sprintf("%s%v:%v", cacheUserAuthUserIdAuthTypePrefix, data.UserId, data.AuthType)
_, 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)
}, userAuthAuthTypeAuthKeyKey, userAuthIdKey, userAuthUserIdAuthTypeKey)
return err
}
func (m *defaultUserAuthModel) formatPrimary(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheUserAuthIdPrefix, primary)
}
func (m *defaultUserAuthModel) 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", userAuthRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo)
}
func (m *defaultUserAuthModel) tableName() string {
return m.table
}

View File

@ -1,27 +0,0 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ UserModel = (*customUserModel)(nil)
type (
// UserModel is an interface to be customized, add more methods here,
// and implement the added methods in customUserModel.
UserModel interface {
userModel
}
customUserModel struct {
*defaultUserModel
}
)
// NewUserModel returns a model for the database table.
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel {
return &customUserModel{
defaultUserModel: newUserModel(conn, c),
}
}

View File

@ -1,410 +0,0 @@
// 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"
"qnc-server/common/globalkey"
)
var (
userFieldNames = builder.RawFieldNames(&User{})
userRows = strings.Join(userFieldNames, ",")
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"
cacheUserIdPrefix = "cache:user:id:"
cacheUserMobilePrefix = "cache:user:mobile:"
)
type (
userModel interface {
Insert(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*User, error)
FindOneByMobile(ctx context.Context, mobile string) (*User, error)
Update(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error)
UpdateWithVersion(ctx context.Context, session sqlx.Session, data *User) 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 *User) 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) ([]*User, error)
FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, error)
FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, int64, error)
FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*User, error)
FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*User, error)
Delete(ctx context.Context, session sqlx.Session, id int64) error
}
defaultUserModel struct {
sqlc.CachedConn
table string
}
User struct {
Id int64 `db:"id"`
CreateTime time.Time `db:"create_time"`
UpdateTime time.Time `db:"update_time"`
DeleteTime time.Time `db:"delete_time"`
DelState int64 `db:"del_state"`
Version int64 `db:"version"` // 版本号
Mobile string `db:"mobile"`
Password string `db:"password"`
Nickname string `db:"nickname"`
Info string `db:"info"`
}
)
func newUserModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserModel {
return &defaultUserModel{
CachedConn: sqlc.NewConn(conn, c),
table: "`user`",
}
}
func (m *defaultUserModel) Insert(ctx context.Context, session sqlx.Session, data *User) (sql.Result, error) {
data.DeleteTime = time.Unix(0, 0)
data.DelState = globalkey.DelStateNo
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
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, userRowsExpectAutoSet)
if session != nil {
return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info)
}
return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.Mobile, data.Password, data.Nickname, data.Info)
}, userIdKey, userMobileKey)
}
func (m *defaultUserModel) FindOne(ctx context.Context, id int64) (*User, error) {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
var resp User
err := m.QueryRowCtx(ctx, &resp, userIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", userRows, 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 *defaultUserModel) FindOneByMobile(ctx context.Context, mobile string) (*User, error) {
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
var resp User
err := m.QueryRowIndexCtx(ctx, &resp, userMobileKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where `mobile` = ? and del_state = ? limit 1", userRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, mobile, globalkey.DelStateNo); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserModel) Update(ctx context.Context, session sqlx.Session, newData *User) (sql.Result, error) {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return nil, err
}
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
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, userRowsWithPlaceHolder)
if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Id)
}
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Id)
}, userIdKey, userMobileKey)
}
func (m *defaultUserModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *User) error {
oldVersion := newData.Version
newData.Version += 1
var sqlResult sql.Result
var err error
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
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, userRowsWithPlaceHolder)
if session != nil {
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Id, oldVersion)
}
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.Mobile, newData.Password, newData.Nickname, newData.Info, newData.Id, oldVersion)
}, userIdKey, userMobileKey)
if err != nil {
return err
}
updateCount, err := sqlResult.RowsAffected()
if err != nil {
return err
}
if updateCount == 0 {
return ErrNoRowsUpdate
}
return nil
}
func (m *defaultUserModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *User) error {
data.DelState = globalkey.DelStateYes
data.DeleteTime = time.Now()
if err := m.UpdateWithVersion(ctx, session, data); err != nil {
return errors.Wrapf(errors.New("delete soft failed "), "UserModel delete err : %+v", err)
}
return nil
}
func (m *defaultUserModel) 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 *defaultUserModel) 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 *defaultUserModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*User, error) {
builder = builder.Columns(userRows)
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 []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, error) {
builder = builder.Columns(userRows)
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 []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*User, int64, error) {
total, err := m.FindCount(ctx, builder, "id")
if err != nil {
return nil, 0, err
}
builder = builder.Columns(userRows)
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 []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, total, nil
default:
return nil, total, err
}
}
func (m *defaultUserModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*User, error) {
builder = builder.Columns(userRows)
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 []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*User, error) {
builder = builder.Columns(userRows)
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 []*User
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
switch err {
case nil:
return resp, nil
default:
return nil, err
}
}
func (m *defaultUserModel) 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 *defaultUserModel) SelectBuilder() squirrel.SelectBuilder {
return squirrel.Select().From(m.table)
}
func (m *defaultUserModel) Delete(ctx context.Context, session sqlx.Session, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, data.Mobile)
_, 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)
}, userIdKey, userMobileKey)
return err
}
func (m *defaultUserModel) formatPrimary(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
}
func (m *defaultUserModel) 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", userRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo)
}
func (m *defaultUserModel) tableName() string {
return m.table
}

View File

@ -1,15 +0,0 @@
package model
import (
"errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var ErrNotFound = sqlx.ErrNotFound
var ErrNoRowsUpdate = errors.New("update db no rows change")
var UserAuthTypeAppMobile string = "app_mobile" //平台内部
var UserAuthTypeAppWechat string = "app_wechat" //微信小程序
var UserAuthTypeH5Mobile string = "h5_mobile"
var UserAuthTypeWxMini string = "wx_mini"
var UserAuthTypeWxOfficialAccount string = "wx_official_account"

View File

@ -15,4 +15,6 @@ const DB_UPDATE_AFFECTED_ZERO_ERROR uint32 = 100006
const PARAM_VERIFICATION_ERROR uint32 = 100007 const PARAM_VERIFICATION_ERROR uint32 = 100007
const CUSTOM_ERROR uint32 = 100008 const CUSTOM_ERROR uint32 = 100008
//用户模块 const LOGIN_FAILED uint32 = 200001
const LOGIC_QUERY_WAIT uint32 = 200002
const LOGIC_QUERY_ERROR uint32 = 200003

View File

@ -0,0 +1,25 @@
# 使用方法:
# .\genModel.ps1 user user
# .\genModel.ps1 user user_auth
# 再将 .\genModel 下的文件剪切到对应服务的 model 目录里面,记得改 package
# goctl model mysql datasource -url="qnc:5vg67b3UNHu8@tcp(127.0.0.1:20001)/qnc" -table="product" -dir="./model" --home="../template" -cache=true --style=goZero
param (
[string]$database,
[string]$tables
)
# 生成的表名
$modeldir = "./genModel"
$templateDir = Join-Path -Path (Resolve-Path "$PSScriptRoot/..") -ChildPath "template"
# 数据库配置
$host = "127.0.0.1"
$port = "20001"
$dbname = "$database"
$username = "qnc"
$passwd = "5vg67b3UNHu8"
Write-Output "开始创建库:$dbname 的表:$tables"
# 执行 goctl 命令生成 model
$command = "goctl model mysql datasource -url=`"$username`:$passwd`@tcp($host`:$port)/$dbname`" -table=`"$tables`" -dir=`"$modeldir`" --home=`"$templateDir`" -cache=true --style=goZero"
Invoke-Expression $command

25
deploy/sql/order.sql Normal file
View File

@ -0,0 +1,25 @@
CREATE TABLE `order` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` varchar(32) NOT NULL COMMENT '自生成的订单号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`product_id` bigint NOT NULL COMMENT '产品ID软关联到产品表',
`payment_platform` enum('alipay', 'wechat', 'other') NOT NULL COMMENT '支付平台(支付宝、微信、其他)',
`payment_scene` enum('app', 'h5', 'mini_program', 'public_account') NOT NULL COMMENT '支付场景App、H5、微信小程序、公众号',
`platform_order_id` varchar(64) DEFAULT NULL COMMENT '支付平台订单号',
`amount` decimal(10, 2) NOT NULL COMMENT '支付金额',
`status` enum('pending', 'paid', 'failed', 'refunded', 'closed') NOT NULL DEFAULT 'pending' COMMENT '支付状态',
`del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态',
`version` bigint NOT NULL DEFAULT '0' COMMENT '版本号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`pay_time` datetime DEFAULT NULL COMMENT '支付时间',
`refund_time` datetime DEFAULT NULL COMMENT '退款时间',
`close_time` datetime DEFAULT NULL COMMENT '订单关闭时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_product_id` (`product_id`),
KEY `idx_payment_platform` (`payment_platform`),
KEY `idx_payment_scene` (`payment_scene`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='订单表';

99
deploy/sql/product.sql Normal file
View File

@ -0,0 +1,99 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for product
-- ----------------------------
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
`del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态',
`version` bigint NOT NULL DEFAULT '0' COMMENT '版本号',
`product_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '服务名',
`product_en` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '英文名',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述',
`notes` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '备注',
`cost_price` DECIMAL(10, 2) NOT NULL DEFAULT '1.00' COMMENT '成本',
`sell_price` DECIMAL(10, 2) NOT NULL DEFAULT '1.00' COMMENT '售价',
PRIMARY KEY (`id`)
UNIQUE KEY `unique_product_name` (`product_name`),
UNIQUE KEY `unique_product_en` (`product_en`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='产品表';
-- ----------------------------
-- Records for product
-- ----------------------------
INSERT INTO `product` (`product_name`, `product_en`, `description`, `notes`, `cost_price`, `sell_price`) VALUES
('背景调查', 'backgroundchecklogic', '', '', 1, 1),
('企业报告', 'companyinfologic', '', '', 1, 1),
('家政服务', 'homeservicelogic', '', '', 1, 1),
('婚姻状态', 'marriagelogic', '', '', 1, 1),
('贷前背调', 'preloanbackgroundchecklogic', '', '', 1, 1),
('租赁服务', 'rentalinfologic', '', '', 1, 1),
('个人风险评估', 'riskassessmentlogic', '', '', 1, 1);
SET FOREIGN_KEY_CHECKS = 1;
-- ----------------------------
-- Table structure for feature
-- ----------------------------
DROP TABLE IF EXISTS `feature`;
CREATE TABLE `feature` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
`del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态',
`version` bigint NOT NULL DEFAULT '0' COMMENT '版本号',
`api_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'API标识',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_api_id` (`api_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='功能表';
-- ----------------------------
-- Table structure for product_feature
-- ----------------------------
DROP TABLE IF EXISTS `product_feature`;
CREATE TABLE `product_feature` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`product_id` bigint NOT NULL COMMENT '产品ID',
`feature_id` bigint NOT NULL COMMENT '功能ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
`del_state` tinyint NOT NULL DEFAULT '0' COMMENT '删除状态',
`version` bigint NOT NULL DEFAULT '0' COMMENT '版本号',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_product_feature` (`product_id`, `feature_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='产品与功能关联表';
-- ----------------------------
-- Records for feature
-- ----------------------------
INSERT INTO `feature` (`api_id`, `name`) VALUES
('G09SC02', '单人婚姻'),
('G27BJ05', '借贷意向'),
('G28BJ05', '借贷行为'),
('G26BJ05', '特殊名单'),
('G34BJ03', '个人不良'),
('G35SC01', '个人涉诉'),
('G05HZ01', '股东人企关系');
-- ----------------------------
-- 插入每个产品与每个功能的对应关系
-- ----------------------------
INSERT INTO `product_feature` (`product_id`, `feature_id`)
SELECT
p.id AS product_id,
f.id AS feature_id
FROM
product p
CROSS JOIN
feature f;

18
deploy/sql/query.sql Normal file
View File

@ -0,0 +1,18 @@
CREATE TABLE `query` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_id` BIGINT NOT NULL COMMENT '订单ID软关联到订单表',
`user_id` BIGINT NOT NULL COMMENT '用户ID直接关联到用户',
`product_id` BIGINT NOT NULL COMMENT '产品ID直接关联到产品',
`query_params` TEXT NOT NULL COMMENT '查询params数据',
`query_data` LONGTEXT COMMENT '查询结果数据',
`query_state` ENUM('pending', 'success', 'failed') NOT NULL DEFAULT 'pending' COMMENT '查询状态',
`del_state` TINYINT NOT NULL DEFAULT '0' COMMENT '删除状态',
`version` BIGINT NOT NULL DEFAULT '0' COMMENT '版本号',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` DATETIME DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_order_id` (`order_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='查询结果表,存储关联订单的查询数据';

View File

@ -9,15 +9,15 @@ CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT, `id` bigint NOT NULL AUTO_INCREMENT,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `delete_time` datetime DEFAULT NULL COMMENT '删除时间',
`del_state` tinyint NOT NULL DEFAULT '0', `del_state` tinyint NOT NULL DEFAULT '0',
`version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号',
`mobile` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `mobile` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
`info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', `info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `idx_mobile` (`mobile`) UNIQUE KEY `unique_mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
-- ---------------------------- -- ----------------------------
@ -28,15 +28,15 @@ CREATE TABLE `user_auth` (
`id` bigint NOT NULL AUTO_INCREMENT, `id` bigint NOT NULL AUTO_INCREMENT,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `delete_time` datetime DEFAULT NULL COMMENT '删除时间',
`del_state` tinyint NOT NULL DEFAULT '0', `del_state` tinyint NOT NULL DEFAULT '0',
`version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号',
`user_id` bigint NOT NULL DEFAULT '0', `user_id` bigint NOT NULL DEFAULT '0',
`auth_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台唯一id', `auth_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台唯一id',
`auth_type` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台类型', `auth_type` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '平台类型',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `idx_type_key` (`auth_type`,`auth_key`) USING BTREE, UNIQUE KEY `unique_type_key` (`auth_type`,`auth_key`) USING BTREE,
UNIQUE KEY `idx_userId_key` (`user_id`,`auth_type`) UNIQUE KEY `unique_userId_key` (`user_id`,`auth_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户授权表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户授权表';
SET FOREIGN_KEY_CHECKS = 1; SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,6 +1,5 @@
func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context,session sqlx.Session, data *{{.upperStartCamelObject}}) (sql.Result,error) { func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context,session sqlx.Session, data *{{.upperStartCamelObject}}) (sql.Result,error) {
data.DeleteTime = time.Unix(0,0)
data.DelState = globalkey.DelStateNo data.DelState = globalkey.DelStateNo
{{if .withCache}}{{.keys}} {{if .withCache}}{{.keys}}
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {

View File

@ -65,7 +65,7 @@ func (m *default{{.upperStartCamelObject}}Model) UpdateWithVersion(ctx context.C
func (m *default{{.upperStartCamelObject}}Model) DeleteSoft(ctx context.Context,session sqlx.Session,data *{{.upperStartCamelObject}}) error { func (m *default{{.upperStartCamelObject}}Model) DeleteSoft(ctx context.Context,session sqlx.Session,data *{{.upperStartCamelObject}}) error {
data.DelState = globalkey.DelStateYes data.DelState = globalkey.DelStateYes
data.DeleteTime = time.Now() data.DeleteTime = sql.NullTime{Time: time.Now(), Valid: true}
if err:= m.UpdateWithVersion(ctx,session, data);err!= nil{ if err:= m.UpdateWithVersion(ctx,session, data);err!= nil{
return errors.Wrapf(errors.New("delete soft failed "),"{{.upperStartCamelObject}}Model delete err : %+v",err) return errors.Wrapf(errors.New("delete soft failed "),"{{.upperStartCamelObject}}Model delete err : %+v",err)
} }

View File

@ -31,8 +31,8 @@ services:
restart: always restart: always
networks: networks:
- qnc_net - qnc_net
- 1panel-network
#redis容器 - Redis container
redis: redis:
image: redis:7.4.0 image: redis:7.4.0
container_name: qnc_redis container_name: qnc_redis
@ -50,8 +50,22 @@ services:
networks: networks:
- qnc_net - qnc_net
asynqmon:
image: hibiken/asynqmon:latest
container_name: qnc_asynqmon
ports:
- "20003:8080"
command:
- '--redis-addr=qnc_redis:6379'
- '--redis-password=3m3WsgyCKWqz'
restart: always
networks:
- qnc_net
depends_on:
- redis
networks: networks:
qnc_net: qnc_net:
driver: bridge driver: bridge
1panel-network:
external: true

View File

@ -1,24 +0,0 @@
version: '3'
services:
qnc_user_api:
image: cosmtrek/air
container_name: qnc_user_api
environment:
- TZ=Asia/Shanghai
working_dir: /app # 将工作目录设置为根目录
ports:
- 31001:8888
volumes:
- .:/app # 将项目根目录挂载到容器根目录
entrypoint: air -c ./app/user/cmd/api/.air.toml
privileged: true
restart: always
networks:
- qnc_net
networks:
qnc_net:
driver: bridge

16
go.mod
View File

@ -10,9 +10,12 @@ require (
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 github.com/alibabacloud-go/tea-utils/v2 v2.0.7
github.com/go-playground/validator/v10 v10.22.1 github.com/go-playground/validator/v10 v10.22.1
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/jinzhu/copier v0.4.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/shopspring/decimal v1.4.0 github.com/shopspring/decimal v1.4.0
github.com/smartwalle/alipay/v3 v3.2.23
github.com/sony/sonyflake v1.2.0 github.com/sony/sonyflake v1.2.0
github.com/wechatpay-apiv3/wechatpay-go v0.2.20
github.com/zeromicro/go-zero v1.7.3 github.com/zeromicro/go-zero v1.7.3
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1
) )
@ -40,7 +43,7 @@ require (
github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/jinzhu/copier v0.4.0 // indirect github.com/hibiken/asynq v0.25.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
@ -52,13 +55,22 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/panjf2000/ants/v2 v2.10.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/smartwalle/ncrypto v1.0.4 // indirect
github.com/smartwalle/ngx v1.0.9 // indirect
github.com/smartwalle/nsign v1.0.9 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
@ -74,8 +86,10 @@ require (
go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect
golang.org/x/crypto v0.28.0 // indirect golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/protobuf v1.35.1 // indirect google.golang.org/protobuf v1.35.1 // indirect

34
go.sum
View File

@ -6,6 +6,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
@ -134,6 +136,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hibiken/asynq v0.25.0 h1:VCPyRRrrjFChsTSI8x5OCPu51MlEz6Rk+1p0kHKnZug=
github.com/hibiken/asynq v0.25.0/go.mod h1:DYQ1etBEl2Y+uSkqFElGYbk3M0ujLVwCfWE+TlvxtEk=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -173,6 +177,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -192,10 +198,20 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE=
github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw=
github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@ -203,6 +219,8 @@ github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y= github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@ -214,12 +232,23 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/wechatpay-apiv3/wechatpay-go v0.2.20 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJWgbtqjCq6k1L9DQ=
github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -301,6 +330,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -339,6 +371,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

View File

@ -1,5 +0,0 @@
#user
app/user/cmd/api/**/*.go {
prep: go build -o data/server/user-api -v app/user/cmd/api/user.go
daemon +sigkill: ./data/server/user-api -f app/user/cmd/api/etc/user.yaml
}

105
pkg/lzkit/crypto/crypto.go Normal file
View File

@ -0,0 +1,105 @@
package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"io"
)
// PKCS7填充
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// 去除PKCS7填充
func PKCS7UnPadding(origData []byte) ([]byte, error) {
length := len(origData)
if length == 0 {
return nil, errors.New("input data error")
}
unpadding := int(origData[length-1])
if unpadding > length {
return nil, errors.New("unpadding size is invalid")
}
// 检查填充字节是否一致
for i := 0; i < unpadding; i++ {
if origData[length-1-i] != byte(unpadding) {
return nil, errors.New("invalid padding")
}
}
return origData[:(length - unpadding)], nil
}
// AES CBC模式加密Base64传入传出
func AesEncrypt(plainText, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
blockSize := block.BlockSize()
plainText = PKCS7Padding(plainText, blockSize)
cipherText := make([]byte, blockSize+len(plainText))
iv := cipherText[:blockSize] // 使用前blockSize字节作为IV
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
return "", err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(cipherText[blockSize:], plainText)
return base64.StdEncoding.EncodeToString(cipherText), nil
}
// AES CBC模式解密Base64传入传出
func AesDecrypt(cipherTextBase64 string, key []byte) ([]byte, error) {
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
if len(cipherText) < blockSize {
return nil, errors.New("ciphertext too short")
}
iv := cipherText[:blockSize]
cipherText = cipherText[blockSize:]
if len(cipherText)%blockSize != 0 {
return nil, errors.New("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(cipherText, cipherText)
plainText, err := PKCS7UnPadding(cipherText)
if err != nil {
return nil, err
}
return plainText, nil
}
// Md5Encrypt 用于对传入的message进行MD5加密
func Md5Encrypt(message string) string {
hash := md5.New()
hash.Write([]byte(message)) // 将字符串转换为字节切片并写入
return hex.EncodeToString(hash.Sum(nil)) // 将哈希值转换为16进制字符串并返回
}

View File

@ -0,0 +1,63 @@
package crypto
import (
"crypto/rand"
"encoding/hex"
"io"
mathrand "math/rand"
"strconv"
"time"
)
// 生成AES-128密钥的函数符合市面规范
func GenerateSecretKey() (string, error) {
key := make([]byte, 16) // 16字节密钥
_, err := io.ReadFull(rand.Reader, key)
if err != nil {
return "", err
}
return hex.EncodeToString(key), nil
}
func GenerateSecretId() (string, error) {
// 创建一个字节数组,用于存储随机数据
bytes := make([]byte, 8) // 因为每个字节表示两个16进制字符
// 读取随机字节到数组中
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
// 将字节数组转换为16进制字符串
return hex.EncodeToString(bytes), nil
}
// GenerateTransactionID 生成16位数的交易单号
func GenerateTransactionID() string {
length := 16
// 获取当前时间戳
timestamp := time.Now().UnixNano()
// 转换为字符串
timeStr := strconv.FormatInt(timestamp, 10)
// 生成随机数
mathrand.Seed(time.Now().UnixNano())
randomPart := strconv.Itoa(mathrand.Intn(1000000))
// 组合时间戳和随机数
combined := timeStr + randomPart
// 如果长度超出指定值,则截断;如果不够,则填充随机字符
if len(combined) >= length {
return combined[:length]
}
// 如果长度不够填充0
for len(combined) < length {
combined += strconv.Itoa(mathrand.Intn(10)) // 填充随机数
}
return combined
}

View File

@ -0,0 +1,150 @@
package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"encoding/base64"
)
const (
KEY_SIZE = 16 // AES-128, 16 bytes
)
// Encrypt encrypts the given data using AES encryption in ECB mode with PKCS5 padding
func WestDexEncrypt(data, secretKey string) (string, error) {
key := generateAESKey(KEY_SIZE*8, []byte(secretKey))
ciphertext, err := aesEncrypt([]byte(data), key)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// Decrypt decrypts the given base64-encoded string using AES encryption in ECB mode with PKCS5 padding
func WestDexDecrypt(encodedData, secretKey string) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encodedData)
if err != nil {
return nil, err
}
key := generateAESKey(KEY_SIZE*8, []byte(secretKey))
plaintext, err := aesDecrypt(ciphertext, key)
if err != nil {
return nil, err
}
return plaintext, nil
}
// generateAESKey generates a key for AES encryption using a SHA-1 based PRNG
func generateAESKey(length int, password []byte) []byte {
h := sha1.New()
h.Write(password)
state := h.Sum(nil)
keyBytes := make([]byte, 0, length/8)
for len(keyBytes) < length/8 {
h := sha1.New()
h.Write(state)
state = h.Sum(nil)
keyBytes = append(keyBytes, state...)
}
return keyBytes[:length/8]
}
// aesEncrypt encrypts plaintext using AES in ECB mode with PKCS5 padding
func aesEncrypt(plaintext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
paddedPlaintext := pkcs5Padding(plaintext, block.BlockSize())
ciphertext := make([]byte, len(paddedPlaintext))
mode := newECBEncrypter(block)
mode.CryptBlocks(ciphertext, paddedPlaintext)
return ciphertext, nil
}
// aesDecrypt decrypts ciphertext using AES in ECB mode with PKCS5 padding
func aesDecrypt(ciphertext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
plaintext := make([]byte, len(ciphertext))
mode := newECBDecrypter(block)
mode.CryptBlocks(plaintext, ciphertext)
return pkcs5Unpadding(plaintext), nil
}
// pkcs5Padding pads the input to a multiple of the block size using PKCS5 padding
func pkcs5Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// pkcs5Unpadding removes PKCS5 padding from the input
func pkcs5Unpadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}
// ECB mode encryption/decryption
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbEncrypter ecb
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
type ecbDecrypter ecb
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Decrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}

View File

@ -0,0 +1,65 @@
package delay
import (
"errors"
"fmt"
"time"
)
// ProgressiveDelay 用于管理渐进式延迟策略
type ProgressiveDelay struct {
initialDelay time.Duration // 初始延迟时间
growthFactor float64 // 延迟增长因子 (例如 1.5)
maxDelay time.Duration // 最大延迟时间
maxRetryDuration time.Duration // 最大重试时间
currentDelay time.Duration // 当前延迟时间
startTime time.Time // 重试开始时间
}
// New 创建一个新的渐进式延迟对象
func New(initialDelay, maxDelay, maxRetryDuration time.Duration, growthFactor float64) (*ProgressiveDelay, error) {
// 参数校验
if initialDelay <= 0 {
return nil, errors.New("initialDelay must be greater than zero")
}
if maxDelay <= 0 {
return nil, errors.New("maxDelay must be greater than zero")
}
if maxRetryDuration <= 0 {
return nil, errors.New("maxRetryDuration must be greater than zero")
}
if growthFactor <= 1.0 {
return nil, errors.New("growthFactor must be greater than 1")
}
// 初始化并返回
return &ProgressiveDelay{
initialDelay: initialDelay,
maxDelay: maxDelay,
maxRetryDuration: maxRetryDuration,
growthFactor: growthFactor,
currentDelay: initialDelay,
startTime: time.Now(),
}, nil
}
// NextDelay 计算并返回下次的延迟时间
func (pd *ProgressiveDelay) NextDelay() (time.Duration, error) {
// 检查最大重试时间是否已过
if time.Since(pd.startTime) > pd.maxRetryDuration {
return 0, fmt.Errorf("最大重试时间超过限制: %v", pd.maxRetryDuration)
}
// 返回当前延迟时间
delay := pd.currentDelay
// 计算下一个延迟时间并更新 currentDelay
pd.currentDelay = time.Duration(float64(pd.currentDelay) * pd.growthFactor)
// 如果下次延迟超过最大延迟时间,限制在最大值
if pd.currentDelay > pd.maxDelay {
pd.currentDelay = pd.maxDelay
}
return delay, nil
}

View File

@ -0,0 +1,38 @@
package lzUtils
import (
"database/sql"
"time"
)
// StringToNullString 将 string 转换为 sql.NullString
func StringToNullString(s string) sql.NullString {
return sql.NullString{
String: s,
Valid: s != "",
}
}
// NullStringToString 将 sql.NullString 转换为 string
func NullStringToString(ns sql.NullString) string {
if ns.Valid {
return ns.String
}
return ""
}
// TimeToNullTime 将 time.Time 转换为 sql.NullTime
func TimeToNullTime(t time.Time) sql.NullTime {
return sql.NullTime{
Time: t,
Valid: !t.IsZero(), // 仅当 t 不是零值时才设置为有效
}
}
// NullTimeToTime 将 sql.NullTime 转换为 time.Time
func NullTimeToTime(nt sql.NullTime) time.Time {
if nt.Valid {
return nt.Time
}
return time.Time{} // 返回零值时间
}

View File

@ -0,0 +1,15 @@
package lzUtils
import "fmt"
// ToWechatAmount 将金额从元转换为微信支付 SDK 需要的分int64 类型)
func ToWechatAmount(amount float64) int64 {
// 将金额从元转换为分,并四舍五入
return int64(amount*100 + 0.5)
}
// ToAlipayAmount 将金额从元转换为支付宝支付 SDK 需要的字符串格式,保留两位小数
func ToAlipayAmount(amount float64) string {
// 格式化为字符串,保留两位小数
return fmt.Sprintf("%.2f", amount)
}

View File

@ -47,6 +47,9 @@ func init() {
if err := validate.RegisterValidation("password", validatePassword); err != nil { if err := validate.RegisterValidation("password", validatePassword); err != nil {
panic(fmt.Sprintf("注册 password 验证器时发生错误: %v", err)) panic(fmt.Sprintf("注册 password 验证器时发生错误: %v", err))
} }
if err := validate.RegisterValidation("payMethod", validatePayMethod); err != nil {
panic(fmt.Sprintf("注册 payMethod 验证器时发生错误: %v", err))
}
} }
@ -152,3 +155,20 @@ func validatePassword(fl validator.FieldLevel) bool {
return true return true
} }
// 支付方式
func validatePayMethod(fl validator.FieldLevel) bool {
payMethod := fl.Field().String()
if payMethod == "" {
return true // 如果为空,认为是有效的
}
validTypes := map[string]bool{
"alipay": true, // 中国电信
"wechatpay": true, // 中国移动
}
return validTypes[payMethod]
}

148
test/test.go Normal file

File diff suppressed because one or more lines are too long