Compare commits

..

No commits in common. "pre" and "main" have entirely different histories.
pre ... main

233 changed files with 621 additions and 22867 deletions

7
.gitignore vendored
View File

@ -25,11 +25,8 @@ Thumbs.db
bin/
cert/
log
configs/config_pro.yaml
configs/config_pre.yaml
configs/config_pre2.yaml
configs/config_mock.yaml
configs_prd
api/**/*.go
cmd/server/wire_gen.go
/cmd/server/wire_gen.go
/internal/conf/*.go
/third_party/swagger_ui/openapi.yaml

View File

@ -1,11 +1,20 @@
FROM registry.cn-chengdu.aliyuncs.com/pkgtool/build:1.23.6 AS builder
FROM registry.cn-chengdu.aliyuncs.com/lansexiongdi/build:1.22.2 AS builder
ENV GOCACHE /root/.cache/go-build
COPY . /src
WORKDIR /src
RUN make build
FROM registry.cn-chengdu.aliyuncs.com/pkgtool/work:v0.0.1
FROM registry.cn-chengdu.aliyuncs.com/lansexiongdi/work:v1
RUN echo 'http://mirrors.ustc.edu.cn/alpine/v3.5/main' > /etc/apk/repositories \
&& echo 'http://mirrors.ustc.edu.cn/alpine/v3.5/community' >>/etc/apk/repositories \
&& apk update && apk add tzdata \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
COPY --from=builder /src/bin /app
#删除config文件线上是通过挂载进来的

View File

@ -27,7 +27,7 @@ init:
go install github.com/envoyproxy/protoc-gen-validate@v1.0.2
.PHONY: config
# generate cpn proto
# generate internal proto
config:
protoc --proto_path=./internal \
--proto_path=./third_party \
@ -76,11 +76,6 @@ all:
make generate;
make wire;
.PHONY: test
# test
test:
sh ./stress_test.sh $(t)
.DEFAULT_GOAL := help
# show help
help:

View File

@ -1,5 +1,59 @@
# <p align="center">招行立减金券系统</p>
# <p align="center">营销系统后台API</p>
### 参与开发
[请参阅](https://tvd8jq9lqkp.feishu.cn/wiki/LNWVweZ64iY2UBkkTkZcezy0n5h?from=from_copylink)
* * *
### 主要工作
+ 发券API
+ 后台接口API
* * *
### 规则说明
+ 路由前缀都为 __/admin__ 开始,路由规则全小写+下划线,例如:/admin/v1/demo_1
* * *
### 构建部署
+ 采用多阶段构建,以获得最小体积的容器镜像
````bash
cd /项目根目录 && make deploy folder=./configs_dev marketing=marketing_backend container_name=marketing_backend http_port=8090
````
* * *
### docker环境下开发
+ 一、[下载Docker Desktop安装程序](https://www.docker.com/products/docker-desktop)
+ 二、在项目根目录下执行命令
```shell
docker build -f Dockerfile_win -t 镜像名称 .
docker run --privileged -itd --name 容器名称 --restart=always -v ./:/src 镜像名称
docker ps
docker exec -it 容器名称 sh
make init
make all
```
### windows非docker开发
1 安装插件(配置goproxy,GOPROXY=https://goproxy.cn,direct)
```shell
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
go install github.com/google/wire/cmd/wire@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest
go install gorm.io/gen/tools/gentool@latest
```
2生成相应rpo
命令kratos proto client api/helloworld/v1/demo.proto
位置api和internal下面的conf
3 wire生成依赖
cd cmd/server
wire
4 配置编译
![img_1.png](img.png)
5 生成service
kratos proto server api/helloworld/v1/demo.proto -t internal/service

View File

@ -1,30 +0,0 @@
syntax = "proto3";
package api.err;
import "errors/errors.proto";
option go_package = "voucher/api/err";
enum CmbErr{
option (errors.default_code) = 499;
// ,
CMB_UNKNOWN = 0;
// ,
CMB_PARAM_FAIL = 1 [(errors.code) = 400];
//
CMB_VERIFY_FAIL = 2 [(errors.code) = 401];
//
CMB_BIZ_CONTENT_DECRYPT_FAIL = 3 [(errors.code) = 401];
// ,
CMB_BIZ_CONTENT_CONVERT_FAIL = 4 [(errors.code) = 401];
//
CMB_BIZ_CONTENT_FAIL = 5 [(errors.code) = 401];
//
CMB_ORDER_NOT_EXIST = 6 [(errors.code) = 404];
//
CMB_PRODUCT_NOT_EXIST = 7 [(errors.code) = 404];
//
CMB_PRODUCT_NOT_AUTH = 8 [(errors.code) = 401];
//
CMB_PRODUCT_NOT_SUPPORTED = 9 [(errors.code) = 401];
}

View File

@ -11,12 +11,15 @@ enum Err {
// panic错误
SYSTEM_PANIC = 0 [(errors.code) = 599];
// DB数据未找到
DB_NOT_FOUND = 1 [(errors.code) = 404];
}
//
NOT_LOGIN = 1 [(errors.code) = 401];
enum NotifyConsumeErr{
option (errors.default_code) = 1;
//
NeedRetryNotify = 0 [(errors.code) = 500];
// DB数据未找到
DB_NOT_FOUND = 2 [(errors.code) = 404];
//
PARAM = 3 [(errors.code) = 400];
//
COMMON = 4 [(errors.code) = 555];
}

View File

@ -1,41 +0,0 @@
syntax = "proto3";
package api.err;
import "errors/errors.proto";
option go_package = "voucher/api/err";
enum WechatErr{
option (errors.default_code) = 1;
WechatFAIL = 0 [(errors.code) = 500];
WechatUserIllegal = 1 [(errors.code) = 500];
WechatAppIDMchIDMismatch = 2 [(errors.code) = 500];
WechatOpenIDAppIDMismatch = 3 [(errors.code) = 500];
WechatInvalidMerchantID = 4 [(errors.code) = 500];
WechatHighFrequency = 5 [(errors.code) = 500];
WechatActivityInactive = 6 [(errors.code) = 500];
WechatBatchInfoError = 7 [(errors.code) = 500];
WechatAppIDRequired = 8 [(errors.code) = 500];
WechatOpenIDRequired = 9 [(errors.code) = 500];
WechatBatchIDRequired = 10 [(errors.code) = 500];
WechatMerchantIDRequired = 11 [(errors.code) = 500];
WechatInvalidBatchStatus = 12 [(errors.code) = 500];
WechatMchNotExists = 13 [(errors.code) = 500];
WechatBatchBudgetInsufficient = 14 [(errors.code) = 500];
WechatDailyLimitExceeded = 15 [(errors.code) = 500];
WechatAccountBalanceInsufficient = 16 [(errors.code) = 500];
WechatBatchBudgetDepleted = 17 [(errors.code) = 500];
WechatMerchantNoPermission = 18 [(errors.code) = 500];
WechatCrossMerchantNotSupported = 19 [(errors.code) = 500];
WechatUserReceiveLimit = 20 [(errors.code) = 500];
WechatAPIChannelNotSupported = 21 [(errors.code) = 500];
WechatSpecifiedDenominationNotSupported = 22 [(errors.code) = 500];
WechatOnlyAdvertisingBatch = 23 [(errors.code) = 500];
WechatUserMaxCoupons = 24 [(errors.code) = 500];
WechatNaturalPersonRuleBlocked = 25 [(errors.code) = 500];
WechatResourceNotExists = 26 [(errors.code) = 500];
WechatFrequencyLimited = 27 [(errors.code) = 500];
WechatAccountFail = 28 [(errors.code) = 500];
//
WechatNeedNoticeFail = 29 [(errors.code) = 500];
}

View File

@ -1,226 +0,0 @@
syntax = "proto3";
package api.v1;
option go_package = "voucher/api/v1;v1";
import "validate/validate.proto";
import "google/api/annotations.proto";
import "v1/common.proto";
service Cmb {
rpc Order (CmbRequest) returns (CmbReply) {
option (google.api.http) = {
post: "/voucher/cmb/v1/order",
body: "*"
};
}
rpc Query (CmbRequest) returns (CmbReply) {
option (google.api.http) = {
post: "/voucher/cmb/v1/query",
body: "*"
};
}
rpc QueryProduct (CmbRequest) returns (CmbReply) {
option (google.api.http) = {
post: "/voucher/cmb/v1/product/query",
body: "*"
};
}
rpc OrderRetry (OrderRetryRequest) returns (Empty) {
option (google.api.http) = {
post: "/voucher/cmb/v1/orderRetry",
body: "*"
};
}
rpc OrderMock (CmbOrderRequest) returns (CmbRequest) {
option (google.api.http) = {
post: "/voucher/cmb/v1/orderMock",
body: "*"
};
}
rpc QueryMock (CmbQueryRequest) returns (CmbRequest) {
option (google.api.http) = {
post: "/voucher/cmb/v1/queryMock",
body: "*"
};
}
rpc QueryProductMock (CmbQueryProductRequest) returns (CmbRequest) {
option (google.api.http) = {
post: "/voucher/cmb/v1/product/queryMock",
body: "*"
};
}
rpc DecryptBody (EncryptBodyRequest) returns (DecryptBodyReply) {
option (google.api.http) = {
post: "/voucher/cmb/v1/decryptBody",
body: "*"
};
}
}
message OrderRetryRequest {
repeated string transactionIds = 1 [json_name = "transactionIds"];
}
message CmbRequest {
//
// ID32
string mid = 1 [json_name = "mid"];
// ID32
string aid = 2 [json_name = "aid"];
// yyyyMMddHHmmss
string date = 3 [json_name = "date"];
// 32
string random = 4 [json_name = "random"];
//
string keyAlias = 5 [json_name = "keyAlias"];
//
string cmbKeyAlias = 6 [json_name = "cmbKeyAlias"];
// API的说明文档
string encryptBody = 7 [json_name = "encryptBody"];
//
string sign = 8 [json_name = "sign"];
}
message CmbReply {
//
// 1000 1001
string respCode = 1 [json_name = "respCode"];
//
string respMsg = 2 [json_name = "respMsg"];
// yyyyMMddHHmmss
string date = 3 [json_name = "date"];
//
string keyAlias = 5 [json_name = "keyAlias"];
//
string cmbKeyAlias = 6 [json_name = "cmbKeyAlias"];
// API的说明文档
string encryptBody = 7 [json_name = "encryptBody"];
//
string sign = 8 [json_name = "sign"];
}
message CmbOrderRequest {
//
// 14
string transactionId = 9 [json_name = "transactionId", (validate.rules).string = {min_len: 1,max_len: 50}];
//
string activityId = 10 [json_name = "activityId", (validate.rules).string = {min_len: 1,max_len: 32}];
// openId
string cmbUid = 11 [json_name = "cmbUid", (validate.rules).string = {min_len: 1,max_len: 50}];
// id
string appId = 14 [json_name = "appId", (validate.rules).string = {min_len: 1,max_len: 20}];
// 0-1-openId
string cmbUidType = 12 [json_name = "cmbUidType", (validate.rules).string = {min_len: 1,max_len: 10}];
// 13
string timestamp = 13 [json_name = "timestamp", (validate.rules).string = {min_len: 1,max_len: 14}];
//
string attach = 15 [json_name = "attach"];
}
message CmbOrderReply {
//
// 1000 1001
string respCode = 1 [json_name = "respCode"];
//
string respMsg = 2 [json_name = "respMsg"];
// 50
string codeNo = 9 [json_name = "codeNo"];
//
string thirdErrCode = 3 [json_name = "thirdErrCode"];
}
message CmbQueryRequest {
//
//
string codeNo = 9 [json_name = "codeNo", (validate.rules).string = {min_len: 1,max_len: 32}];
}
message CmbQueryReply {
// codeNo
string ticket = 9 [json_name = "ticket"];
// 0使1使
string status = 10 [json_name = "status"];
// yyyy-mm-dd hh:mm:ss.sss
string transDate = 11 [json_name = "transDate"];
//
string orgNo = 12 [json_name = "orgNo"];
//
string ext = 13 [json_name = "ext"];
}
message CmbQueryProductRequest {
//
//
string activityId = 9 [json_name = "activityId", (validate.rules).string = {min_len: 1,max_len: 32}];
}
message CmbQueryProductReply {
// 1000 1001
string respCode = 1 [json_name = "respCode"];
//
string respMsg = 2 [json_name = "respMsg"];
//
//
string activityName = 9 [json_name = "activityName"];
//
string activityId = 10 [json_name = "activityId"];
//
string amount = 11 [json_name = "amount"];
//
string minAmount = 12 [json_name = "minAmount"];
// 01
string availableType = 13 [json_name = "availableType"];
// -
string availableDays = 14 [json_name = "availableDays"];
// -
string startTime = 15 [json_name = "startTime"];
// -
string endTime = 16 [json_name = "endTime"];
//
string availableStock = 17 [json_name = "availableStock"];
//
string detail = 18 [json_name = "detail"];
// yyyy-mm-dd hh:mm:ss.sss
string saleStartTime = 19 [json_name = "saleStartTime"];
// yyyy-mm-dd hh:mm:ss.sss
string saleEndTime = 20 [json_name = "saleEndTime"];
//
string thirdErrCode = 21 [json_name = "thirdErrCode"];
}
message CmbNotifyRequest {
// codeNo
string ticket = 9 [json_name = "ticket"];
// 0使1使
string status = 10 [json_name = "status"];
// yyyy-mm-dd hh:mm:ss.sss
string transDate = 11 [json_name = "transDate"];
//
string orgNo = 12 [json_name = "orgNo"];
//
string ext = 13 [json_name = "ext"];
string attach = 14 [json_name = "attach"];
}
message CmbNotifyReply {
// 1000 1001
string respCode = 1 [json_name = "respCode"];
//
string respMsg = 2 [json_name = "respMsg"];
}
message EncryptBodyRequest {
string encryptBody = 1 [json_name = "encryptBody"];
}
message DecryptBodyReply {
string decryptBody = 1 [json_name = "decryptBody"];
}

View File

@ -5,3 +5,43 @@ option go_package = "voucher/api/v1;v1";
//
message Empty {}
//
message ReqEmpty {}
//
message RespEmpty {}
//
message RespOption {
//
string label = 1;
//
string value = 2;
//
bool select = 3;
//
bool disable = 4;
//
repeated RespOption children = 5;
}
//
message RespOptions {
//
repeated RespOption list = 1;
}
// ReqPage
message RespPage {
//
int32 page = 1;
//
int32 limit = 2;
//
int32 total = 3;
}

40
api/v1/order.proto Normal file
View File

@ -0,0 +1,40 @@
syntax = "proto3";
package api.v1;
option go_package = "voucher/api/v1;v1";
import "google/api/annotations.proto";
import "validate/validate.proto";
import "v1/common.proto";
service Order {
rpc Order (OrderRequest) returns (OrderReply) {
option (google.api.http) = {
post: "/openapi/v1/voucher/order",
body:"*"
};
}
rpc Query (QueryRequest) returns (QueryReply) {
option (google.api.http) = {
post: "/openapi/v1/voucher/query",
body:"*"
};
}
}
message OrderRequest {
}
message OrderReply {
}
message QueryRequest {
}
message QueryReply {
}

View File

@ -12,7 +12,6 @@ import (
"github.com/nacos-group/nacos-sdk-go/common/constant"
_ "go.uber.org/automaxprocs"
"gopkg.in/yaml.v2"
"html/template"
"os"
"voucher/internal/conf"
log2 "voucher/internal/pkg/log"
@ -47,7 +46,7 @@ const (
func init() {
flag.StringVar(&nacosIp, "nacosIp", "120.55.12.245", "nacos ip address")
flag.IntVar(&nacosPort, "nacosPort", 8848, "nacos port")
flag.StringVar(&nacosSpace, "nacosSpace", "voucher-test", "nacos space")
flag.StringVar(&nacosSpace, "nacosSpace", "voucher", "nacos space")
flag.StringVar(&nacosUsername, "nacosUsername", "nacos", "nacos passowrd")
flag.StringVar(&nacosPassword, "nacosPassword", "LsxdNacos@2025", "nacos passowrd")
flag.Parse()
@ -58,9 +57,6 @@ func newApp(
logger log.Logger,
httpServer *http.Server,
consumerServer *server.Consumer,
cronServer *server.CronServer,
wechatNotifyConsumer *server.WechatNotifyConsumer,
rdbConsumer *server.RdbConsumer,
) *kratos.App {
return kratos.New(
kratos.ID(id),
@ -70,9 +66,6 @@ func newApp(
kratos.Server(
httpServer,
consumerServer,
cronServer,
wechatNotifyConsumer,
rdbConsumer,
),
)
}
@ -121,14 +114,10 @@ func loadConfig() *conf.Bootstrap {
func main() {
bc := loadConfig()
if bc == nil {
panic("配置文件加载失败")
}
businessLogger := log2.NewBusinessLogger(bc.Logs.Business, Name, Name, Version)
accessLogger := log2.NewAccessLogger(bc.Logs.Access, id, Name, Version)
accessLogger := log2.NewAccessLogger(bc.Logs.Business, id, Name, Version)
app, cleanup, err := wireApp(bc, temp(), businessLogger, accessLogger)
app, cleanup, err := wireApp(bc, businessLogger, accessLogger)
if err != nil {
panic(err)
}
@ -139,17 +128,3 @@ func main() {
panic(err)
}
}
func temp() *template.Template {
wd, err := os.Getwd()
if err != nil {
panic(fmt.Sprintf("获取当前工作目录失败:", err))
}
templatePath := fmt.Sprintf("%s/templates/index.tmpl", wd)
tmpl, err := template.ParseFiles(templatePath)
if err != nil {
panic(err)
}
return tmpl
}

View File

@ -9,35 +9,26 @@ import (
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/log"
"github.com/google/wire"
"github.com/robfig/cron"
"html/template"
"voucher/internal/biz"
"voucher/internal/biz/cmb"
"voucher/internal/biz/timeslicequery"
"voucher/internal/conf"
"voucher/internal/data"
"voucher/internal/data/mixrepoimpl"
"voucher/internal/data/repoimpl"
"voucher/internal/data/wechatrepoimpl"
"voucher/internal/data/repositoryimpl"
"voucher/internal/data/thirdrepositoryimpl"
log2 "voucher/internal/pkg/log"
"voucher/internal/server"
"voucher/internal/service"
)
// wireApp init kratos application.
func wireApp(*conf.Bootstrap, *template.Template, log.Logger, *log2.AccessLogger) (*kratos.App, func(), error) {
func wireApp(*conf.Bootstrap, log.Logger, *log2.AccessLogger) (*kratos.App, func(), error) {
panic(wire.Build(
server.ProviderSetServer,
service.ProviderSetService,
cmb.ProviderSetCmb,
biz.ProviderSetBiz,
repositoryimpl.ProviderRepoImplSet,
thirdrepositoryimpl.ProviderThirdRepositoryImplSet,
data.ProviderDataSet,
repoimpl.ProviderRepoImplSet,
wechatrepoimpl.ProviderWechatReposImplSet,
mixrepoimpl.ProviderMixRepoImplSet,
timeslicequery.ProviderSetTimeSliceQuery,
log2.NewLogHelper,
cron.New,
newApp,
))
}

49
cmd/server/wire_gen.go Normal file
View File

@ -0,0 +1,49 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
import (
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/log"
"voucher/internal/biz"
"voucher/internal/conf"
"voucher/internal/data"
"voucher/internal/data/repositoryimpl"
"voucher/internal/data/thirdrepositoryimpl"
log2 "voucher/internal/pkg/log"
"voucher/internal/server"
"voucher/internal/service"
)
import (
_ "go.uber.org/automaxprocs"
)
// Injectors from wire.go:
// wireApp init kratos application.
func wireApp(bootstrap *conf.Bootstrap, logger log.Logger, accessLogger *log2.AccessLogger) (*kratos.App, func(), error) {
helper := log2.NewLogHelper(logger)
gormDb, cleanup := data.NewGormDb(bootstrap)
db := data.NewDb(gormDb)
orderRepo := repositoryimpl.NewOrderRepoImpl(db)
rocketMQ, cleanup2, err := data.NewRocketMQ(bootstrap)
if err != nil {
cleanup()
return nil, nil, err
}
thirdMQSend := thirdrepositoryimpl.NewMQSendImpl(rocketMQ)
voucherBiz := biz.NewVoucherBiz(orderRepo, thirdMQSend)
voucherService := service.NewVoucherService(voucherBiz)
httpServer := server.NewHTTPServer(bootstrap, helper, accessLogger, voucherService)
consumer := server.NewConsumer(helper, bootstrap, voucherService)
app := newApp(logger, httpServer, consumer)
return app, func() {
cleanup2()
cleanup()
}, nil
}

View File

@ -8,20 +8,20 @@ server:
data:
db:
driver: mysql
source: root:lansexiongdi6,@tcp(47.108.53.72:3306)/voucher?parseTime=True&loc=Local
maxIdle: 200 #最大的空闲连接数
maxOpen: 1000 #最大连接数0表示不受限制
maxLifetime: 300s #连接复用的最大生命周期
isDebug: false
source: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local
maxIdle: 5 #最大的空闲连接数
maxOpen: 100 #最大连接数0表示不受限制
maxLifetime: 5s #连接复用的最大生命周期
isDebug: true
redis: #没有则注释此属性
addr: 47.108.53.72:6379
addr: 47.97.27.195:6379
password: lansexiongdi@666
readTimeout: 5s
writeTimeout: 5s
poolSize: 200 #连接池大小不配置或配置为0表示不启用连接池
minIdleConns: 50 #最小空闲连接数
connMaxIdleTime: 60s #每个连接最大空闲时间,如果超过了这个时间会被关闭
db: 3
readTimeout: 3s
writeTimeout: 3s
poolSize: 5 #连接池大小不配置或配置为0表示不启用连接池
minIdleConns: 2 #最小空闲连接数
connMaxIdleTime: 30s #每个连接最大空闲时间,如果超过了这个时间会被关闭
db: 1
rocketMQ:
addr: "http://rmq-cn-nwy3fn4ex09.cn-chengdu.rmq.aliyuncs.com:8080"
@ -29,98 +29,24 @@ rocketMQ:
secretKey: "Z3596KCFA9RAUR6k"
secretToken: ""
eventMap:
notifyRetry: # 重试延迟队列
topic: voucher_order_notifyRetry
group: voucher_order_notifyRetry_group
isOpenConsumer: true #是否启动消费 true/false
PerCoroutineCnt: 2 #协程数量不配置默认为20
order:
topic: voucher_order_create
group: voucher_order_create_group
isOpenConsumer: false #是否启动消费 true/false
PerCoroutineCnt: 5 #协程数量不配置默认为20
RetryCnt: 3 #重试次数,不配置默认38
query:
topic: voucher_order_query
group: voucher_order_query_group
isOpenConsumer: false #是否启动消费 true/false
PerCoroutineCnt: 5 #协程数量不配置默认为20
RetryCnt: 3 #重试次数,不配置默认38
notify:
topic: voucher_order_notify
group: voucher_order_notify_group
isOpenConsumer: false #是否启动消费 true/false
PerCoroutineCnt: 5 #协程数量不配置默认为20
RetryCnt: 3 #重试次数,不配置默认38
wechatNotifyMQ:
accessKeyId: "LTAI5tPyV7FynQNTfEvbEBuX"
accessKeySecret: "tZmTh8cV98xAQgtlRU0soWcb6Tpd4T"
endPoint: "http://1389288909295870.mqrest.cn-hangzhou.aliyuncs.com"
regionId: "hangzhou"
instanceId: "MQ_INST_1389288909295870_BYSoMttI"
topic: "notify"
groupId: "GID_voucher"
tag: "voucher_notify_dev"
isOpenConsumer: true #是否启动消费 true/false
registerTagUrl: "https://wpcallbacks.api.1688sup.com/wechatPay/register_tag"
wechat:
mchID: "1710953361" # 证书所属商户 蓝色兄弟服务商立减金配置
mchCertificateSerialNumber: "6006B8208815DB5EAC5BF2E783CB9D34082C3772" #商户证书序列号
wechatPayPublicKeyID: "PUB_KEY_ID_0117109533612025031800326400002563" #微信支付公钥ID
cmb:
sm2Prk: "8d39ff3d2559258c163f4510f082727f51531e1953ab203d5ab1ea4a6d94fd73"
sm2Puk: "04d827a7dbaaa358ce45b8c7794a7f54819f5c175005a702370e47f135ef6f5f9732758b1474f218419fe9e87f90c28c3b05f08254c651db27df35fae67b77b2e4" # 公钥,给到招行密钥
# cmbSm2Pik: "" # 招行私钥mock使用生产不会有该值
# cmbSm2Puk: "0416445bc16cbf42e47002ad9fe7c7af67d902b48be1eb69b98f6a006b0918630e1127f5f2fff83b2ecb30fc7fd72c34c33f37c7c355dffde3589f66800f0036ca" # 招行公钥
cmbSm2Pik: "f6a8d2f412e289686aba6a0f33cad1a64367d0ba012046ee0fbbefd3ffd675bd" # 招行私钥mock使用生产不会有该值
cmbSm2Puk: "043b2fade30067b6bd8e61b42771b1e953116fc5a0f9ed6939fceb9254b8d7d6989902c913642c3c68c42a2b56364512675ea0b517dd4469e73b73c888a2f4e8e3" # 招行公钥-mock使用
mid: "d6fdd78b6fd13a808818286b9cad9687"
aid: "5efaa21263b94f669a1c90ed0279df20"
keyAlias: "CO_PUB_KEY_SM2"
cmbKeyAlias: "SM2_CMBLIFE"
orgNo: "LANSEXIONGDI" # 发码机构号,固定值,掌上生活优惠券系统提供
notifyUrl: "https://sandbox.cdcc.cmbchina.com/AccessGateway/transIn/updateCodeStatus.json" # 招行测试回调地址
noticeStartDays: 7
noticeEndDays: 1
#告警配置
alarm:
webhookURL: "https://oapi.dingtalk.com/robot/send?access_token=5f10c2213cbf8168985cb2d061ebb1a5f70bd1dd47ec7cef58fa6fe545d52588"
secret: "SEC77b63d70a9e22317144e712b4538ce1e0013db885c65f7f9bae283e8958b39eb"
isAll: false
atMobiles:
- "18666173766"
warningMobiles:
- "13474987525"
cron:
isOpen: true #是否启动,控制全局
commandMap:
orderNotice:
isOpen: true #是否启动 true/false
command: "0 0 1 * * ?" # 每天凌晨1点执行一次
warningBudget:
isOpen: true #是否启动 true/false
command: "0 */5 * * * ?" #cron表达式,每5分钟执行一次
rdsMQ:
wechatQuery:
name: "wechatQuery"
retryNum: 1 #重试次数
numWorkers: 2 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
wechatTimeSliceQuery:
name: "wechatTimeSliceQuery"
retryNum: 1 #重试次数
numWorkers: 3 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
orderRetry:
name: "orderRetry"
retryNum: 1 #重试次数
numWorkers: 2 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
retryNotify:
name: "retryNotify"
retryNum: 1 #重试次数
numWorkers: 1 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
aliYunSms:
accessKeyId:
accessKeySecret:
endpoint: dysmsapi.aliyuncs.com
signName: 蓝色兄弟
templateWarning: "SMS_489660721"
#配置日志
logs:

View File

@ -1,117 +0,0 @@
server:
http:
addr: 0.0.0.0:13000
timeout: 30s
isOpenSwagger: true #是否开启api文档仅用于开发、测试线上禁止启用
isResponseReqHeaders: true #是否开启响应请求头,仅用于开发调试,线上禁止启用
data:
db:
driver: mysql
source: root:lansexiongdi6,@tcp(47.97.27.195:3306)/voucher?parseTime=True&loc=Local
maxIdle: 200 #最大的空闲连接数
maxOpen: 1000 #最大连接数0表示不受限制
maxLifetime: 300s #连接复用的最大生命周期
isDebug: false
redis: #没有则注释此属性
addr: 47.97.27.195:6379
password: lansexiongdi@666
readTimeout: 5s
writeTimeout: 5s
poolSize: 200 #连接池大小不配置或配置为0表示不启用连接池
minIdleConns: 50 #最小空闲连接数
connMaxIdleTime: 60s #每个连接最大空闲时间,如果超过了这个时间会被关闭
db: 3
rocketMQ:
addr: "http://rmq-cn-nwy3fn4ex09.cn-chengdu.rmq.aliyuncs.com:8080"
accessKey: "Qecl4cea2IAZPKoD"
secretKey: "Z3596KCFA9RAUR6k"
secretToken: ""
eventMap:
notifyRetry: # 重试延迟队列
topic: voucher_order_notifyRetry
group: voucher_order_notifyRetry_group
isOpenConsumer: true #是否启动消费 true/false
PerCoroutineCnt: 2 #协程数量不配置默认为20
RetryCnt: 3 #重试次数,不配置默认38
wechatNotifyMQ:
accessKeyId: "LTAI5tPyV7FynQNTfEvbEBuX"
accessKeySecret: "tZmTh8cV98xAQgtlRU0soWcb6Tpd4T"
endPoint: "http://1389288909295870.mqrest.cn-hangzhou.aliyuncs.com"
regionId: "hangzhou"
instanceId: "MQ_INST_1389288909295870_BYSoMttI"
topic: "notify"
groupId: "GID_voucher"
tag: "voucher_notify_dev"
isOpenConsumer: true #是否启动消费 true/false
registerTagUrl: "https://wpcallbacks.api.1688sup.com/wechatPay/register_tag"
wechat:
mchID: "1710953361" # 证书所属商户 蓝色兄弟服务商立减金配置
mchCertificateSerialNumber: "6006B8208815DB5EAC5BF2E783CB9D34082C3772" #商户证书序列号
wechatPayPublicKeyID: "PUB_KEY_ID_0117109533612025031800326400002563" #微信支付公钥ID
cmb:
sm2Prk: "8d39ff3d2559258c163f4510f082727f51531e1953ab203d5ab1ea4a6d94fd73"
sm2Puk: "04d827a7dbaaa358ce45b8c7794a7f54819f5c175005a702370e47f135ef6f5f9732758b1474f218419fe9e87f90c28c3b05f08254c651db27df35fae67b77b2e4" # 公钥,给到招行密钥
# cmbSm2Pik: "" # 招行私钥mock使用生产不会有该值
# cmbSm2Puk: "0416445bc16cbf42e47002ad9fe7c7af67d902b48be1eb69b98f6a006b0918630e1127f5f2fff83b2ecb30fc7fd72c34c33f37c7c355dffde3589f66800f0036ca" # 招行公钥
cmbSm2Pik: "f6a8d2f412e289686aba6a0f33cad1a64367d0ba012046ee0fbbefd3ffd675bd" # 招行私钥mock使用生产不会有该值
cmbSm2Puk: "043b2fade30067b6bd8e61b42771b1e953116fc5a0f9ed6939fceb9254b8d7d6989902c913642c3c68c42a2b56364512675ea0b517dd4469e73b73c888a2f4e8e3" # 招行公钥-mock使用
mid: "d6fdd78b6fd13a808818286b9cad9687"
aid: "5efaa21263b94f669a1c90ed0279df20"
keyAlias: "CO_PUB_KEY_SM2"
cmbKeyAlias: "SM2_CMBLIFE"
orgNo: "LANSEXIONGDI" # 发码机构号,固定值,掌上生活优惠券系统提供
notifyUrl: "https://sandbox.cdcc.cmbchina.com/AccessGateway/transIn/updateCodeStatus.json" # 招行测试回调地址
noticeStartDays: 7
noticeEndDays: 1
#告警配置
alarm:
webhookURL: "https://oapi.dingtalk.com/robot/send?access_token=5f10c2213cbf8168985cb2d061ebb1a5f70bd1dd47ec7cef58fa6fe545d52588"
secret: "SEC77b63d70a9e22317144e712b4538ce1e0013db885c65f7f9bae283e8958b39eb"
isAll: false
atMobiles:
- "18666173766"
cron:
isOpen: true #是否启动,控制全局
commandMap:
orderNotice:
isOpen: true #是否启动 true/false
command: "0 0 1 * * ?" # 每天凌晨1点执行一次
rdsMQ:
wechatQuery: #发放结算
name: "wechatQuery"
retryNum: 1 #重试次数
numWorkers: 1 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: true #是否启动消费 true/false
wechatTimeSliceQuery:
name: "wechatTimeSliceQuery"
retryNum: 1 #重试次数
numWorkers: 3 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: true #是否启动消费 true/false
retryNotify:
name: "retryNotify"
retryNum: 1 #重试次数
numWorkers: 1 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
aliYunSms:
accessKeyId: LTAI5tM1X4HuqUwT8D74qXAH
accessKeySecret: gxsC1QK12NSKH1HkCqKR1EnMdAy3ad
endpoint: dysmsapi.aliyuncs.com
signName: 蓝色兄弟
templateWarning: "SMS_489660721"
#配置日志
logs:
business: business.log #业务日志路径:如果不写日志,则不配置或配置为空
access: access.log #访问日志路径:如果不写日志,则不配置或配置为空

View File

@ -1,37 +0,0 @@
#!/bin/bash
CI_COMMIT_ID=$1
server="voucher"
image="voucher"
http_port=13000
docker pull registry.cn-chengdu.aliyuncs.com/lsxdjr/$image:"${CI_COMMIT_ID}"
docker stop $server && docker rm $server
# docker run
docker run --network=merketing-network -itd --name $server --restart=always \
-p $http_port:13000 \
-v /var/www/marketing/cert:/app/cert \
-v /var/www/marketing/templates:/app/templates \
registry.cn-chengdu.aliyuncs.com/lsxdjr/$image:"${CI_COMMIT_ID}" \
./server -nacosIp "47.110.74.203" -nacosPort 8848 -nacosSpace "voucher" -nacosUsername "" -nacosPassword ""
docker image prune -f
docker container prune -f
#测试 /var/www/marketing/cert
#压测环境 /var/www/cert
#线上 /var/www/cert
docker run -itd --name "voucher" --restart=always \
-p 13000:13000 \
-v /var/www/cert:/app/cert \
registry.cn-chengdu.aliyuncs.com/lsxdjr/voucher-pre:50bb64d6 \
./server -nacosIp "172.16.0.239" -nacosPort 8848 -nacosSpace "pre" -nacosUsername "nacos" -nacosPassword "nacos"
# mysql
docker run -itd --name "mysql" --restart=always \
-p 3306:3306 \
-v /data/mysql/data:/var/lib/mysql:rw \
-v /data/mysql/mysql/conf:/etc/mysql/conf.d:rw \
-v /data/mysql/mysql/logs:/logs:rw \
mysql:8.0.27

View File

@ -1 +0,0 @@
docker run -d --name nginx -p 80:80 -p 443:443 -p 9200:9200 -v /var/www/web/vhost:/etc/nginx/conf.d -v /var/www/web/sites:/usr/share/nginx/html nginx

View File

@ -1,39 +0,0 @@
upstream voucherServer{
least_conn;
# 第一个后端服务器
server 172.29.42.89:9200;
# 新增第二个后端服务器
server 172.29.42.90:9200;
}
server {
listen 443;
listen 80;
listen [::]:80;
server_name voucher.86698.cn;
ssl_certificate /etc/nginx/conf/certs/voucher.pem;
ssl_certificate_key /etc/nginx/conf/certs/voucher.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html/web;
index index.html index.html;
try_files $uri $uri/ /index.html;
}
location /voucher {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header referer $http_referer;
proxy_pass http://voucherServer;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -1,31 +0,0 @@
upstream voucher{
# 第一个后端服务器
server 172.29.42.89:13000 backup;
# 新增第二个后端服务器
server 172.29.42.90:13000;
}
server {
listen 9200;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
root /usr/share/nginx/html/web;
index index.html index.html;
try_files $uri $uri/ /index.html;
}
location /voucher {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header referer $http_referer;
proxy_pass http://voucher;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -1,32 +0,0 @@
upstream voucher{
# 第一个后端服务器
server 172.29.42.89:13000;
# 新增第二个后端服务器
server 172.29.42.90:13000 backup;
}
server {
listen 9200;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
root /usr/share/nginx/html/web;
index index.html index.html;
try_files $uri $uri/ /index.html;
}
location /voucher {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header referer $http_referer;
proxy_pass http://voucher;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

55
go.mod
View File

@ -3,99 +3,78 @@ module voucher
go 1.22.2
require (
gitea.cdlsxd.cn/self-tools/l_crypt v1.0.0
github.com/ZZMarquis/gm v1.3.2
github.com/aliyunmq/mq-http-go-sdk v1.0.3
github.com/apache/rocketmq-client-go/v2 v2.1.2
github.com/brianvoe/gofakeit/v6 v6.28.0
github.com/bwmarrin/snowflake v0.3.0
github.com/duke-git/lancet/v2 v2.2.8
github.com/emmansun/gmsm v0.29.8
github.com/envoyproxy/protoc-gen-validate v1.0.4
github.com/go-kratos/kratos/contrib/config/nacos/v2 v2.0.0-20241105072421-f8b97f675b32
github.com/go-kratos/kratos/v2 v2.8.2
github.com/go-playground/validator/v10 v10.25.0
github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9
github.com/gogf/gf v1.16.9
github.com/google/wire v0.6.0
github.com/gorilla/handlers v1.5.1
github.com/nacos-group/nacos-sdk-go v1.1.4
github.com/pkg/errors v0.9.1
github.com/redis/go-redis/v9 v9.1.0
github.com/robfig/cron v1.2.0
github.com/tjfoc/gmsm v1.4.1
github.com/wechatpay-apiv3/wechatpay-go v0.2.20
go.opentelemetry.io/otel v1.28.0
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
go.opentelemetry.io/otel/sdk v1.28.0
go.opentelemetry.io/otel/trace v1.28.0
go.uber.org/automaxprocs v1.5.1
golang.org/x/time v0.5.0
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v2 v2.4.0
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
gorm.io/hints v1.1.2
)
require (
dario.cat/mergo v1.0.0 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-kratos/aegis v0.2.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/assert/v2 v2.2.0 // indirect
github.com/go-playground/form/v4 v4.2.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8 // indirect
github.com/golang/mock v1.3.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/sirupsen/logrus v1.4.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/smartystreets/assertions v1.1.0 // indirect
github.com/tidwall/gjson v1.13.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.59.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.uber.org/atomic v1.6.0 // indirect
go.uber.org/multierr v1.5.0 // indirect
go.uber.org/zap v1.15.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/grpc v1.61.1 // indirect
gopkg.in/ini.v1 v1.42.0 // indirect
golang.org/x/lint v0.0.0-20241112194109-818c5a804067 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/grpc v1.64.0 // indirect
gopkg.in/ini.v1 v1.56.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
stathat.com/c/consistent v1.0.0 // indirect

157
go.sum
View File

@ -1,21 +1,10 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
gitea.cdlsxd.cn/self-tools/l_crypt v1.0.0 h1:U0SBdxgrWNmbzPjEMuAtA51GeQ8PpRoZgN+SNoyqrQM=
gitea.cdlsxd.cn/self-tools/l_crypt v1.0.0/go.mod h1:Q7sEfyYLXGZ7i97HBJ7OaYLYyc4f9scUJK9Ev3ZGyDM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/ZZMarquis/gm v1.3.2 h1:lFtpzg5zeeVMZ/gKi0gtYcKLBEo9XTqsZDHDz6s3Gow=
github.com/ZZMarquis/gm v1.3.2/go.mod h1:wWbjZYgruQVd7Bb8UkSN8ujU931kx2XUW6nZLCiDE0Q=
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/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
github.com/aliyunmq/mq-http-go-sdk v1.0.3 h1:/uhH7DUoaw9XTtsPgDp7zdPUyG5FBKj2GmJJph9z+6o=
github.com/aliyunmq/mq-http-go-sdk v1.0.3/go.mod h1:JYfRMQoPexERvnNNBcal0ZQ2TVQ5ialDiW9ScjaadEM=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/apache/rocketmq-client-go/v2 v2.1.2 h1:yt73olKe5N6894Dbm+ojRf/JPiP0cxfDNNffKwhpJVg=
github.com/apache/rocketmq-client-go/v2 v2.1.2/go.mod h1:6I6vgxHR3hzrvn+6n/4mrhS+UTulzK/X9LB2Vk1U5gE=
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
@ -26,9 +15,6 @@ github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -37,8 +23,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -49,15 +33,8 @@ github.com/duke-git/lancet/v2 v2.2.8 h1:wlruXhliDe4zls1e2cYmz4qLc+WtcvrpcCnk1VJd
github.com/duke-git/lancet/v2 v2.2.8/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/emmansun/gmsm v0.29.8 h1:py9RwKHe4sIxjgD9mtWSIF5bmspP7IXvQ70Rx8x22Ow=
github.com/emmansun/gmsm v0.29.8/go.mod h1:7UTFG3GmF8yxyZVB4HgpdeU0JoergL/i2OMGm5w02RA=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
@ -66,8 +43,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ=
@ -86,31 +61,17 @@ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lY
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9 h1:qvGIRaCYFKkyFK9SgRXJCc/lmQCeeg2cl3mwBKQd5W0=
github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9/go.mod h1:tbRYYYC7g/H7QlCeX0Z2zaThWKowF4QQCFIsGgAsqRo=
github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8 h1:AuxION6c7in+AsPmFjQTUKT6/o1suT8XEEpfU0pWsHA=
github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8/go.mod h1:6q1WEv2BiAO4FSdwLQTJbWQYAn1/qDNJHUGJNXCj9kM=
github.com/gogf/gf v1.16.9 h1:Q803UmmRo59+Ws08sMVFOcd8oNpkSWL9vS33hlo/Cyk=
github.com/gogf/gf v1.16.9/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -119,8 +80,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -134,15 +93,14 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
@ -152,19 +110,16 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -173,8 +128,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -182,20 +135,15 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -226,27 +174,22 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY=
github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -254,9 +197,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=
@ -265,18 +205,6 @@ 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 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
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/wechatpay-apiv3/wechatpay-go v0.2.21 h1:uIyMpzvcaHA33W/QPtHstccw+X52HO1gFdvVL9O6Lfs=
github.com/wechatpay-apiv3/wechatpay-go v0.2.21/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
@ -307,20 +235,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20241112194109-818c5a804067 h1:adDmSQyFTCiv19j015EGKJBoaa7ElV0Q1Wovb/4G7NA=
golang.org/x/lint v0.0.0-20241112194109-818c5a804067/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -329,17 +250,13 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
@ -349,22 +266,17 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -389,17 +301,14 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -408,18 +317,12 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.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-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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -430,31 +333,17 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -472,8 +361,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
@ -490,17 +379,9 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o=
gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=

31
gorm.sh
View File

@ -101,7 +101,7 @@ echo "领域实体Bo代码生成完成${bo_file}"
# 生成 CRUD 操作代码部分
echo "正在生成 CRUD 操作代码..."
repo_file="${repo_path}/${table}.go"
repo_file="${repo_path}/${table}_repo.go"
mkdir -p "${repo_path}"
cat > "${repo_file}" <<EOL
package repo
@ -120,7 +120,7 @@ type ${table_capitalized}Repo interface {
EOL
crud_file="${repo_impl_path}/${table}.go"
crud_file="${repo_impl_path}/${table}_repoImpl.go"
mkdir -p "${repo_impl_path}"
# 使用heredoc方式向文件中写入CRUD操作代码定义了针对指定表的各种数据库操作方法包括创建、根据ID获取、更新、删除、列表查询以及按字段查询等功能
@ -130,27 +130,20 @@ package repoimpl
import (
"context"
"voucher/internal/data/model"
"voucher/internal/biz/repo"
"voucher/internal/biz/repository"
"voucher/internal/biz/bo"
"voucher/internal/data"
err2 "voucher/api/err"
)
// ${table_capitalized}RepoImpl .
// ${table_capitalized}RepoImpl 定义了针对 ${table_capitalized} 表的 CRUD 操作
type ${table_capitalized}RepoImpl struct {
Base[model.${table_capitalized}, bo.${table_capitalized}Bo]
db *data.Db
}
// New${table_capitalized}RepoImpl .
func New${table_capitalized}RepoImpl() repo.${table_capitalized}Repo {
// New${table_capitalized}RepoImpl 创建一个新的 ${table_capitalized}RepoImpl 实例
func New${table_capitalized}RepoImpl() repository.${table_capitalized}Repo {
return &${table_capitalized}RepoImpl{}
}
func (r *${table_capitalized}RepoImpl) DB(ctx context.Context) *gorm.DB {
return p.db.DB(ctx).WithContext(ctx).Model(model.${table_capitalized}{})
}
func (r *${table_capitalized}RepoImpl) Create(ctx context.Context, req *bo.${table_capitalized}Bo) (*bo.${table_capitalized}Bo, error) {
// todo 待实现
return nil, nil
@ -159,17 +152,7 @@ func (r *${table_capitalized}RepoImpl) Create(ctx context.Context, req *bo.${tab
// GetByID 根据 ID 获取 ${table_capitalized}
func (r *${table_capitalized}RepoImpl) GetByID(ctx context.Context,id int32) (*bo.${table_capitalized}Bo, error) {
var item model.${table_capitalized}
tx := p.DB(ctx).Where(model.${table_capitalized}{ID: id}).First(&item)
if tx.Error != nil {
return nil, fmt.Errorf("b fail %w", tx.Error)
}
if tx.RowsAffected == 0 {
return nil, err2.ErrorDbNotFound("数据不存在")
}
// todo 待实现
return r.ToBo(&item), nil
}
EOL

View File

@ -1,79 +0,0 @@
package biz
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
"strings"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error {
// 1小时 内 指定的批次号 发放 发生错误 预警
c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo})
_, err := this.rdb.Rdb.Get(ctx, c.Key).Result()
if err == nil {
// 缓存存在,直接返回
return nil
}
if err != redis.Nil {
return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err))
}
cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo})
return lock.NewMutex(this.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
// 二次获取,判定处理,以免获取锁后又执行了一次
cacheValue, err3 := this.rdb.Rdb.Get(ctx, c.Key).Result()
if err3 != nil && err3 != redis.Nil {
return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err))
}
if len(cacheValue) > 0 {
return nil // 有直接返回
}
// 通知
title := fmt.Sprintf("券发放异常")
if err = this.DingMixRepo.SendMarkdownMessage(ctx, title, alarmText(order, errMsg)); err != nil {
return err
}
if err = this.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil {
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
}
return nil
})
}
func alarmText(order *bo.OrderBo, errMsg string) string {
var card strings.Builder
// 警示信息(简化版)
card.WriteString("⚠️ **券发放异常** ⚠️\n\n")
// 订单信息(通过引用和代码块模拟小字体)
card.WriteString("> **订单信息:**\n\n")
card.WriteString(fmt.Sprintf("> AppID: `%s`\n\n", order.AppID))
card.WriteString(fmt.Sprintf("> OpenID: `%s`\n\n", order.Account))
card.WriteString(fmt.Sprintf("> 批次号: `%s`\n\n", order.BatchNo))
card.WriteString(fmt.Sprintf("> 活动号: `%s`\n\n", order.ProductNo))
card.WriteString(fmt.Sprintf("> 订单号: `%s`\n\n", order.OrderNo))
card.WriteString(fmt.Sprintf("> 招行订单号: `%s`\n", order.OutBizNo))
card.WriteString("\n")
// 报警原因(通过引用和代码块模拟小字体)
card.WriteString("> **❗ 报警原因:**\n\n")
card.WriteString(fmt.Sprintf("> `%s`\n", errMsg))
return card.String()
}

View File

@ -1,14 +0,0 @@
package bo
import "voucher/internal/biz/vo"
type CmbRequestBo struct {
FuncName vo.CmbFuncName
BizContent string
}
type CmbResponseBo struct {
RespCode string
RespMsg string
BizContent string
}

View File

@ -1,57 +1,18 @@
package bo
import (
"time"
"voucher/internal/biz/vo"
)
import "time"
// OrderBo 领域实体Bo结构字段和模型字段保持一致
type OrderBo struct {
ID uint64
ID int32
OrderNo string
OutBizNo string
VoucherNo string
ProductNo string
BatchNo string
ActivityId string
Account string
Type vo.OrderType
AccountType vo.OrderAccountType
Status vo.OrderStatus
AccountType bool
Status bool
AppID string
MerchantNo string
NotifyUrl string
Channel vo.Channel
Attach string
Remark string
TransactionId string
ReceiveSuccessTime *time.Time
LastUseTime *time.Time
CreateTime *time.Time
UpdateTime *time.Time
}
type OrderCreateReqBo struct {
OutBizNo string
ProductNo string
Account string
AppID string
Type vo.OrderType
AccountType vo.OrderAccountType
NotifyUrl string
Attach string
}
type FindInBatchesUseBo struct {
StartTime *time.Time
EndTime *time.Time
}
type FindInBatchesBo struct {
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
ProductNo string `json:"product_no"`
OrderNos []string `json:"order_nos"`
OutBizNos []string `json:"out_biz_nos"`
VoucherNos []string `json:"voucher_nos"`
Channel bool
CreateTime time.Time
}

View File

@ -1,22 +0,0 @@
package bo
import (
"time"
"voucher/internal/biz/vo"
)
// OrderNotifyBo 领域实体Bo结构字段和模型字段保持一致
type OrderNotifyBo struct {
ID uint64
OrderNo string
Status vo.OrderNotifyStatus
Request string
Event vo.OrderNotifyEvent
Channel vo.Channel
Type vo.OrderType
Responses string
Remark string
NotifyUrl string
CreateTime *time.Time
UpdateTime *time.Time
}

View File

@ -1,29 +0,0 @@
package bo
import (
"time"
"voucher/internal/biz/vo"
)
// ProductBo 领域实体Bo结构字段和模型字段保持一致
type ProductBo struct {
ID int32
Name string
ProductNo string
BatchName string
BatchNo string
ActivityId string
MchId string
Channel vo.Channel
AvailableType vo.AvailableType
AvailableDays uint32
Amount int64
AllBudget int64
AvailableBudget int64
WarningBudget int64
WarningPerson string
StartTime *time.Time
EndTime *time.Time
CreateTime *time.Time
UpdateTime *time.Time
}

View File

@ -1,18 +0,0 @@
package bo
import (
"time"
"voucher/internal/biz/vo"
)
// WechatNotifyRegisterTagBo 领域实体Bo结构字段和模型字段保持一致
type WechatNotifyRegisterTagBo struct {
ID int32
StockID string
StockCreatorMchID string
Tag string
Status vo.WechatNotifyRegisterTagStatus
Remark string
CreateTime *time.Time
UpdateTime *time.Time
}

View File

@ -1,36 +0,0 @@
package bo
import "voucher/internal/biz/vo"
// ConsumeInformation 定义消费信息结构体
type ConsumeInformation struct {
ConsumeTime string `json:"consume_time"`
ConsumeMchid string `json:"consume_mchid"`
TransactionID string `json:"transaction_id"`
}
// PlainText 定义明文数据结构体
type PlainText struct {
StockCreatorMchid string `json:"stock_creator_mchid"`
StockID string `json:"stock_id"`
CouponID string `json:"coupon_id"`
CouponName string `json:"coupon_name"`
Description string `json:"description"`
Status vo.WechatVoucherStatus `json:"status"`
CreateTime string `json:"create_time"`
CouponType string `json:"coupon_type"`
NoCash bool `json:"no_cash"`
Singleitem bool `json:"singleitem"`
ConsumeInformation ConsumeInformation `json:"consume_information,omitempty"`
}
type WechatVoucherNotifyBo struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
ResourceType string `json:"resource_type"`
EventType string `json:"event_type"`
Summary string `json:"summary"`
OriginalType string `json:"original_type"`
AssociatedData string `json:"associated_data"`
PlainText PlainText `json:"plain_text"`
}

View File

@ -1,42 +0,0 @@
package cmb
import (
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/repo"
"voucher/internal/biz/wechatrepo"
"voucher/internal/conf"
"voucher/internal/data"
)
// Cmb .
// @link https://open.cmbchina.com/Platform/#/resource/document/approvalAPI
// @link https://open.cmbchina.com/Platform/#/resource/document/guide
type Cmb struct {
bc *conf.Bootstrap
rdb *data.Rdb
OrderRepo repo.OrderRepo
ProductRepo repo.ProductRepo
OrderNotifyRepo repo.OrderNotifyRepo
WechatCpnRepo wechatrepo.WechatCpnRepo
CmbMixRepo mixrepos.CmbMixRepo
}
func NewCmb(
bc *conf.Bootstrap,
rdb *data.Rdb,
orderRepo repo.OrderRepo,
ProductRepo repo.ProductRepo,
OrderNotifyRepo repo.OrderNotifyRepo,
WechatCpnRepo wechatrepo.WechatCpnRepo,
CmbMixRepo mixrepos.CmbMixRepo,
) *Cmb {
return &Cmb{
bc: bc,
rdb: rdb,
OrderRepo: orderRepo,
ProductRepo: ProductRepo,
OrderNotifyRepo: OrderNotifyRepo,
WechatCpnRepo: WechatCpnRepo,
CmbMixRepo: CmbMixRepo,
}
}

View File

@ -1,148 +0,0 @@
package cmb
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"time"
err2 "voucher/api/err"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
func (v *Cmb) Notify(ctx context.Context, order *bo.OrderBo) (*bo.OrderNotifyBo, error) {
if !order.Status.IsCanNotify() {
return nil, fmt.Errorf("订单状态不允许通知,orderNo:%s,orderStatusText:%s", order.OrderNo, order.Status.GetText())
}
event, err := order.Status.GetOrderNotifyEvent()
if err != nil {
return nil, err
}
req := &bo.OrderNotifyBo{
OrderNo: order.OrderNo,
NotifyUrl: order.NotifyUrl,
Channel: order.Channel,
Event: event,
Type: order.Type,
}
request, orderNotify, err := v.notifyCreate(ctx, order, req)
if err != nil {
return nil, err
}
reply, err := v.CmbMixRepo.Request(ctx, request, order.NotifyUrl)
if err != nil {
return orderNotify, v.notifyFail(ctx, orderNotify.ID, err.Error())
}
if err = v.CmbMixRepo.VerifyResponse(ctx, reply); err != nil {
log.Errorf("回调通知招行返回验证结果发生错误,rep:%+v error:%s", reply, err.Error())
return orderNotify, v.notifyFail(ctx, orderNotify.ID, err.Error())
}
if reply.RespCode == vo.CmbResponseStatusSuccess.GetValue() {
replyJson, _ := json.Marshal(reply)
return orderNotify, v.notifySuccess(ctx, orderNotify.ID, string(replyJson))
}
return orderNotify, v.notifyFail(ctx, orderNotify.ID, reply.RespMsg)
}
func (v *Cmb) bizContent(_ context.Context, order *bo.OrderBo, orderNotify *bo.OrderNotifyBo) (string, error) {
cmbStatus, err := orderNotify.Event.GetCmbStatusText()
if err != nil {
return "", err
}
req := &v1.CmbNotifyRequest{
Ticket: orderNotify.OrderNo,
Status: cmbStatus.GetValue(),
TransDate: "", // 格式yyyy-mm-dd hh:mm:ss.sss
OrgNo: v.bc.Cmb.OrgNo,
Attach: order.Attach,
Ext: "",
}
if cmbStatus == vo.CmbStatusUse {
if order.LastUseTime == nil || order.LastUseTime.IsZero() {
req.TransDate = time.Now().Format("2006-01-02 15:04:05.000")
} else {
req.TransDate = order.LastUseTime.Format("2006-01-02 15:04:05.000")
}
} else {
req.TransDate = time.Now().Format("2006-01-02 15:04:05.000")
}
bizJsonBytes, err := json.Marshal(req)
if err != nil {
return "", err
}
return string(bizJsonBytes), nil
}
func (v *Cmb) NotifyRequest(ctx context.Context, order *bo.OrderBo, orderNotify *bo.OrderNotifyBo) (*v1.CmbRequest, error) {
bizContent, err := v.bizContent(ctx, order, orderNotify)
if err != nil {
return nil, err
}
request, err := v.CmbMixRepo.GetRequest(ctx, &bo.CmbRequestBo{
FuncName: vo.CmbNotifyFuncName,
BizContent: bizContent,
})
if err != nil {
return nil, err
}
return request, nil
}
func (v *Cmb) notifyCreate(ctx context.Context, order *bo.OrderBo, req *bo.OrderNotifyBo) (*v1.CmbRequest, *bo.OrderNotifyBo, error) {
request, err := v.NotifyRequest(ctx, order, req)
if err != nil {
return nil, nil, err
}
requestBytes, err := json.Marshal(request)
if err != nil {
return nil, nil, err
}
req.Request = string(requestBytes)
orderNotify, err := v.OrderNotifyRepo.Create(ctx, req)
if err != nil {
return nil, nil, err
}
return request, orderNotify, err
}
func (v *Cmb) notifySuccess(ctx context.Context, notifyId uint64, responses string) error {
if len(responses) == 0 {
responses = "{}"
}
return v.OrderNotifyRepo.Success(ctx, notifyId, responses)
}
func (v *Cmb) notifyFail(ctx context.Context, notifyId uint64, errMsg string) error {
if err := v.OrderNotifyRepo.Fail(ctx, notifyId, errMsg); err != nil {
return err
}
return err2.ErrorNeedRetryNotify(errMsg)
}

View File

@ -1,44 +0,0 @@
package cmb
import (
"context"
"encoding/json"
"github.com/go-kratos/kratos/v2/log"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
func (v *Cmb) NotifyRetryConsume(ctx context.Context, order *bo.OrderBo, orderNotify *bo.OrderNotifyBo) (*bo.OrderNotifyBo, error) {
req := &bo.OrderNotifyBo{
OrderNo: orderNotify.OrderNo,
NotifyUrl: order.NotifyUrl,
Channel: order.Channel,
Event: orderNotify.Event,
Type: order.Type,
Request: "",
}
request, orderNotify, err := v.notifyCreate(ctx, order, req)
if err != nil {
return nil, err
}
reply, err := v.CmbMixRepo.Request(ctx, request, order.NotifyUrl)
if err != nil {
return orderNotify, v.notifyFail(ctx, orderNotify.ID, err.Error())
}
if err = v.CmbMixRepo.VerifyResponse(ctx, reply); err != nil {
log.Errorf("NotifyRetryConsume CmbMixRepo.VerifyResponse error:%s", err.Error())
return orderNotify, v.notifyFail(ctx, orderNotify.ID, err.Error())
}
if reply.RespCode != vo.CmbResponseStatusSuccess.GetValue() {
return orderNotify, v.notifyFail(ctx, orderNotify.ID, reply.RespMsg)
}
replyJson, _ := json.Marshal(reply)
return orderNotify, v.notifySuccess(ctx, orderNotify.ID, string(replyJson))
}

View File

@ -1,8 +0,0 @@
package cmb
import (
"github.com/google/wire"
)
// ProviderSetCmb is biz providers.
var ProviderSetCmb = wire.NewSet(NewCmb)

View File

@ -0,0 +1 @@
package consume

View File

@ -0,0 +1 @@
package consume

View File

@ -0,0 +1 @@
package consume

View File

@ -0,0 +1 @@
package consume

View File

@ -1,241 +0,0 @@
package biz
import (
"context"
"errors"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/redis/go-redis/v9"
"golang.org/x/sync/errgroup"
"runtime"
"time"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) isCanNotice(ctx context.Context) error {
if this.bc.Cmb.NoticeStartDays == 0 {
return errors.New("订单定时通知,noticeStartDays eq 0")
}
if this.bc.Cmb.NoticeEndDays == 0 {
return errors.New("订单定时通知,noticeEndDays eq 0")
}
cache := vo.CmbBatchNoticeCacheKey.BuildCache([]string{""})
_, err := this.rdb.Rdb.Get(ctx, cache.Key).Result()
if err == nil {
return fmt.Errorf("订单定时通知,notice 获取redis缓存存在已被执行,本台服务不做执行")
}
if err != redis.Nil {
return fmt.Errorf(fmt.Sprintf("订单定时通知,notice 获取redis缓存%s异常:%v", cache.Key, err))
}
c := vo.CmbBatchNoticeLockKey.BuildCache([]string{""})
return lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
// 二次获取,判定处理,以免获取锁后又执行了一次
cacheValue, err2 := this.rdb.Rdb.Get(ctx, cache.Key).Result()
if err2 != nil && err2 != redis.Nil {
return fmt.Errorf(fmt.Sprintf("订单定时通知,notice 二次获取redis缓存%s异常:%v", cache.Key, err2))
}
if len(cacheValue) > 0 {
return fmt.Errorf("订单定时通知,notice 二次获取redis缓存存在已被执行,本台服务不做执行")
}
if err = this.rdb.Rdb.Set(ctx, cache.Key, fmt.Sprintf("%d_%d", this.bc.Cmb.NoticeStartDays, this.bc.Cmb.NoticeEndDays), c.TTL).Err(); err != nil {
return fmt.Errorf(fmt.Sprintf("notice 设置redis缓存%s异常:%v", cache.Key, err))
}
log.Warnf("订单定时通知,notice 获取redis缓存,不存在,开始处理")
return nil
})
}
func (this *VoucherBiz) Notice(ctx context.Context) error {
if err := this.isCanNotice(ctx); err != nil {
return err
}
now := time.Now()
// 获取 NoticeStartDays 天前的日期
noticeStartDay := now.AddDate(0, 0, int(-this.bc.Cmb.NoticeStartDays))
// 获取 NoticeStartDays 天前 00:00:00 的时间
startTime := time.Date(noticeStartDay.Year(), noticeStartDay.Month(), noticeStartDay.Day(), 0, 0, 0, 0, noticeStartDay.Location())
// 获取 NoticeEndDays 天前的日期
noticeEndDay := now.AddDate(0, 0, int(-this.bc.Cmb.NoticeEndDays))
// 获取 NoticeEndDays 天 23:59:59 的时间
endTime := time.Date(noticeEndDay.Year(), noticeEndDay.Month(), noticeEndDay.Day(), 23, 59, 59, 0, noticeEndDay.Location())
return this.timeSliceQuery(ctx, startTime, endTime)
}
func (this *VoucherBiz) timeSliceQuery(ctx context.Context, startTime, endTime time.Time) error {
log.Warnf("订单定时通知,开始处理,按每两个小时分片处理,范围:%s到%s", startTime.Format(time.DateTime), endTime.Format(time.DateTime))
duration := 2 * time.Hour
eg := new(errgroup.Group)
eg.SetLimit(10)
for start := startTime; start.Before(endTime); start = start.Add(duration) {
end := start.Add(duration) // 计算每次请求的结束时间
if end.After(endTime) {
end = endTime
} else {
end = end.Add(-1 * time.Second)
}
eg.Go(func() error {
req := &bo.FindInBatchesUseBo{
StartTime: &start,
EndTime: &end,
}
defer func() {
if err := recover(); err != nil {
_, file, line, _ := runtime.Caller(1) // 1 表示获取当前调用者的调用信息
log.Errorf("订单定时通知,发生错误:req:%+v,err:%v,file:%s,line:%d", req, err, file, line)
}
}()
return this.ExecuteNotice(ctx, req)
})
}
return eg.Wait() // 仅返回第一个错误
}
func (this *VoucherBiz) ExecuteNotice(ctx context.Context, req *bo.FindInBatchesUseBo) error {
start := time.Now()
num := 0
useNum := 0
sucNum := 0
err := this.OrderRepo.FindInBatches(ctx, req, func(ctx context.Context, rows []*bo.OrderBo) error {
for _, order := range rows {
num += 1
if err := this.notice(ctx, order, &useNum, &sucNum); err != nil {
log.Errorf("订单定时通知,err:%v", err)
}
}
return nil
})
logFields := map[string]interface{}{
"sTime": req.StartTime.Format(time.DateTime) + "到" + req.EndTime.Format(time.DateTime),
"num": num, // 查询总量
"useNum": useNum, // 核销通知数量
"sucNum": sucNum, // 重置为成功数量
"elapsed": time.Now().Sub(start).String(),
}
log.Warnf("订单定时通知,%+v", logFields)
return err
}
func (this *VoucherBiz) notice(ctx context.Context, order *bo.OrderBo, useNum, sucNum *int) (respErr error) {
// 批量通知不做数据存储,量会很大
status, err := this.WechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if order.Status == status {
return nil // 券状态未改变,忽略不处理
}
order.Status = status
event, err := status.GetOrderNotifyEvent()
if err != nil {
return err
}
notify := &bo.OrderNotifyBo{
OrderNo: order.OrderNo,
NotifyUrl: order.NotifyUrl,
Channel: order.Channel,
Event: event,
Type: order.Type,
}
if err = this.request(ctx, order, notify); err != nil {
return err
}
if err = this.UpdateOrderStatus(ctx, order.ID, status); err != nil {
return err
}
if event.IsUsed() {
*useNum += 1
} else if event.IsSendDEd() || event.IsExpired() {
*sucNum += 1
}
return nil
}
func (this *VoucherBiz) request(ctx context.Context, order *bo.OrderBo, notify *bo.OrderNotifyBo) (respErr error) {
defer func() {
if err := recover(); err != nil {
// 打印堆栈信息
stackBuf := make([]byte, 1024)
stackSize := runtime.Stack(stackBuf, false)
// 获取调用栈信息
_, file, line, _ := runtime.Caller(1) // 1 表示获取当前调用者的调用信息
respErr = fmt.Errorf("request panic:%v, orderNo:%s, file:%s, line:%d, stack: %s", err, order.OrderNo, file, line, stackBuf[:stackSize])
}
}()
if order == nil {
return fmt.Errorf("request order is nil")
}
if notify == nil {
return fmt.Errorf("notify is nil")
}
if !notify.Event.CanNotify() {
return nil // 不可通知,忽略
}
request, err := this.Cmb.NotifyRequest(ctx, order, notify)
if err != nil {
return err
}
if request == nil {
return fmt.Errorf("request is nil,orderNo:%s,outBizNo:%s,stockId:%s", order.OrderNo, order.OutBizNo, order.BatchNo)
}
if _, err = this.CmbMixRepo.Request(ctx, request, order.NotifyUrl); err != nil {
return fmt.Errorf("orderNo:%s,outBizNo:%s,stockId:%s,err:%s", order.OrderNo, order.OutBizNo, order.BatchNo, err.Error())
}
return nil
}

View File

@ -1,36 +0,0 @@
package do
import "time"
type WxResp struct {
Amount int64 // 券面额
AllBudget int64 // 总预算
AllStock int64 // 总库存
UsedStock int64 // 已使用库存
UsedBudget int64 // 已使用预算
AvailableStock int64 // 可用库存
AvailableBudget int64 // 可用预算
StockUsageRate float64 // 券使用率
StartTime *time.Time
EndTime *time.Time
}
type WarningPerson struct {
Mobile string `json:"mobile"`
Name string `json:"name"`
Tag string `json:"tag"`
}
type WarningBudget struct {
StockName string // 券名称
StockId string // 券ID
StockNo string // 券编号
Amount int64 // 券面额
AllBudget int64 // 总预算
AllStock int64 // 总库存
UsedStock int64 // 已使用库存
UsedBudget int64 // 已使用预算
AvailableStock int64 // 可用库存
RemainingBudget int64 // 剩余预算
StockUsageRate float64 // 券使用率
}

View File

@ -1,18 +0,0 @@
package do
type WechatQuery struct {
ProductNo string `json:"product_no"`
BatchNo string `json:"batch_no"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
OrderNo string `json:"order_no"`
}
type RdsWechatQuery struct {
ProductNo string `json:"product_no"`
BatchNo string `json:"batch_no"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
GoNum int `json:"go_num"` // 并发数
TimeSliceHours int64 `json:"time_slice_hours"` // 时间片"小时"
}

View File

@ -1,28 +0,0 @@
package mixrepos
import (
"context"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
)
type CmbMixRepo interface {
// OrderVerify cmb 券订单参数验证
OrderVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbOrderRequest, error)
// QueryVerify cmb 券订单参数验证
QueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryRequest, error)
// ProductQueryVerify cmb 券产品查询参数验证
ProductQueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryProductRequest, error)
// GetMockRequest cmb 券参数mock构建
GetMockRequest(ctx context.Context, bizContent string) (*v1.CmbRequest, error)
// GetRequest 我们请求cmb参数构建处理
GetRequest(ctx context.Context, reqBo *bo.CmbRequestBo) (*v1.CmbRequest, error)
// GetResponse cmb 请求我们响应返回结果处理
GetResponse(ctx context.Context, reqBo *bo.CmbResponseBo) (*v1.CmbReply, error)
// VerifyResponse cmb 请求响应返回结果验证
VerifyResponse(ctx context.Context, req *v1.CmbReply) error
// Request cmb 请求
Request(ctx context.Context, req *v1.CmbRequest, uri string) (*v1.CmbReply, error)
// Decrypt cmb 业务参数 解密
Decrypt(ctx context.Context, encryptBody string) (string, error)
}

View File

@ -1,8 +0,0 @@
package mixrepos
import "context"
type DingMixRepo interface {
SendMessage(_ context.Context, title, text string) error
SendMarkdownMessage(ctx context.Context, title, text string) error
}

View File

@ -1,9 +0,0 @@
package mixrepos
import "context"
// GenerateMixRepo interface
type GenerateMixRepo interface {
GeneratorString(ctx context.Context, uid string) string
GeneratorNumber(ctx context.Context, uid string) int64
}

View File

@ -1,7 +0,0 @@
package mixrepos
import "context"
type SmsMixRepo interface {
Send(ctx context.Context, phoneNumbers []string, templateCode string, params map[string]string) error
}

View File

@ -1,96 +0,0 @@
package biz
import (
"context"
"fmt"
"go.opentelemetry.io/otel/trace"
"strconv"
errPb "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
"voucher/internal/pkg/mq"
)
func (this *VoucherBiz) PushNotifyRetryDelayMQ(ctx context.Context, level int, orderNotifyId uint64) error {
str := strconv.FormatUint(orderNotifyId, 10)
eventMap := this.bc.RocketMQ.EventMap["notifyRetry"]
sendOption := []mq.SendOption{
mq.WithSendShardingKeysOption(str),
mq.WithOpenTelemetryOption(trace.SpanFromContext(ctx).SpanContext().TraceID().String()),
mq.WithSendDelayLevelOption(level),
}
if err := this.MqSendMixRepo.SendSync(ctx, eventMap.Topic, []byte("{}"), sendOption...); err != nil {
return fmt.Errorf("回调通知延迟队列,投递消息出错err=%s", err.Error())
}
return nil
}
func (this *VoucherBiz) NotifyRetryConsume(ctx context.Context, orderNotifyId uint64) error {
var (
err error
orderNotify *bo.OrderNotifyBo
c = vo.NotifyRetryConsume.BuildCacheUint64([]uint64{orderNotifyId})
)
err = lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
orderNotify, err = this.OrderNotifyRepo.GetByID(ctx, orderNotifyId)
if err != nil {
return err
}
order, err2 := this.OrderRepo.GetByOrderNo(ctx, orderNotify.OrderNo)
if err2 != nil {
return err2
}
if order.Type.IsCmb() {
orderNotify, err2 = this.Cmb.NotifyRetryConsume(ctx, order, orderNotify)
return err2
}
return fmt.Errorf("订单类型错误:%s", order.Type.GetText())
})
if !errPb.IsNeedRetryNotify(err) {
return err
}
level, err2 := this.level(ctx, orderNotify)
if err2 != nil {
return err2
}
return this.PushNotifyRetryDelayMQ(ctx, level, orderNotify.ID)
}
func (this *VoucherBiz) level(ctx context.Context, orderNotify *bo.OrderNotifyBo) (int, error) {
// 状态回调接口失败需要支持重试 重试间隔为1分钟、2分钟、12分钟、60分钟、360分钟
count, err := this.OrderNotifyRepo.GetCountByOrderNoAndEvent(ctx, orderNotify.OrderNo, orderNotify.Event)
if err != nil {
return 0, err
}
switch count {
case 1:
return 60, nil
case 2:
return 120, nil
case 3:
return 720, nil
case 4:
return 3600, nil
case 5:
return 21600, nil
}
return 0, fmt.Errorf("回调通知失败次数超过5次不再重试")
}

View File

@ -1,168 +0,0 @@
package biz
import (
"context"
"fmt"
err2 "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
func (this *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (orderNo string, err error) {
order, err3 := this.GetByOutBizNo(ctx, req)
if err3 != nil {
return "", err3
}
if order != nil {
if order.Status.IsFail() || order.Status.IsIng() {
if err4 := this.orderRetry(ctx, order); err4 != nil {
return "", err4
}
}
return order.OrderNo, err
}
product, err3 := this.ProductRepo.GetByProductNo(ctx, req.ProductNo)
if err3 != nil {
return "", err3
}
order, err3 = this.order(ctx, req, product)
if err3 != nil {
return "", err3
}
return order.OrderNo, nil
}
func (this *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) {
order, err := this.create(ctx, req, product)
if err != nil {
return nil, err
}
voucherNo, err := this.send(ctx, order)
if err != nil {
if err3 := this.fail(ctx, order, err); err3 != nil {
return nil, err3
}
return nil, err
}
if err = this.success(ctx, order, voucherNo); err != nil {
return nil, err
}
return order, nil
}
func (this *VoucherBiz) send(ctx context.Context, order *bo.OrderBo) (string, error) {
if order.ActivityId == "" {
return this.WechatCpnRepo.Order(ctx, order)
}
return this.BankMultiActivityRepo.Order(order)
}
func (this *VoucherBiz) orderRetry(ctx context.Context, order *bo.OrderBo) error {
voucherNo, err := this.send(ctx, order)
if err != nil {
if err3 := this.fail(ctx, order, err); err3 != nil {
return err3
}
return err
}
return this.success(ctx, order, voucherNo)
}
func (this *VoucherBiz) create(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) {
o := &bo.OrderBo{
OrderNo: this.GenerateMixRepo.GeneratorString(ctx, fmt.Sprintf("%d%s", req.Type, req.OutBizNo)),
OutBizNo: req.OutBizNo,
ProductNo: req.ProductNo,
Account: req.Account,
AppID: req.AppID,
MerchantNo: product.MchId,
Channel: product.Channel,
BatchNo: product.BatchNo,
NotifyUrl: req.NotifyUrl,
AccountType: vo.OrderAccountTypeOpenId,
Type: req.Type,
Status: vo.OrderStatusIng, // 同步发放,状态至为发放中
Attach: req.Attach,
ActivityId: product.ActivityId, // 多笔立减活动
}
return this.OrderRepo.Create(ctx, o)
}
func (this *VoucherBiz) ing(ctx context.Context, id uint64) error {
return this.OrderRepo.Ing(ctx, id)
}
func (this *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo string) error {
return this.OrderRepo.Success(ctx, order.ID, voucherNo)
}
func (this *VoucherBiz) fail(ctx context.Context, order *bo.OrderBo, errReq error) error {
errMsg := errReq.Error()
if err := this.OrderRepo.Fail(ctx, order.ID, errMsg); err != nil {
return err
}
if errMsg == `Post "https://api.mch.weixin.qq.com/v3/marketing/favor/users/oW97fjrv_ntYBPjMsaaEMRSj6TPA/coupons": EOF` {
// 微信:不同微信号,领取了很多次,自然人限领,发放频率限制,微信研发排期,后续这个错误信息应该会更新,近期没那么快同步上去
return nil
}
if err2.IsWechatAccountFail(errReq) || err2.IsWechatUserMaxCoupons(errReq) {
return nil // 过滤调该类型错误通知
}
return this.alarm(ctx, order, errReq.Error())
}
func (this *VoucherBiz) GetByOutBizNo(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) {
order, err := this.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, req.OutBizNo)
if err != nil && !err2.IsDbNotFound(err) {
return nil, err
}
return order, nil
}
func (this *VoucherBiz) UpdateOrderStatus(ctx context.Context, orderId uint64, status vo.OrderStatus) error {
if status.IsSuccess() {
return this.OrderRepo.Available(ctx, orderId)
} else if status.IsUse() {
return this.OrderRepo.Used(ctx, orderId)
} else if status.IsExpired() {
return this.OrderRepo.Expired(ctx, orderId)
}
return fmt.Errorf("notice 未知券状态,orderId:%d,statuText:%s", orderId, status.GetText())
}

View File

@ -1,117 +0,0 @@
package biz
import (
"context"
"fmt"
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
"time"
v1 "voucher/api/v1"
"voucher/internal/biz/do"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) CmbProductQuery(ctx context.Context, productNo string) (reps *v1.CmbQueryProductReply, err error) {
c := vo.CmbProductQueryLockKey.BuildCache([]string{productNo})
err = lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
product, err3 := this.ProductRepo.GetByProductNo(ctx, productNo)
if err3 != nil {
return err3
}
if !product.Channel.IsWeChat() {
return fmt.Errorf("只支持微信")
}
wechatResp, err4 := this.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
if err4 != nil {
return err4
}
reps = &v1.CmbQueryProductReply{
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
RespMsg: "成功",
ActivityName: product.Name,
ActivityId: product.ProductNo,
Amount: "",
MinAmount: "",
AvailableType: "",
AvailableDays: "", // 动态有效期天数
StartTime: "",
EndTime: "",
AvailableStock: "",
Detail: *wechatResp.Description,
}
inputFormat := time.RFC3339
if wechatResp.AvailableBeginTime != nil {
availableBeginTime, _ := time.Parse(inputFormat, *wechatResp.AvailableBeginTime)
reps.StartTime = availableBeginTime.Format("2006-01-02 15:04:05.000")
reps.SaleStartTime = reps.StartTime
}
if wechatResp.AvailableEndTime != nil {
availableEndTime, _ := time.Parse(inputFormat, *wechatResp.AvailableEndTime)
reps.EndTime = availableEndTime.Format("2006-01-02 15:04:05.000")
reps.SaleEndTime = reps.EndTime
}
reps.Amount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.CouponAmount)
reps.MinAmount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.TransactionMinimum)
availableStock := *wechatResp.StockUseRule.MaxCoupons - *wechatResp.DistributedCoupons
reps.AvailableStock = fmt.Sprintf("%d", availableStock)
availableType, err3 := product.AvailableType.GetCmbAvailableType()
if err3 != nil {
return err3
}
reps.AvailableType = availableType.GetValue()
reps.AvailableDays = fmt.Sprintf("%d", product.AvailableDays)
return nil
})
return
}
func (this *VoucherBiz) WxResp(wxResp *cashcoupons.Stock) (reps *do.WxResp) {
availableStock := *wxResp.StockUseRule.MaxCoupons - *wxResp.DistributedCoupons
couponAmount := *wxResp.StockUseRule.FixedNormalCoupon.CouponAmount / 100
remainingBudget := availableStock * couponAmount
stockUsageRate := float64(*wxResp.DistributedCoupons) / float64(*wxResp.StockUseRule.MaxCoupons) * 100
req := &do.WxResp{
Amount: couponAmount,
AllBudget: *wxResp.StockUseRule.MaxAmount / 100,
AllStock: *wxResp.StockUseRule.MaxCoupons,
UsedStock: *wxResp.DistributedCoupons,
UsedBudget: *wxResp.DistributedCoupons * couponAmount,
AvailableStock: availableStock,
AvailableBudget: remainingBudget,
StockUsageRate: stockUsageRate,
}
inputFormat := time.RFC3339
if wxResp.AvailableBeginTime != nil {
availableBeginTime, _ := time.Parse(inputFormat, *wxResp.AvailableBeginTime)
req.StartTime = &availableBeginTime
}
if wxResp.AvailableEndTime != nil {
availableEndTime, _ := time.Parse(inputFormat, *wxResp.AvailableEndTime)
req.EndTime = &availableEndTime
}
return req
}

View File

@ -1,82 +0,0 @@
package biz
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"time"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) CmbQuery(ctx context.Context, orderNo string) (resp *v1.CmbQueryReply, err error) {
c := vo.CmbQueryLockKey.BuildCache([]string{orderNo})
err = lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
order, err3 := this.OrderRepo.GetByOrderNo(ctx, orderNo)
if err3 != nil {
return err3
}
if err = this.Query(ctx, order); err != nil {
return err
}
status, err3 := order.Status.GetCmbStatusText()
if err3 != nil {
return err3
}
resp = &v1.CmbQueryReply{
Ticket: order.OrderNo,
Status: status.GetValue(),
TransDate: time.Now().Format("20060102150405"),
OrgNo: this.bc.Cmb.OrgNo,
Ext: "",
}
return nil
})
return
}
func (this *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error {
status, err := this.WechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if order.Status == status {
log.Warnf("券状态未改变:%s忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo)
return nil
}
if err = this.UpdateOrderStatus(ctx, order.ID, status); err != nil {
return err
}
order.Status = status
return nil
}
func (this *VoucherBiz) QueryOrder(ctx context.Context, orderNo string) (string, error) {
order, err3 := this.OrderRepo.GetByOrderNo(ctx, orderNo)
if err3 != nil {
return "", err3
}
status, err := this.WechatCpnRepo.Query(ctx, order)
if err != nil {
return "", err
}
return fmt.Sprintf("orderNo:%s,订单状态:%s,微信查询返回状态:%s", orderNo, order.Status.GetText(), status.GetText()), nil
}

View File

@ -1,77 +0,0 @@
package biz
import (
"context"
"fmt"
err2 "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) RegisterTag(ctx context.Context, id int32) error {
stock, err := this.ProductRepo.GetById(ctx, id)
if err != nil {
return err
}
wxResp, err := this.WechatCpnRepo.QueryProduct(ctx, stock.MchId, stock.BatchNo)
if err != nil {
return err
}
if err = this.ProductRepo.UpdateByWxResp(ctx, stock.ID, this.WxResp(wxResp)); err != nil {
return err
}
if err = this.registerNotifyTag(ctx, stock.MchId, stock.BatchNo); err != nil {
return err
}
_, err = this.ProductRepo.GetByProductNo(ctx, stock.ProductNo)
return err
}
func (this *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error {
cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{this.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
return lock.NewMutex(this.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
wechatNotifyTag, err3 := this.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID)
if err3 != nil && !err2.IsDbNotFound(err3) {
return err3
}
if wechatNotifyTag != nil {
if wechatNotifyTag.Tag != this.bc.WechatNotifyMQ.Tag {
return fmt.Errorf("tag不一致请检查tag配置:%s", wechatNotifyTag.Tag)
}
if wechatNotifyTag.Status.IsSuccess() {
return nil
}
} else {
wechatNotifyTag, err3 = this.createRegisterTag(ctx, stockCreatorMchID, stockID)
if err3 != nil {
return err3
}
}
if err := this.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil {
return this.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error())
}
return this.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID)
})
}
func (this *VoucherBiz) createRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) {
return this.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{
StockID: stockID,
StockCreatorMchID: stockCreatorMchID,
Tag: this.bc.WechatNotifyMQ.Tag,
})
}

View File

@ -1,29 +0,0 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
"voucher/internal/biz/vo"
)
type OrderRepo interface {
SpecifyFindInBatches(ctx context.Context, w *bo.FindInBatchesBo, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
FinSucByStockIdInBatches(ctx context.Context, req *do.WechatQuery, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
FinFailByStockIdInBatches(ctx context.Context, batchNo string, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
FindIngInBatches(ctx context.Context, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
FindInBatches(ctx context.Context, w *bo.FindInBatchesUseBo, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
GetByOutBizNo(ctx context.Context, t vo.OrderType, outBizNo string) (*bo.OrderBo, error)
GetByOrderNo(ctx context.Context, orderNo string) (*bo.OrderBo, error)
GetByCouponId(ctx context.Context, merchantNo, batchNo, voucherNo string) (*bo.OrderBo, error)
GetByTransactionId(ctx context.Context, stockCreatorMchId, stockID, transactionId string) (*bo.OrderBo, error)
Create(ctx context.Context, req *bo.OrderBo) (*bo.OrderBo, error)
GetByID(ctx context.Context, id uint64) (*bo.OrderBo, error)
Ing(ctx context.Context, id uint64) error
Success(ctx context.Context, id uint64, voucherNo string) error
Fail(ctx context.Context, id uint64, remark string) error
Used(ctx context.Context, id uint64) error
NotifyUsed(ctx context.Context, id uint64, transactionId string) error
Available(ctx context.Context, id uint64) error
Expired(ctx context.Context, id uint64) error
}

View File

@ -1,10 +0,0 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
)
type OrderBakRepo interface {
SpecifyFindInBatches(ctx context.Context, w *bo.FindInBatchesBo, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
}

View File

@ -1,15 +0,0 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
type OrderNotifyRepo interface {
GetByID(ctx context.Context, id uint64) (*bo.OrderNotifyBo, error)
GetCountByOrderNoAndEvent(ctx context.Context, orderNo string, event vo.OrderNotifyEvent) (int64, error)
Create(ctx context.Context, req *bo.OrderNotifyBo) (*bo.OrderNotifyBo, error)
Success(ctx context.Context, id uint64, responses string) error
Fail(ctx context.Context, id uint64, remark string) error
}

View File

@ -0,0 +1,13 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
)
type OrderRepo interface {
// Create 创建 Order
Create(ctx context.Context, req *bo.OrderBo) (*bo.OrderBo, error)
// GetByID 根据 ID 获取 Order
GetByID(ctx context.Context, id int32) (*bo.OrderBo, error)
}

View File

@ -1,15 +0,0 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
)
type ProductRepo interface {
GetById(ctx context.Context, id int32) (*bo.ProductBo, error)
FindWarningBudget(ctx context.Context, fun func(ctx context.Context, rows []*bo.ProductBo) error) error
GetByBatchNo(ctx context.Context, batchNo string) (*bo.ProductBo, error)
GetByProductNo(ctx context.Context, productNo string) (*bo.ProductBo, error)
UpdateByWxResp(ctx context.Context, id int32, req *do.WxResp) error
}

View File

@ -1,13 +0,0 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
)
type WechatNotifyRegisterTagRepo interface {
GetByStockIdAndMchId(ctx context.Context, stockCreatorMchID, stockId string) (*bo.WechatNotifyRegisterTagBo, error)
Create(ctx context.Context, req *bo.WechatNotifyRegisterTagBo) (*bo.WechatNotifyRegisterTagBo, error)
Success(ctx context.Context, id int32) error
Fail(ctx context.Context, id int32, remark string) error
}

View File

@ -0,0 +1,4 @@
package repository
type OrderRepo interface {
}

View File

@ -1,44 +0,0 @@
package biz
import (
"context"
)
func (this *VoucherBiz) OrderRetry(ctx context.Context, outBizNos []string) error {
return nil
//if len(outBizNos) > 0 {
//
// for _, outBizNo := range outBizNos {
//
// order, err := this.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, outBizNo)
// if err != nil {
// return fmt.Errorf(fmt.Sprintf("获取订单%s异常:%v", outBizNo, err))
// }
//
// if !order.Status.IsIng() {
// return fmt.Errorf(fmt.Sprintf("订单%s状态异常:%s", order.OrderNo, order.Status))
// }
//
// if err4 := this.orderRetry(ctx, order); err4 != nil {
// return err4
// }
// }
//
// return nil
//}
//
//return this.OrderRepo.FindIngInBatches(ctx, func(ctx context.Context, rows []*bo.OrderBo) error {
//
// for _, order := range rows {
//
// if err4 := this.orderRetry(ctx, order); err4 != nil {
// return err4
// }
// }
//
// return nil
//})
}

View File

@ -1,122 +0,0 @@
package biz
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport/http"
"time"
"voucher/internal/biz/bo"
)
func (this *VoucherBiz) PushRetryNotify(ctx http.Context, req *bo.FindInBatchesBo) error {
queue := this.bc.RdsMQ.GetRetryNotify()
if queue == nil {
return fmt.Errorf("队列不存在")
}
msg, err := json.Marshal(req)
if err != nil {
return err
}
strMsg := string(msg)
_, err = this.rdb.Rdb.RPush(ctx, queue.Name, strMsg).Result()
if err != nil {
return fmt.Errorf("添加到队列失败:%v", err)
}
return nil
}
func (this *VoucherBiz) RetryNotify(ctx context.Context, msg string) error {
var req *bo.FindInBatchesBo
if err := json.Unmarshal([]byte(msg), &req); err != nil {
return err
}
if err := this.RetryNotifyOrder(ctx, req); err != nil {
return err
}
return this.RetryNotifyOrderBack(ctx, req)
}
func (this *VoucherBiz) RetryNotifyOrder(ctx context.Context, req *bo.FindInBatchesBo) error {
start := time.Now()
startStr := time.Now().String()
log.Warnf("重试通知处理:%s", startStr)
fmt.Printf("重试通知处理:%s", startStr)
n := 0
eNum := 0
notifyNum := 0
err := this.OrderRepo.SpecifyFindInBatches(ctx, req, func(ctx context.Context, rows []*bo.OrderBo) error {
n += 1
for _, order := range rows {
eNum += 1
if _, err := this.Cmb.Notify(ctx, order); err != nil {
log.Errorf("重试通知处理,发生错误,orderNo:%s,outBizNo:%s,voucherNo:%s,err:%v", order.OrderNo, order.OutBizNo, order.VoucherNo, err)
} else {
notifyNum += 1
}
}
log.Warnf("重试通知处理,第:%d组,已执行条数:%d,通知成功条数:%d,", n, eNum, notifyNum)
return nil
})
endTime := time.Now()
log.Warnf("重试通知处理,耗时:%s,结束时间:%s,处理%d组,处理%d单,通知成功条数:%d", endTime.Sub(start).String(), endTime.String(), n, eNum, notifyNum)
fmt.Printf("重试通知处理,耗时:%s,结束时间%s,处理%d组,处理%d单,通知成功条数:%d", endTime.Sub(start).String(), endTime.String(), n, eNum, notifyNum)
return err
}
func (this *VoucherBiz) RetryNotifyOrderBack(ctx context.Context, req *bo.FindInBatchesBo) error {
start := time.Now()
startStr := time.Now().String()
log.Warnf("重试通知处理Bak:%s", startStr)
fmt.Printf("重试通知处理Bak:%s", startStr)
n := 0
eNum := 0
notifyNum := 0
err := this.OrderBakRepo.SpecifyFindInBatches(ctx, req, func(ctx context.Context, rows []*bo.OrderBo) error {
n += 1
for _, order := range rows {
eNum += 1
if _, err := this.Cmb.Notify(ctx, order); err != nil {
log.Errorf("重试通知处理Bak,发生错误,orderNo:%s,outBizNo:%s,voucherNo:%s,err:%v", order.OrderNo, order.OutBizNo, order.VoucherNo, err)
} else {
notifyNum += 1
}
}
log.Warnf("重试通知处理Bak,第:%d组,已执行条数:%d,通知成功条数:%d,", n, eNum, notifyNum)
return nil
})
endTime := time.Now()
log.Warnf("重试通知处理Bak,耗时:%s,结束时间:%s,处理%d组,处理%d单,通知成功条数:%d", endTime.Sub(start).String(), endTime.String(), n, eNum, notifyNum)
fmt.Printf("重试通知处理Bak,耗时:%s,结束时间%s,处理%d组,处理%d单,通知成功条数:%d", endTime.Sub(start).String(), endTime.String(), n, eNum, notifyNum)
return err
}

View File

@ -1,11 +1,11 @@
package mixrepos
package thirdrepository
import (
"context"
"voucher/internal/pkg/mq"
)
type MQSendMixRepo interface {
type ThirdMQSend interface {
// SendASync 异步发送消息出错会自动写日志errFn 中可以不用再写失败日志,可以处理一些数据纠正的操作等
SendASync(ctx context.Context, topicName string, body []byte, errFn func(error), sendOptions ...mq.SendOption) error

View File

@ -1,14 +0,0 @@
# <p align="center">券状态同步</p>
* * *
### 主要工作
+ 券状态查询同步
* * *
### 规则说明
+ 按照时间分片处理按照2小时为一个时间片启用一个协程消费处理
+ 协程最大可同时运行指定数量设置为2
* * *
### 使用方式
+ 消费处理,按照时间范围上报消费
+ 每次请求按照时间片分别启用2个协程消费处理也就是每个请求可以同时启用2个协程消费处理
+ 请不要无休止的访问,请按照时间片进行访问,并且不要重复的时间片访问,增加系统负载,特殊发券日期量较大,建议缩短时间范围上报,多切分上报处理
* * *

View File

@ -1,84 +0,0 @@
package timeslicequery
import (
"github.com/nacos-group/nacos-sdk-go/util"
"sync"
"voucher/internal/biz/cmb"
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/repo"
"voucher/internal/biz/wechatrepo"
"voucher/internal/conf"
"voucher/internal/data"
)
type Query struct {
mu sync.RWMutex
queryMap map[string]bool
bc *conf.Bootstrap
rdb *data.Rdb
cmb *cmb.Cmb
productRepo repo.ProductRepo
orderRepo repo.OrderRepo
wechatCpnRepo wechatrepo.WechatCpnRepo
mqSendMixRepo mixrepos.MQSendMixRepo
}
func NewQuery(
bc *conf.Bootstrap,
rdb *data.Rdb,
cmb *cmb.Cmb,
productRepo repo.ProductRepo,
orderRepo repo.OrderRepo,
wechatCpnRepo wechatrepo.WechatCpnRepo,
mqSendMixRepo mixrepos.MQSendMixRepo) *Query {
return &Query{
queryMap: make(map[string]bool),
bc: bc,
rdb: rdb,
cmb: cmb,
productRepo: productRepo,
orderRepo: orderRepo,
wechatCpnRepo: wechatCpnRepo,
mqSendMixRepo: mqSendMixRepo}
}
func (v *Query) uid(no string) string {
return util.Md5("query" + no)
}
func (this *Query) GetAll() map[string]bool {
return this.queryMap
}
func (this *Query) Get(uid string) bool {
this.mu.Lock()
defer this.mu.Unlock()
if _, ok := this.queryMap[uid]; ok {
return ok
}
return false
}
func (this *Query) Add(uid string) {
this.mu.Lock()
defer this.mu.Unlock()
this.queryMap[uid] = true
}
func (this *Query) Remove(uid string) {
this.mu.Lock()
defer this.mu.Unlock()
if _, ok := this.queryMap[uid]; ok {
delete(this.queryMap, uid)
}
}

View File

@ -1,98 +0,0 @@
package timeslicequery
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"time"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
"voucher/internal/pkg/timeslice"
)
func (v *Query) execute(ctx context.Context, req *timeslice.Manager) error {
start := time.Now()
managerStartStr := req.StartTime.Format(time.DateTime)
managerEndStr := req.EndTime.Format(time.DateTime)
log.Warnf("微信券查询,%s到%s,开始", managerStartStr, managerEndStr)
fmt.Printf("微信券查询,%s到%s,开始\n", managerStartStr, managerEndStr)
taskCount, err := timeslice.NewManager(v.callbackFunc).Run(ctx, req)
if err != nil {
log.Errorf("微信券查询,%s到%s,失败:%v", managerStartStr, managerEndStr, err)
}
elapsed := time.Now().Sub(start).String()
log.Warnf("微信券查询,%s到%s,总任务数:%d,总耗时:%s", managerStartStr, managerEndStr, taskCount, elapsed)
fmt.Printf("微信券查询,%s到%s,总任务数:%d,总耗时:%s\n", managerStartStr, managerEndStr, taskCount, elapsed)
return nil
}
func (v *Query) callbackFunc(ctx context.Context, req *timeslice.Task) error {
startTimeStr := req.Process.Manager.StartTime.Format(time.DateTime)
endTimeStr := req.Process.Manager.EndTime.Format(time.DateTime)
currentStartTimeStr := req.CurrentStartTime.Format(time.DateTime)
currentEndTimeStr := req.CurrentEndTime.Format(time.DateTime)
start := time.Now()
bReq := &do.WechatQuery{
StartTime: currentStartTimeStr,
EndTime: currentEndTimeStr,
ProductNo: req.Process.Manager.ProductNo,
BatchNo: req.Process.Manager.BatchNo,
}
num := 0
errNum := 0
useNum := 0
err := v.orderRepo.FinSucByStockIdInBatches(ctx, bReq, func(ctx context.Context, rows []*bo.OrderBo) error {
for _, order := range rows {
num += 1
if err := v.wechatQuery(ctx, order, &useNum); err != nil {
errNum += 1
logFields := map[string]string{
"order_no": order.OrderNo,
"coupon_id": order.VoucherNo,
"open_id": order.Account,
"stock_id": order.BatchNo,
"err": err.Error(),
}
log.Errorf("微信券查询,%s到%s,taskId:%d,错误:%+v", startTimeStr, endTimeStr, req.TaskID, logFields)
if errNum > 20 {
return fmt.Errorf("微信券查询,%s到%s,第%d个任务,已经连续发生20次错误%+v", startTimeStr, endTimeStr, req.TaskID, logFields)
}
}
}
return nil
})
end := time.Now()
logFields := map[string]any{
"sTime": currentStartTimeStr + "到" + currentEndTimeStr,
"num": num,
"useNum": useNum,
"errNum": errNum,
"batchNo": req.Process.Manager.BatchNo,
"elapsed": end.Sub(start).String(),
}
log.Warnf("微信券查询,%s到%s,taskId:%d,处理完毕:%+v", startTimeStr, endTimeStr, req.TaskID, logFields)
return err
}

View File

@ -1,119 +0,0 @@
package timeslicequery
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport/http"
"time"
"voucher/internal/biz/do"
"voucher/internal/pkg/timeslice"
)
func (v *Query) Push(ctx http.Context, req *do.RdsWechatQuery) (string, error) {
if req.StartTime == "" || req.EndTime == "" {
return "", fmt.Errorf("时间参数不能为空")
}
queue := v.bc.RdsMQ.GetWechatTimeSliceQuery()
if queue == nil {
return "", fmt.Errorf("队列不存在")
}
if queue.Name == "" {
return "", fmt.Errorf("队列不存在")
}
if queue.IsOpen == false {
return "", fmt.Errorf("队列未开启")
}
if req.ProductNo != "" {
_, err := v.productRepo.GetByProductNo(ctx, req.ProductNo)
if err != nil {
return "", err
}
}
b, err := json.Marshal(req)
if err != nil {
return "", err
}
strMsg := string(b)
uid := v.uid(strMsg)
if v.Get(uid) {
return "", fmt.Errorf("此台服务队列正在处理中,%s-%s,ip:%s", uid, strMsg, ctx.Header().Get("X-Forwarded-For"))
}
v.Add(uid)
_, err = v.rdb.Rdb.RPush(ctx, queue.Name, strMsg).Result()
if err != nil {
v.Remove(uid)
return "", fmt.Errorf("添加到队列失败:%v", err)
}
return strMsg, nil
}
func (v *Query) getManager(msg string) (*timeslice.Manager, error) {
var req *do.RdsWechatQuery
if err := json.Unmarshal([]byte(msg), &req); err != nil {
return nil, err
}
if req.StartTime == "" || req.EndTime == "" {
return nil, fmt.Errorf("时间参数不能为空")
}
start, err := time.Parse(time.DateTime, req.StartTime)
if err != nil {
return nil, err
}
end, err := time.Parse(time.DateTime, req.EndTime)
if err != nil {
return nil, err
}
manager := &timeslice.Manager{
StartTime: start,
EndTime: end,
ProductNo: req.ProductNo,
BatchNo: req.BatchNo,
GoNum: timeslice.DefaultGoNum, // 协程数量
TimeSliceHours: timeslice.DefaultTimeSliceHours, // 时间间隔
}
if req.GoNum > 0 {
manager.GoNum = req.GoNum
}
if req.TimeSliceHours > 0 {
manager.TimeSliceHours = req.TimeSliceHours
}
return manager, nil
}
func (v *Query) Consumer(ctx context.Context, msg string) error {
defer v.Remove(v.uid(msg))
req, err := v.getManager(msg)
if err != nil {
log.Errorf("微信券查询,前置参数处理失败,msg:%s,err:%v", msg, err)
return nil
}
if err = v.execute(ctx, req); err != nil {
log.Errorf("微信券查询,失败,msg:%s,err:%v", msg, err)
}
return nil
}

View File

@ -1,7 +0,0 @@
package timeslicequery
import (
"github.com/google/wire"
)
var ProviderSetTimeSliceQuery = wire.NewSet(NewQuery)

View File

@ -1,65 +0,0 @@
package timeslicequery
import (
"context"
"voucher/internal/biz/bo"
)
func (v *Query) wechatQuery(ctx context.Context, order *bo.OrderBo, useNum *int) error {
status, err := v.wechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if status.IsUse() {
if err = v.queryUsed(ctx, order); err != nil {
return err
}
*useNum += 1
} else if status.IsExpired() {
return v.queryExpired(ctx, order)
}
return nil
}
func (v *Query) queryUsed(ctx context.Context, order *bo.OrderBo) error {
if order.Status.IsUse() {
return v.notify(ctx, order)
}
if err := v.orderRepo.Used(ctx, order.ID); err != nil {
return err
}
return v.notify(ctx, order)
}
func (v *Query) queryExpired(ctx context.Context, order *bo.OrderBo) error {
if order.Status.IsExpired() {
return nil
}
if err := v.orderRepo.Expired(ctx, order.ID); err != nil {
return err
}
return v.notify(ctx, order)
}
func (v *Query) notify(ctx context.Context, order *bo.OrderBo) error {
order, err := v.orderRepo.GetByID(ctx, order.ID)
if err != nil {
return err
}
if _, err = v.cmb.Notify(ctx, order); err != nil {
return err
}
return nil
}

View File

@ -1,46 +0,0 @@
package vo
import "errors"
type AvailableType uint8
const (
AvailableTypeFixed AvailableType = iota + 1
AvailableTypeDynamic
)
var AvailableTypeMap = map[AvailableType]string{
AvailableTypeFixed: "固定有效期",
AvailableTypeDynamic: "动态有效期",
}
func (s AvailableType) GetText() string {
if t, ok := AvailableTypeMap[s]; ok {
return t
}
return "未知券领取类型"
}
func (s AvailableType) GetValue() uint8 {
return uint8(s)
}
func (s AvailableType) IsFixed() bool {
return s == AvailableTypeFixed
}
func (s AvailableType) IsDynamic() bool {
return s == AvailableTypeDynamic
}
var AvailableCmbTypeMap = map[AvailableType]CmbAvailableType{
AvailableTypeFixed: CmbAvailableTypeFixed,
AvailableTypeDynamic: CmbAvailableTypeDynamic,
}
func (s AvailableType) GetCmbAvailableType() (CmbAvailableType, error) {
if t, ok := AvailableCmbTypeMap[s]; ok {
return t, nil
}
return "", errors.New("未知券领取类型")
}

View File

@ -1,107 +0,0 @@
package vo
import (
"time"
"voucher/internal/pkg/helper"
)
type CacheKey string
const (
CmbOrderLockKey CacheKey = "cmb_order"
CmbQueryLockKey CacheKey = "cmb_query"
CmbProductQueryLockKey CacheKey = "cmb_product_query"
CmbBatchNoticeCacheKey CacheKey = "cmb_batch_notice"
CmbBatchNoticeLockKey CacheKey = "cmb_batch_notice_lock"
NotifyRetryConsume CacheKey = "notify_retry_consume"
OrderConsumeFailAlarmKey CacheKey = "order_consume_fail_alarm"
OrderConsumeFailAlarmLockKey CacheKey = "order_consume_fail_alarm_lock"
WechatNotifyRegisterTagCacheLockKey CacheKey = "register_tag_lock"
WechatNotifyConsumeLockKey CacheKey = "wechat_notify_consume"
)
const (
ProductQueryKey CacheKey = "product_query"
ProductQueryLockKey CacheKey = "product_query_lock"
)
var (
WarningBudgetCron CacheKey = "warning_budget_cron"
WarningBudgetSendIncr CacheKey = "warning_budget_incr"
)
var CacheKeyMap = map[CacheKey]time.Duration{
CmbOrderLockKey: 30 * time.Second,
CmbQueryLockKey: 30 * time.Second,
CmbProductQueryLockKey: 30 * time.Second,
CmbBatchNoticeCacheKey: 21600 * time.Second, // 6小时
CmbBatchNoticeLockKey: 300 * time.Second,
OrderConsumeFailAlarmKey: 3 * time.Hour, // 3小时
OrderConsumeFailAlarmLockKey: 60 * time.Second,
NotifyRetryConsume: 60 * time.Second,
WechatNotifyRegisterTagCacheLockKey: 60 * time.Second,
WechatNotifyConsumeLockKey: 30 * time.Second,
ProductQueryKey: 30 * 86400 * time.Second, // 30天
ProductQueryLockKey: 30 * time.Second,
WarningBudgetSendIncr: 3 * time.Hour,
WarningBudgetCron: 5 * time.Minute,
}
type Cache struct {
Key string
TTL time.Duration
}
func (s CacheKey) GetValue() string {
return string(s)
}
func (s CacheKey) BuildCache(strArr []string) *Cache {
k := s.GetValue()
if len(strArr) > 0 {
k = helper.BuildStr(k, strArr)
}
c := &Cache{
Key: k,
}
ttl, ok := CacheKeyMap[s]
if !ok {
c.TTL = 30 // 默认30秒
}
c.TTL = ttl
return c
}
func (s CacheKey) BuildCacheUint64(ids []uint64) *Cache {
k := s.GetValue()
if len(ids) > 0 {
k = helper.BuildStr(k, ids)
}
c := &Cache{
Key: k,
}
ttl, ok := CacheKeyMap[s]
if !ok {
c.TTL = 30 // 默认30秒
}
c.TTL = ttl
return c
}

View File

@ -1,32 +0,0 @@
package vo
type Channel uint8
const (
OrderChannelWechat Channel = iota + 1
OrderChannelAlipay
)
var OrderChannelMap = map[Channel]string{
OrderChannelWechat: "微信",
OrderChannelAlipay: "支付宝",
}
func (s Channel) GetText() string {
if t, ok := OrderChannelMap[s]; ok {
return t
}
return "未知商品渠道类型"
}
func (s Channel) GetValue() uint8 {
return uint8(s)
}
func (s Channel) IsWeChat() bool {
return s == OrderChannelWechat
}
func (s Channel) IsAlipay() bool {
return s == OrderChannelAlipay
}

View File

@ -1,51 +0,0 @@
package vo
// CmbFuncName . 招行接口名称
type CmbFuncName string
const (
// CmbNotifyFuncName . 券状态回调通知方法
CmbNotifyFuncName CmbFuncName = "updateCodeStatus.json"
)
func (s CmbFuncName) GetValue() string {
return string(s)
}
// CmbStatus . 券通知状态
type CmbStatus string
const (
CmbStatusSuccess CmbStatus = "0" // 券可用
CmbStatusUse CmbStatus = "1" // 券使用
//CmbStatusExpired CmbStatus = "2" // 券过期-待确认是否通知
CmbStatusExpired CmbStatus = "0" // 券过期-等于券没有使用
)
func (s CmbStatus) GetValue() string {
return string(s)
}
// CmbResponseStatus . 响应状态
type CmbResponseStatus string
const (
CmbResponseStatusSuccess CmbResponseStatus = "1000" // 响应成功
CmbResponseStatusFail CmbResponseStatus = "1001" // 响应失败
)
func (s CmbResponseStatus) GetValue() string {
return string(s)
}
// CmbAvailableType . 有效期形式0固定有效期1动态有效期
type CmbAvailableType string
const (
CmbAvailableTypeFixed CmbAvailableType = "0" // 固定有效期
CmbAvailableTypeDynamic CmbAvailableType = "1" // 动态有效期
)
func (s CmbAvailableType) GetValue() string {
return string(s)
}

View File

@ -1,32 +0,0 @@
package vo
type OrderAccountType uint8
const (
OrderAccountTypeOpenId OrderAccountType = iota + 1
OrderAccountTypePhone
)
var OrderAccountTypeMap = map[OrderAccountType]string{
OrderAccountTypeOpenId: "openid/userid",
OrderAccountTypePhone: "手机号",
}
func (s OrderAccountType) GetText() string {
if t, ok := OrderAccountTypeMap[s]; ok {
return t
}
return "未知账号类型"
}
func (s OrderAccountType) GetValue() uint8 {
return uint8(s)
}
func (s OrderAccountType) IsOpenId() bool {
return s == OrderAccountTypeOpenId
}
func (s OrderAccountType) IsPhone() bool {
return s == OrderAccountTypePhone
}

View File

@ -1,57 +0,0 @@
package vo
import "fmt"
type OrderNotifyEvent uint8
const (
OrderNotifyEventSendDEd OrderNotifyEvent = iota + 1
OrderNotifyEventUsed
OrderNotifyEventExpired
)
var OrderNotifyEventMap = map[OrderNotifyEvent]string{
OrderNotifyEventSendDEd: "可用",
OrderNotifyEventUsed: "已实扣",
OrderNotifyEventExpired: "已过期",
}
func (s OrderNotifyEvent) GetText() string {
if t, ok := OrderNotifyEventMap[s]; ok {
return t
}
return "未知通知事件"
}
func (s OrderNotifyEvent) GetValue() uint8 {
return uint8(s)
}
func (s OrderNotifyEvent) IsSendDEd() bool {
return s == OrderNotifyEventSendDEd
}
func (s OrderNotifyEvent) IsUsed() bool {
return s == OrderNotifyEventUsed
}
func (s OrderNotifyEvent) IsExpired() bool {
return s == OrderNotifyEventExpired
}
func (s OrderNotifyEvent) CanNotify() bool {
return s.IsSendDEd() || s.IsUsed() || s.IsExpired()
}
var OrderNotifyEventMapCmbStatus = map[OrderNotifyEvent]CmbStatus{
OrderNotifyEventSendDEd: CmbStatusSuccess,
OrderNotifyEventUsed: CmbStatusUse,
OrderNotifyEventExpired: CmbStatusExpired,
}
func (s OrderNotifyEvent) GetCmbStatusText() (CmbStatus, error) {
if t, ok := OrderNotifyEventMapCmbStatus[s]; ok {
return t, nil
}
return "", fmt.Errorf("cmbStatus[%s]未定义", s)
}

View File

@ -1,38 +0,0 @@
package vo
type OrderNotifyStatus uint8
const (
OrderNotifyStatusWait OrderNotifyStatus = iota + 1
OrderNotifyStatusSuccess
OrderNotifyStatusFail
)
var OrderNotifyStatusMap = map[OrderNotifyStatus]string{
OrderNotifyStatusWait: "待请求",
OrderNotifyStatusSuccess: "请求成功",
OrderNotifyStatusFail: "请求失败",
}
func (s OrderNotifyStatus) GetText() string {
if t, ok := OrderNotifyStatusMap[s]; ok {
return t
}
return "未知请求状态"
}
func (s OrderNotifyStatus) GetValue() uint8 {
return uint8(s)
}
func (s OrderNotifyStatus) IsWait() bool {
return s == OrderNotifyStatusWait
}
func (s OrderNotifyStatus) IsSuccess() bool {
return s == OrderNotifyStatusSuccess
}
func (s OrderNotifyStatus) IsFail() bool {
return s == OrderNotifyStatusFail
}

View File

@ -1,88 +1 @@
package vo
import "fmt"
type OrderStatus uint8
const (
OrderStatusWait OrderStatus = iota + 1
OrderStatusIng
OrderStatusSuccess
OrderStatusFail
OrderStatusUse
OrderStatusExpired
)
var OrderStatusMap = map[OrderStatus]string{
OrderStatusWait: "待发放",
OrderStatusIng: "发放中",
OrderStatusSuccess: "发放成功",
OrderStatusFail: "发放失败",
OrderStatusUse: "已使用",
OrderStatusExpired: "已过期",
}
func (s OrderStatus) GetText() string {
if t, ok := OrderStatusMap[s]; ok {
return t
}
return "未知状态"
}
func (s OrderStatus) GetValue() uint8 {
return uint8(s)
}
func (s OrderStatus) IsWait() bool {
return s == OrderStatusWait
}
func (s OrderStatus) IsIng() bool {
return s == OrderStatusIng
}
func (s OrderStatus) IsSuccess() bool {
return s == OrderStatusSuccess
}
func (s OrderStatus) IsFail() bool {
return s == OrderStatusFail
}
func (s OrderStatus) IsUse() bool {
return s == OrderStatusUse
}
func (s OrderStatus) IsExpired() bool {
return s == OrderStatusExpired
}
func (s OrderStatus) IsCanNotify() bool {
return s.IsSuccess() || s.IsUse() || s.IsExpired()
}
var OrderStatusMapOrderNotifyEvent = map[OrderStatus]OrderNotifyEvent{
OrderStatusSuccess: OrderNotifyEventSendDEd,
OrderStatusUse: OrderNotifyEventUsed,
OrderStatusExpired: OrderNotifyEventExpired,
}
func (s OrderStatus) GetOrderNotifyEvent() (OrderNotifyEvent, error) {
if t, ok := OrderStatusMapOrderNotifyEvent[s]; ok {
return t, nil
}
return 0, fmt.Errorf("CmbStatus[%s]未定义", s)
}
var OrderStatusMapCmbStatus = map[OrderStatus]CmbStatus{
OrderStatusSuccess: CmbStatusSuccess,
OrderStatusUse: CmbStatusUse,
OrderStatusExpired: CmbStatusExpired,
}
func (s OrderStatus) GetCmbStatusText() (CmbStatus, error) {
if t, ok := OrderStatusMapCmbStatus[s]; ok {
return t, nil
}
return "", fmt.Errorf("cmbStatus[%s]未定义", s)
}

View File

@ -1,34 +0,0 @@
package vo
import (
"fmt"
)
type OrderType uint8
const (
OrderTypeCmb OrderType = iota + 1
)
var OrderTypeMap = map[OrderType]string{
OrderTypeCmb: "招行",
}
func (s OrderType) GetText() string {
if t, ok := OrderTypeMap[s]; ok {
return t
}
return "未知类型"
}
func (s OrderType) String() string {
return fmt.Sprintf("%d", s)
}
func (s OrderType) GetValue() uint8 {
return uint8(s)
}
func (s OrderType) IsCmb() bool {
return s == OrderTypeCmb
}

View File

@ -1,38 +0,0 @@
package vo
type WechatNotifyRegisterTagStatus uint8
const (
WechatNotifyRegisterTagStatusWait WechatNotifyRegisterTagStatus = iota + 1
WechatNotifyRegisterTagStatusSuccess
WechatNotifyRegisterTagStatusFail
)
var WechatNotifyRegisterTagStatusMap = map[WechatNotifyRegisterTagStatus]string{
WechatNotifyRegisterTagStatusWait: "待请求",
WechatNotifyRegisterTagStatusSuccess: "请求成功",
WechatNotifyRegisterTagStatusFail: "请求失败",
}
func (s WechatNotifyRegisterTagStatus) GetText() string {
if t, ok := WechatNotifyRegisterTagStatusMap[s]; ok {
return t
}
return "未知请求状态"
}
func (s WechatNotifyRegisterTagStatus) GetValue() uint8 {
return uint8(s)
}
func (s WechatNotifyRegisterTagStatus) IsWait() bool {
return s == WechatNotifyRegisterTagStatusWait
}
func (s WechatNotifyRegisterTagStatus) IsSuccess() bool {
return s == WechatNotifyRegisterTagStatusSuccess
}
func (s WechatNotifyRegisterTagStatus) IsFail() bool {
return s == WechatNotifyRegisterTagStatusFail
}

View File

@ -1,38 +0,0 @@
package vo
type WechatVoucherStatus string
const (
WechatVoucherStatusSended WechatVoucherStatus = "SENDED"
WechatVoucherStatusUsed WechatVoucherStatus = "USED"
WechatVoucherStatusExpired WechatVoucherStatus = "EXPIRED"
)
var VoucherStatusMap = map[WechatVoucherStatus]string{
WechatVoucherStatusSended: "可用",
WechatVoucherStatusUsed: "已实扣",
WechatVoucherStatusExpired: "已过期",
}
func (s WechatVoucherStatus) GetText() string {
if t, ok := VoucherStatusMap[s]; ok {
return t
}
return "未知类型"
}
func (s WechatVoucherStatus) GetValue() string {
return string(s)
}
func (s WechatVoucherStatus) IsSended() bool {
return s == WechatVoucherStatusSended
}
func (s WechatVoucherStatus) IsUsed() bool {
return s == WechatVoucherStatusUsed
}
func (s WechatVoucherStatus) IsExpired() bool {
return s == WechatVoucherStatusExpired
}

View File

@ -1,98 +1,17 @@
package biz
import (
"sync"
"voucher/internal/biz/cmb"
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/repo"
"voucher/internal/biz/wechatrepo"
"voucher/internal/conf"
"voucher/internal/data"
"voucher/internal/biz/repository"
"voucher/internal/biz/thirdrepository"
)
type VoucherBiz struct {
bc *conf.Bootstrap
rdb *data.Rdb
Cmb *cmb.Cmb
ProductRepo repo.ProductRepo
OrderRepo repo.OrderRepo
OrderBakRepo repo.OrderBakRepo
OrderNotifyRepo repo.OrderNotifyRepo
WechatNotifyRegisterTagRepo repo.WechatNotifyRegisterTagRepo
MqSendMixRepo mixrepos.MQSendMixRepo
GenerateMixRepo mixrepos.GenerateMixRepo
WechatCpnRepo wechatrepo.WechatCpnRepo
BankMultiActivityRepo wechatrepo.BankMultiActivityRepo
DingMixRepo mixrepos.DingMixRepo
CmbMixRepo mixrepos.CmbMixRepo
SmsMixRepo mixrepos.SmsMixRepo
mu sync.RWMutex
queryMap map[string]bool
OrderRepo repository.OrderRepo
ThirdMQSend thirdrepository.ThirdMQSend
}
func NewVoucherBiz(
bc *conf.Bootstrap,
rdb *data.Rdb,
Cmb *cmb.Cmb,
ProductRepo repo.ProductRepo,
OrderRepo repo.OrderRepo,
OrderBakRepo repo.OrderBakRepo,
OrderNotifyRepo repo.OrderNotifyRepo,
WechatNotifyRegisterTagRepo repo.WechatNotifyRegisterTagRepo,
MqSendMixRepo mixrepos.MQSendMixRepo,
GenerateMixRepo mixrepos.GenerateMixRepo,
WechatCpnRepo wechatrepo.WechatCpnRepo,
BankMultiActivityRepo wechatrepo.BankMultiActivityRepo,
DingMixRepo mixrepos.DingMixRepo,
CmbMixRepo mixrepos.CmbMixRepo,
SmsMixRepo mixrepos.SmsMixRepo,
) *VoucherBiz {
return &VoucherBiz{
bc: bc,
rdb: rdb,
Cmb: Cmb,
ProductRepo: ProductRepo,
OrderRepo: OrderRepo,
OrderBakRepo: OrderBakRepo,
OrderNotifyRepo: OrderNotifyRepo,
WechatNotifyRegisterTagRepo: WechatNotifyRegisterTagRepo,
MqSendMixRepo: MqSendMixRepo,
GenerateMixRepo: GenerateMixRepo,
WechatCpnRepo: WechatCpnRepo,
BankMultiActivityRepo: BankMultiActivityRepo,
DingMixRepo: DingMixRepo,
CmbMixRepo: CmbMixRepo,
SmsMixRepo: SmsMixRepo,
queryMap: make(map[string]bool),
}
func NewVoucherBiz(orderRepo repository.OrderRepo, thirdMQSend thirdrepository.ThirdMQSend) *VoucherBiz {
return &VoucherBiz{OrderRepo: orderRepo, ThirdMQSend: thirdMQSend}
}
func (this *VoucherBiz) Get(uid string) bool {
this.mu.Lock()
defer this.mu.Unlock()
if _, ok := this.queryMap[uid]; ok {
return ok
}
return false
}
func (this *VoucherBiz) Add(uid string) {
this.mu.Lock()
defer this.mu.Unlock()
this.queryMap[uid] = true
}
func (this *VoucherBiz) Remove(uid string) {
this.mu.Lock()
defer this.mu.Unlock()
if _, ok := this.queryMap[uid]; ok {
delete(this.queryMap, uid)
}
}
// 1:收单

View File

@ -1,251 +0,0 @@
package biz
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
"runtime"
"strconv"
"strings"
"time"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
"voucher/internal/biz/vo"
)
func (this *VoucherBiz) Warning(ctx context.Context, id int32) error {
product, err := this.ProductRepo.GetById(ctx, id)
if err != nil {
return err
}
return this.WarningBudget(ctx, product)
}
func (this *VoucherBiz) WarningBudgetIncr(ctx context.Context, key string, ttl time.Duration) (int64, error) {
// 增加发送计数
count, err := this.rdb.Rdb.IncrBy(ctx, key, 1).Result()
if err != nil {
return 0, err
}
// 如果是第一次发送,设置 过期时间
if count == 1 {
if err = this.rdb.Rdb.Expire(ctx, key, ttl).Err(); err != nil {
return 0, fmt.Errorf("设置过期时间失败: %v", err)
}
}
// 如果发送次数超过 “指定” 条,清除再来
if count > 24 { // 大约2小时
return 0, this.WarningBudgetIncrDel(ctx, key)
}
return count, nil
}
func (this *VoucherBiz) WarningBudgetIncrDel(ctx context.Context, key string) error {
// 检查键是否存在
exists, err := this.rdb.Rdb.Exists(ctx, key).Result()
if err != nil {
return fmt.Errorf("检查键存在性失败: %w", err)
}
// 如果键不存在,直接返回成功
if exists == 0 {
return nil
}
if _, err = this.rdb.Rdb.Del(ctx, key).Result(); err != nil {
return err
}
return nil
}
func (this *VoucherBiz) CronWarningBudget(ctx context.Context) {
uid := "warningBudget"
if b := this.Get(uid); b {
log.Warn("预警查询,上波还未执行完毕,此次暂不执行")
return
}
this.Add(uid)
defer this.Remove(uid)
start := time.Now()
log.Warnf("预警查询,执行开始: %s", start.Format(time.DateTime))
if err := this.cronWarningBudget(ctx); err != nil {
log.Errorf("预警查询,执行失败: %s", err)
}
end := time.Now()
elapsed := end.Sub(start)
log.Warnf("预警查询,开始执行时间%s,执行结束时间%s,代码块执行耗时: %s", start.Format(time.DateTime), end.Format(time.DateTime), elapsed)
return
}
func (this *VoucherBiz) cronWarningBudget(ctx context.Context) error {
return this.ProductRepo.FindWarningBudget(ctx, func(ctx context.Context, rows []*bo.ProductBo) error {
for _, row := range rows {
if err := this.WarningBudget(ctx, row); err != nil {
log.Context(ctx).Errorf("预警查询,处理失败: %s", err)
}
time.Sleep(time.Second * 2)
}
return nil
})
}
func (this *VoucherBiz) WarningBudget(ctx context.Context, product *bo.ProductBo) (wErr error) {
defer func() {
if err := recover(); err != nil {
_, file, line, _ := runtime.Caller(1) // 1 表示获取当前调用者的调用信息
log.Errorf("预警查询,发生错误:req:%s,err:%v,file:%s,line:%d", product.BatchNo, err, file, line)
wErr = fmt.Errorf("预警查询,panic: %v", err)
}
}()
if product.WarningBudget == 0 {
return fmt.Errorf("no warning budget")
}
if product.WarningBudget == 0 {
return fmt.Errorf("no warning budget")
}
now := time.Now()
if product.StartTime == nil || product.EndTime == nil {
return fmt.Errorf("no start or end time")
}
if now.Before(*product.StartTime) {
return fmt.Errorf("not start")
}
if now.After(*product.EndTime) {
return fmt.Errorf("expired")
}
wxResp, err := this.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
if err != nil {
return err
}
return this.Calculate(ctx, product, wxResp)
}
func (this *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxResp *cashcoupons.Stock) error {
w := this.WxResp(wxResp)
b := vo.WarningBudgetSendIncr.BuildCache([]string{product.BatchNo})
key := b.Key
ttl := b.TTL
if w.AllBudget > product.AllBudget {
if err := this.WarningBudgetIncrDel(ctx, key); err != nil {
return err
}
}
if err := this.ProductRepo.UpdateByWxResp(ctx, product.ID, w); err != nil {
return err
}
if product.WarningBudget >= w.AvailableBudget {
count, err2 := this.WarningBudgetIncr(ctx, key, ttl)
if err2 != nil {
return err2
}
if count == 1 {
return this.WarningSend(ctx, product, w)
} else {
log.Warnf("预警查询,当前达到预警第[%d]次,暂不做通知", count)
}
}
return nil
}
func (this *VoucherBiz) WarningSend(ctx context.Context, product *bo.ProductBo, wxResp *do.WxResp) error {
title := fmt.Sprintf("券预算预警通知:%s", product.BatchName)
if err := this.DingMixRepo.SendMessage(ctx, title, formatAsCard(product, wxResp)); err != nil {
return err
}
var warningPerson []do.WarningPerson
if err := json.Unmarshal([]byte(product.WarningPerson), &warningPerson); err != nil {
return err
}
var mobileList []string
for _, person := range warningPerson {
mobileList = append(mobileList, person.Mobile)
}
if len(mobileList) > 0 {
return this.SmsMixRepo.Send(ctx, mobileList, this.bc.AliYunSms.TemplateWarning, buildTemplateParams(product, wxResp))
}
return nil
}
func buildTemplateParams(product *bo.ProductBo, req *do.WxResp) map[string]string {
return map[string]string{
"stock_name": product.BatchName,
"stock_no": product.ProductNo,
"amount": strconv.Itoa(int(req.Amount)),
"all_stock": strconv.Itoa(int(req.AllStock)),
"all_budget": strconv.Itoa(int(req.AllBudget)),
"used_stock": strconv.Itoa(int(req.UsedStock)),
"used_budget": strconv.Itoa(int(req.UsedBudget)),
"available_stock": strconv.Itoa(int(req.AvailableStock)),
"available_budget": strconv.Itoa(int(req.AvailableBudget)),
"budget_usage_rate": fmt.Sprintf("%.1f", req.StockUsageRate),
}
}
func formatAsCard(product *bo.ProductBo, req *do.WxResp) string {
var card strings.Builder
card.WriteString("### " + product.BatchName + "\n\n")
// 基本信息
card.WriteString("#### 🎫 基本信息\n")
card.WriteString(fmt.Sprintf("- **批次号**: %s\n", product.BatchNo))
card.WriteString(fmt.Sprintf("- **活动号**: %s\n", product.ProductNo))
card.WriteString(fmt.Sprintf("- **预警值**: %d元\n", product.WarningBudget))
card.WriteString(fmt.Sprintf("- **面额**: %d元\n", req.Amount))
card.WriteString(fmt.Sprintf("- **总预算**: %d元\n", req.AllBudget))
card.WriteString(fmt.Sprintf("- **总库存**: %d张\n", req.AllStock))
card.WriteString("\n")
// 使用情况
card.WriteString("#### 📊 使用情况\n")
card.WriteString(fmt.Sprintf("- **已发券数**: %d张\n", req.UsedStock))
card.WriteString(fmt.Sprintf("- **已发券金额**: %d元\n", req.UsedBudget))
card.WriteString(fmt.Sprintf("- **剩余库存**: %d张\n", req.AvailableStock))
card.WriteString(fmt.Sprintf("- **剩余预算**: %d元\n", req.AvailableBudget))
card.WriteString("\n")
card.WriteString("#### 📈 使用率\n")
card.WriteString(fmt.Sprintf("- **使用率**: %.1f%%\n", req.StockUsageRate))
return card.String()
}

View File

@ -1,146 +0,0 @@
package biz
import (
"context"
"errors"
"fmt"
"gorm.io/gorm"
errPb "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, tag string, req *bo.WechatVoucherNotifyBo) error {
c := vo.WechatNotifyConsumeLockKey.BuildCache([]string{tag, req.PlainText.StockID, req.PlainText.CouponID})
return lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
order, err := this.getOrder(ctx, req)
if err != nil {
return err
}
if req.PlainText.Status.IsSended() {
return this.available(ctx, order)
} else if req.PlainText.Status.IsUsed() {
return this.notifyUsed(ctx, order, req)
} else if req.PlainText.Status.IsExpired() {
return this.expired(ctx, order)
}
return fmt.Errorf("未知通知类型:%s", req.PlainText.Status.GetText())
})
}
func (this *VoucherBiz) getOrder(ctx context.Context, req *bo.WechatVoucherNotifyBo) (*bo.OrderBo, error) {
order, err := this.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
order, err = this.OrderRepo.GetByTransactionId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.ConsumeInformation.TransactionID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("微信回调消费,订单不存在,StockCreatorMchid:%s,StockID:%s,CouponID:%s,CreateTime:%s",
req.PlainText.StockCreatorMchid,
req.PlainText.StockID,
req.PlainText.CouponID,
req.PlainText.CreateTime,
)
}
return nil, err
}
return order, nil
}
return order, nil
}
func (this *VoucherBiz) notifyUsed(ctx context.Context, order *bo.OrderBo, req *bo.WechatVoucherNotifyBo) error {
if order.Status.IsUse() {
// 券状态已是已使用,忽略不处理
return nil
}
if err := this.OrderRepo.NotifyUsed(ctx, order.ID, req.PlainText.ConsumeInformation.TransactionID); err != nil {
return err
}
return this.notify(ctx, order)
}
func (this *VoucherBiz) available(ctx context.Context, order *bo.OrderBo) error {
if order.Status.IsSuccess() {
// 券状态已是可使用,忽略不处理
return nil
}
if err := this.OrderRepo.Available(ctx, order.ID); err != nil {
return err
}
return this.notify(ctx, order)
}
func (this *VoucherBiz) expired(ctx context.Context, order *bo.OrderBo) error {
if order.Status.IsExpired() {
// 券状态已是已过期,忽略不处理
return nil
}
if err := this.OrderRepo.Expired(ctx, order.ID); err != nil {
return err
}
return this.notify(ctx, order)
}
func (this *VoucherBiz) notify(ctx context.Context, order *bo.OrderBo) error {
if order.ActivityId == "" {
return nil // 多笔立减活动,不做通知(?不知道有没有核销通知,多笔核销情况未知)
}
return this.cmbNotify(ctx, order.ID)
}
func (this *VoucherBiz) cmbNotify(ctx context.Context, orderId uint64) error {
order, err := this.OrderRepo.GetByID(ctx, orderId)
if err != nil {
return err
}
if orderNotify, err2 := this.Cmb.Notify(ctx, order); err != nil {
if !errPb.IsNeedRetryNotify(err2) {
return err2
}
// 第一次通知失败重试入队
// 状态回调接口失败需要支持重试 重试间隔为1分钟、2分钟、12分钟、60分钟、360分钟
return this.PushNotifyRetryDelayMQ(ctx, 60, orderNotify.ID)
}
return nil
}

View File

@ -1,130 +0,0 @@
package biz
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport/http"
"github.com/nacos-group/nacos-sdk-go/util"
"time"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
)
func (this *VoucherBiz) uid(_ context.Context, msg string) string {
return util.Md5(msg)
}
func (this *VoucherBiz) PushWechatQuery(ctx http.Context, req *do.WechatQuery) error {
if req.ProductNo != "" {
_, err := this.ProductRepo.GetByProductNo(ctx, req.ProductNo)
if err != nil {
return err
}
}
queue := this.bc.RdsMQ.GetWechatQuery()
if queue == nil {
return fmt.Errorf("队列不存在")
}
msg, err := json.Marshal(req)
if err != nil {
return err
}
strMsg := string(msg)
uid := this.uid(ctx, strMsg)
if this.Get(uid) {
return fmt.Errorf("此台服务队列正在处理中,key:%s,ip:%s", uid, ctx.Header().Get("X-Forwarded-For"))
}
this.Add(uid)
_, err = this.rdb.Rdb.RPush(ctx, queue.Name, strMsg).Result()
if err != nil {
this.Remove(uid)
return fmt.Errorf("添加到队列失败:%v", err)
}
return nil
}
func (this *VoucherBiz) WechatQuery(ctx context.Context, msg string) error {
defer this.Remove(this.uid(ctx, msg))
var req *do.WechatQuery
if err := json.Unmarshal([]byte(msg), &req); err != nil {
return err
}
start := time.Now()
startStr := time.Now().String()
log.Warnf("微信券查询处理开始:%s,msg:%s", startStr, msg)
fmt.Printf("微信券查询处理开始:%s,msg:%s", startStr, msg)
n := 0
num := 0
notifyNum := 0
err := this.OrderRepo.FinSucByStockIdInBatches(ctx, req, func(ctx context.Context, rows []*bo.OrderBo) error {
n += 1
for _, order := range rows {
num += 1
if err := this.wechatQuery(ctx, order, &notifyNum); err != nil {
log.Errorf("微信查询券订单状态发生错误,msg:%s,orderNo:%s,couponId:%s,appId:%s,openId:%s,stockId:%s,err:%v",
msg, order.OrderNo, order.VoucherNo, order.AppID, order.Account, order.BatchNo, err)
}
}
groupTime := time.Now()
log.Warnf("微信券查询处理第:%d组,已执行条数:%d,核销通知条数:%d,执行开始时间:%s当前时间:%s,已耗时:%s", n, num, notifyNum, startStr, groupTime.String(), groupTime.Sub(start).String())
return nil
})
endTime := time.Now()
log.Warnf("微信券查询处理耗时:%s,结束时间:%s,处理%d组,处理%d单,核销通知条数:%d,msg:%s", endTime.Sub(start).String(), endTime.String(), n, num, notifyNum, msg)
fmt.Printf("微信券查询处理耗时:%s,结束时间%s,处理%d组,处理%d单,核销通知条数:%d,msg:%s", endTime.Sub(start).String(), endTime.String(), n, num, notifyNum, msg)
return err
}
func (this *VoucherBiz) wechatQuery(ctx context.Context, order *bo.OrderBo, notifyNum *int) error {
status, err := this.WechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if status.IsUse() {
return this.queryUsed(ctx, order, notifyNum)
} else if status.IsExpired() {
return this.expired(ctx, order)
}
return nil
}
func (this *VoucherBiz) queryUsed(ctx context.Context, order *bo.OrderBo, notifyNum *int) error {
*notifyNum += 1
if order.Status.IsUse() {
return this.notify(ctx, order)
}
if err := this.OrderRepo.Used(ctx, order.ID); err != nil {
return err
}
return this.notify(ctx, order)
}

View File

@ -1,62 +0,0 @@
package biz
import (
"context"
"fmt"
)
func (this *VoucherBiz) PushWechatRetry(ctx context.Context, batchNo string) error {
product, err := this.ProductRepo.GetByBatchNo(ctx, batchNo)
if err != nil {
return err
}
queue := this.bc.RdsMQ.GetWechatRetry()
if queue == nil {
return fmt.Errorf("队列不存在")
}
_, err = this.rdb.Rdb.RPush(ctx, queue.Name, product.BatchNo).Result()
if err != nil {
return fmt.Errorf("添加到队列失败:%v", err)
}
return nil
}
func (this *VoucherBiz) WechatRetry(ctx context.Context, batchNo string) error {
return nil
//start := time.Now()
//log.Warnf("失败订单重试开始:%s,batchNo:%s", start.String(), batchNo)
//fmt.Printf("失败订单重试开始:%s,batchNo:%s", start.String(), batchNo)
//
//num := 0
//err := this.OrderRepo.FinFailByStockIdInBatches(ctx, batchNo, func(ctx context.Context, rows []*bo.OrderBo) error {
//
// if len(rows) == 0 {
// log.Infof("微信查询券订单状态,batchNo[%s],已处理[%d]单,无订单,结束执行", batchNo, num)
// return nil
// }
//
// for _, order := range rows {
//
// num += 1
// if err := this.orderRetry(ctx, order); err != nil {
// log.Errorf("失败订单重试发生错误,batchNo:%s,orderNo:%s,appId:%s,openId:%s,err:%v",
// batchNo, order.OrderNo, order.AppID, order.Account, err)
// }
//
// }
//
// time.Sleep(1 * time.Second)
//
// return nil
//})
//
//log.Warnf("微信券查询处理耗时:%s,batchNo:%s,处理%d单", time.Now().Sub(start).String(), batchNo, num)
//fmt.Printf("微信券查询处理耗时:%s,batchNo:%s,处理%d单", time.Now().Sub(start).String(), batchNo, num)
//
//return err
}

View File

@ -1,9 +0,0 @@
package wechatrepo
import (
"voucher/internal/biz/bo"
)
type BankMultiActivityRepo interface {
Order(order *bo.OrderBo) (couponId string, err error)
}

View File

@ -1,18 +0,0 @@
package wechatrepo
import (
"context"
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
type WechatCpnRepo interface {
Order(ctx context.Context, order *bo.OrderBo) (couponId string, err error)
Query(ctx context.Context, order *bo.OrderBo) (vo.OrderStatus, error)
QueryCoupon(ctx context.Context, orderWechat *bo.OrderBo) (*cashcoupons.Coupon, error)
QueryProduct(ctx context.Context, stockCreatorMchId, stockId string) (*cashcoupons.Stock, error)
QueryCallback(ctx context.Context) (*cashcoupons.Callback, error)
SetCallback(ctx context.Context, url string) (*cashcoupons.SetCallbackResponse, error)
RegisterNotifyTag(ctx context.Context, stockID string) error
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
syntax = "proto3";
package voucher.config;
option go_package = "voucher/cpn/conf;conf";
option go_package = "voucher/internal/conf;conf";
import "google/protobuf/duration.proto";
@ -10,13 +10,6 @@ message Bootstrap {
Logs logs = 2;
Data data = 3;
RocketMQ rocketMQ = 4;
Wechat wechat = 5;
Cmb cmb = 6;
WechatNotifyMQ wechatNotifyMQ = 7;
Alarm alarm = 8;
Cron cron = 9;
RdsMQ rdsMQ = 10;
AliYunSms aliYunSms = 11;
}
message Server {
@ -70,80 +63,6 @@ message EventMap {
bool isOpenConsumer = 4;
}
message Wechat {
string mchID = 1;
string mchCertificateSerialNumber = 2;
string wechatPayPublicKeyID = 3;
string name = 4;
}
message Cmb {
string mid = 1;
string aid = 2;
string sm2Prk = 3;
string sm2Puk = 4;
string cmbSm2Pik = 5;
string cmbSm2Puk = 6;
string keyAlias = 7;
string cmbKeyAlias = 8;
string orgNo = 9;
string notifyUrl = 10;
int64 noticeStartDays = 11;
int64 noticeEndDays = 12;
}
message WechatNotifyMQ {
string accessKeyId = 1;
string accessKeySecret = 2;
string endPoint = 3;
string regionId = 4;
string instanceId = 5;
string topic = 6;
string tag = 7;
string groupId = 8;
string registerTagUrl = 10;
bool isOpenConsumer = 11;
}
message Alarm {
string webhookURL = 1;
string secret = 2;
bool atAll = 3;
repeated string atMobiles = 4;
repeated string warningMobiles = 5;
}
message Cron {
message CommandMap {
bool isOpen = 1;
string command = 2;
}
bool isOpen = 1;
map<string, CommandMap> commandMap = 2;
}
message RdsMQ {
message Queue {
string name = 1;
bool isOpen = 2;
uint32 retryNum = 3;
uint32 numWorkers = 4;
google.protobuf.Duration waitTime = 5;
}
Queue wechatQuery = 1;
Queue wechatTimeSliceQuery = 2;
Queue wechatRetry = 3;
Queue retryNotify = 4;
}
message AliYunSms {
string accessKeyId = 1;
string accessKeySecret = 2;
string endpoint = 3;
string signName = 4;
string templateWarning = 5;
}
message Logs {
string business = 1;
string access = 2;

View File

@ -15,6 +15,12 @@ func NewDb(db *GormDb) *Db {
}
}
func (d *Db) DB(_ context.Context) *gorm.DB {
type contextTxKey struct{}
func (d *Db) DB(ctx context.Context) *gorm.DB {
tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
if ok {
return tx
}
return d.db.Client
}

View File

@ -1,9 +1,12 @@
package data
import (
"database/sql"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"time"
"voucher/internal/conf"
)
@ -11,20 +14,22 @@ type GormDb struct {
Client *gorm.DB
}
func NewGormDb(c *conf.Bootstrap) *GormDb {
return &GormDb{
Client: db(c.Data.Db),
func NewGormDb(c *conf.Bootstrap) (*GormDb, func()) {
db1, mf := db(c.Data.Db)
cleanup := func() {
mf()
}
return &GormDb{
Client: db1,
}, cleanup
}
func db(data *conf.Data_Database) *gorm.DB {
func db(data *conf.Data_Database) (*gorm.DB, func()) {
mysqlConn, err := sql.Open(data.Driver, data.Source)
gormDB, err := gorm.Open(
mysql.Open(data.Source),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
SkipDefaultTransaction: true,
},
mysql.New(mysql.Config{Conn: mysqlConn}),
&gorm.Config{Logger: logger.Default.LogMode(logger.Info)},
)
if err != nil {
@ -36,8 +41,19 @@ func db(data *conf.Data_Database) *gorm.DB {
panic("failed to gormDB " + err.Error())
}
sqlDB.SetMaxIdleConns(100)
sqlDB.SetMaxOpenConns(1000)
// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
sqlDB.SetMaxIdleConns(int(data.MaxIdle))
// SetMaxOpenConns sets the maximum number of openapi connections to the database.
sqlDB.SetMaxOpenConns(int(data.MaxOpen))
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
sqlDB.SetConnMaxLifetime(time.Hour)
return gormDB
return gormDB, func() {
if mysqlConn != nil {
fmt.Println("关闭 db")
if err := mysqlConn.Close(); err != nil {
fmt.Printf("关闭 db 失败:%v", err)
}
}
}
}

View File

@ -1,216 +0,0 @@
package data
import (
"context"
"errors"
"fmt"
"google.golang.org/protobuf/types/known/durationpb"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/hints"
"sync"
"testing"
"time"
"voucher/internal/biz/vo"
"voucher/internal/conf"
"voucher/internal/data/model"
)
var source = "voucher:Lsxd@2024@tcp(voucher.rwlb.cn-chengdu.rds.aliyuncs.com:3306)/voucher?parseTime=True&loc=Local"
func Test_gorm_db_create(t *testing.T) {
gormDB, err := gorm.Open(
mysql.Open(source),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
SkipDefaultTransaction: true,
},
)
if err != nil {
t.Fatal("failed to connect database " + err.Error())
}
sqlDB, err := gormDB.DB()
if err != nil {
t.Fatal("failed to gormDB " + err.Error())
}
sqlDB.SetMaxIdleConns(20)
sqlDB.SetMaxOpenConns(100)
start2 := time.Now()
concurrency := 1000 // 调整并发数
errCount := 0
stats := sqlDB.Stats()
fmt.Printf("order create 当前打开连接数:%d, 空闲连接数:%d\n", stats.OpenConnections, stats.Idle)
go func() {
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
fmt.Printf("监控: 当前打开连接数:%d, 空闲连接数:%d\n", stats.OpenConnections, stats.Idle)
}
}()
var wg sync.WaitGroup
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func(i int) {
defer wg.Done()
ctx := context.Background()
now := time.Now()
info := &model.Order{
OrderNo: fmt.Sprintf("test_create_10_%d", i),
OutBizNo: fmt.Sprintf("374252390990605ngywYrSAGE8310_%d", i),
ProductNo: "001",
BatchNo: "001",
Account: "oO3vO5K2nE131-9uMoeYymLhlbYk",
AccountType: 1,
Status: 2,
Type: 1,
AppID: "",
MerchantNo: "wx9ed74283ad25bca1",
Channel: 1,
NotifyUrl: "https://sandbox.cdcc.cmbchina.com/AccessGateway/transIn/updateCodeStatus.json",
Attach: "{}",
CreateTime: &now,
UpdateTime: &now,
}
if info.ProductNo == "001" {
info.VoucherNo = info.OrderNo
info.Status = vo.OrderStatusSuccess.GetValue()
}
ddd := gormDB.WithContext(ctx).Model(model.Order{})
tx := ddd.Create(info)
if tx.Error != nil {
fmt.Printf("写入错误: %v\n", tx.Error)
errCount += 1
}
sqlDBx, _ := ddd.DB()
fmt.Printf("order create 当前打开连接数:%d, 空闲连接数:%d\n", sqlDBx.Stats().OpenConnections, sqlDBx.Stats().Idle)
}(i)
}
fmt.Printf("order create 当前打开连接数:%d, 空闲连接数:%d\n", stats.OpenConnections, stats.Idle)
wg.Wait()
t.Logf("\n--------------连接请求,总请求耗时: %v,总数: %d, 失败次数: %d--------------\n", time.Since(start2), concurrency, errCount)
}
func Test_gorm_db(t *testing.T) {
// 镜像mysql
//data := &conf.Data_Database{
// Driver: "mysql",
// Source: "root:lsxddb123.@tcp(47.108.53.72:3306)/voucher?parseTime=True&loc=Local",
// MaxIdle: 20, // 空闲连接池
// MaxOpen: 200, // 最大连接数
// MaxLifetime: durationpb.New(60), // 5分钟
// IsDebug: false,
//}
// 测试
//data := &conf.Data_Database{
// Driver: "mysql",
// Source: "root:lansexiongdi6,@tcp(47.97.27.195:3306)/voucher?parseTime=True&loc=Local",
// MaxIdle: 2, // 空闲连接池
// MaxOpen: 10, // 最大连接数
// MaxLifetime: durationpb.New(60),
// IsDebug: false,
//}
data := &conf.Data_Database{
Driver: "mysql",
Source: "voucher:Lsxd@2024@tcp(voucher.rwlb.cn-chengdu.rds.aliyuncs.com:3306)/voucher?parseTime=True&loc=Local",
MaxIdle: 20, // 空闲连接池
MaxOpen: 200, // 最大连接数
MaxLifetime: durationpb.New(60), // 5分钟
IsDebug: false,
}
gormDb := db(data)
start2 := time.Now()
concurrency := 10000 // 调整并发数
errCount := 0
var wg sync.WaitGroup
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func(i int) {
defer wg.Done()
//ctx := context.Background()
var info model.Order
tx := gormDb.
//WithContext(ctx).
Model(model.Order{}).
//Clauses(hints.UseIndex("udx_out_biz_no_type")).
Where(model.Order{Type: vo.OrderTypeCmb.GetValue(), OutBizNo: fmt.Sprintf("174252390990605ngywYrSAGE83e1%d", i)}).
First(&info)
if tx.Error != nil {
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
t.Errorf("未找到记录")
} else {
fmt.Printf("请求错误: %v\n", tx.Error)
errCount += 1
}
}
time.Sleep(1 * time.Microsecond)
}(i)
}
wg.Wait()
t.Logf("\n--------------连接请求,总请求耗时: %v,总数: %d, 失败次数: %d--------------\n", time.Since(start2), concurrency, errCount)
}
func Test_db(t *testing.T) {
maxLifetime := durationpb.New(300) // 5分钟
data := &conf.Data_Database{
Driver: "mysql",
Source: "root:lansexiongdi6,@tcp(47.97.27.195:3306)/voucher?parseTime=True&loc=Local",
MaxIdle: 2, // 空闲连接池
MaxOpen: 10, // 最大连接数
MaxLifetime: maxLifetime,
IsDebug: false,
}
gormDb := db(data)
start2 := time.Now()
errCount := 0
const concurrency = 1
for i := 0; i < concurrency; i++ {
ctx := context.Background()
var info model.Order
tx := gormDb.
WithContext(ctx).
Model(model.Order{}).
Clauses(hints.ForceIndex("udx_out_biz_no_type")).
Where(model.Order{Type: vo.OrderTypeCmb.GetValue(), OutBizNo: fmt.Sprintf("174252390990605ngywYrSAGE83e1%d", i)}).
First(&info)
if tx.Error != nil {
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
t.Errorf("未找到记录")
} else {
fmt.Printf("请求错误: %v\n", tx.Error)
errCount += 1
}
}
}
t.Logf("\n--------------连接请求,总请求耗时: %v,总数: %d, 失败次数: %d--------------\n", time.Since(start2), concurrency, errCount)
}

View File

@ -1,322 +0,0 @@
package mixrepoimpl
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-kratos/kratos/v2/log"
http2 "github.com/go-kratos/kratos/v2/transport/http"
"io"
"net/http"
"net/url"
"time"
err2 "voucher/api/err"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/vo"
"voucher/internal/conf"
"voucher/internal/pkg/cmb"
"voucher/internal/pkg/helper"
"voucher/internal/pkg/request"
)
type CmbMixRepoImpl struct {
bc *conf.Bootstrap
// 连接池复用(优化网络开销)
options *request.Options
}
func NewCmbMixRepoImpl(bc *conf.Bootstrap) mixrepos.CmbMixRepo {
h := http.Header{
"Content-Type": []string{"application/json"},
}
hc := &http.Client{
Timeout: 20 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 20, // 每个主机的最大空闲连接数
IdleConnTimeout: 30 * time.Second, // 空闲连接超时时间
},
}
return &CmbMixRepoImpl{
bc: bc,
options: request.NewOptions(request.WithHeaders(h), request.WithHttpClient(hc)),
}
}
func (c *CmbMixRepoImpl) recordBody(ctx context.Context) {
httpRequest, ok := http2.RequestFromServerContext(ctx)
if !ok {
log.Errorf("read body not ok")
return
}
bodyBytes, err := io.ReadAll(httpRequest.Body)
if err != nil {
log.Errorf("read body not ok, %v", err)
return
}
log.Errorf("body %v", string(bodyBytes))
}
func (s *CmbMixRepoImpl) OrderVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbOrderRequest, error) {
bizStr, err := s.Verify(ctx, req)
if err != nil {
return nil, err2.ErrorCmbVerifyFail(err.Error())
}
if len(bizStr) == 0 {
s.recordBody(ctx)
return nil, err2.ErrorCmbBizContentFail("业务参数获取异常,请检查参数是否正确传递")
}
var bizContent *v1.CmbOrderRequest
if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
if err = bizContent.Validate(); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
return bizContent, nil
}
func (s *CmbMixRepoImpl) QueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryRequest, error) {
bizStr, err := s.Verify(ctx, req)
if err != nil {
return nil, err2.ErrorCmbVerifyFail(err.Error())
}
if len(bizStr) == 0 {
s.recordBody(ctx)
return nil, err2.ErrorCmbBizContentFail("业务参数获取异常,请检查参数是否正确传递")
}
var bizContent *v1.CmbQueryRequest
if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
if err = bizContent.Validate(); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
return bizContent, nil
}
func (s *CmbMixRepoImpl) ProductQueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryProductRequest, error) {
bizStr, err := s.Verify(ctx, req)
if err != nil {
return nil, err
}
if len(bizStr) == 0 {
s.recordBody(ctx)
return nil, err2.ErrorCmbBizContentFail("业务参数获取异常,请检查参数是否正确传递")
}
var bizContent *v1.CmbQueryProductRequest
if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
if err = bizContent.Validate(); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
return bizContent, nil
}
func (s *CmbMixRepoImpl) Verify(_ context.Context, req *v1.CmbRequest) (string, error) {
str := cmb.SortStructStr(req)
b, err := cmb.VerifyBody(str, req.Sign, s.bc.Cmb.CmbSm2Puk)
if err != nil {
return "", err2.ErrorCmbVerifyFail(err.Error())
}
if !b {
return "", err2.ErrorCmbVerifyFail("签名验证失败")
}
bizBytes, err := cmb.DecryptBody(&cmb.Decrypts{EncryptBody: req.EncryptBody, PrivateKey: s.bc.Cmb.Sm2Prk})
if err != nil {
return "", err2.ErrorCmbBizContentDecryptFail(err.Error())
}
return string(bizBytes), nil
}
func (s *CmbMixRepoImpl) GetRequest(_ context.Context, reqBo *bo.CmbRequestBo) (*v1.CmbRequest, error) {
encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.CmbSm2Puk, JsonParam: reqBo.BizContent})
if err != nil {
return nil, err
}
req := &v1.CmbRequest{
Mid: s.bc.Cmb.Mid,
Aid: s.bc.Cmb.Aid,
Date: time.Now().Format("20060102150405"),
Random: string(cmb.RandomBytes(16)),
KeyAlias: s.bc.Cmb.KeyAlias,
CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
EncryptBody: encryptBody,
Sign: "",
}
str := fmt.Sprintf("%s?%s", reqBo.FuncName, cmb.SortStructStr(req))
sign, err := cmb.SignBody(str, s.bc.Cmb.Sm2Prk)
if err != nil {
return nil, err
}
req.Sign = sign
return req, nil
}
func (s *CmbMixRepoImpl) GetMockRequest(_ context.Context, bizContent string) (*v1.CmbRequest, error) {
if len(s.bc.Cmb.Sm2Puk) == 0 {
return nil, errors.New("mock sm2 puk is empty")
}
if len(s.bc.Cmb.CmbSm2Pik) == 0 {
return nil, errors.New("mock cmb sm2 pik is empty")
}
encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.Sm2Puk, JsonParam: bizContent})
if err != nil {
return nil, err
}
req := &v1.CmbRequest{
Mid: s.bc.Cmb.Mid,
Aid: s.bc.Cmb.Aid,
Date: time.Now().Format("20060102150405"),
Random: string(cmb.RandomBytes(16)),
KeyAlias: s.bc.Cmb.KeyAlias,
CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
EncryptBody: encryptBody,
Sign: "",
}
sign, err := cmb.SignBody(cmb.SortStructStr(req), s.bc.Cmb.CmbSm2Pik)
if err != nil {
return nil, err
}
req.Sign = sign
return req, nil
}
func (s *CmbMixRepoImpl) VerifyResponse(_ context.Context, req *v1.CmbReply) error {
str := cmb.SortStructStr(req)
b, err := cmb.VerifyBody(str, req.Sign, s.bc.Cmb.CmbSm2Puk)
if err != nil {
return err
}
if !b {
return errors.New("签名验证失败")
}
return nil
}
func (s *CmbMixRepoImpl) GetResponse(_ context.Context, reqBo *bo.CmbResponseBo) (*v1.CmbReply, error) {
reply := &v1.CmbReply{
RespCode: reqBo.RespCode,
RespMsg: reqBo.RespMsg,
Date: time.Now().Format("20060102150405"),
KeyAlias: s.bc.Cmb.KeyAlias,
CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
EncryptBody: "",
Sign: "",
}
if len(reqBo.BizContent) > 0 {
encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.CmbSm2Puk, JsonParam: reqBo.BizContent})
if err != nil {
return nil, err
}
reply.EncryptBody = encryptBody
}
sign, err := cmb.SignBody(cmb.SortStructStr(reply), s.bc.Cmb.Sm2Prk)
if err != nil {
return nil, err
}
reply.Sign = sign
return reply, nil
}
func (s *CmbMixRepoImpl) Request(ctx context.Context, req *v1.CmbRequest, uri string) (*v1.CmbReply, error) {
kvRows := helper.SortStructFieldsByKey(req)
uv := url.Values{}
for _, kv := range kvRows {
uv.Set(kv.Key, fmt.Sprintf("%v", kv.Value))
}
h := http.Header{
"Content-Type": []string{"application/x-www-form-urlencoded"},
}
r := uri + "?" + uv.Encode()
_, bodyBytes, err := request.Post(ctx, r, nil, request.WithHeaders(h), request.WithTimeout(time.Second*20))
if err != nil {
log.Errorf("请求掌上生活报错,url:%s,err:%v", r, err)
return nil, err
}
var response *v1.CmbReply
if err = json.Unmarshal(bodyBytes, &response); err != nil {
log.Errorf("请求掌上生活返回数据解析报错:%s,url:%s,bodyBytes:%s", err.Error(), r, string(bodyBytes))
return nil, err
}
if response.RespCode != vo.CmbResponseStatusSuccess.GetValue() {
log.Errorf("请求掌上生活返回报错:msg:%s,url:%s,bodyBytes:%s", response.RespMsg, r, string(bodyBytes))
return nil, fmt.Errorf(response.RespMsg)
}
return response, nil
}
func (s *CmbMixRepoImpl) Decrypt(_ context.Context, encryptBody string) (string, error) {
if len(s.bc.Cmb.CmbSm2Pik) == 0 {
return "", errors.New("mock CmbSm2Pik is empty")
}
rs, err := cmb.DecryptBody(&cmb.Decrypts{EncryptBody: encryptBody, PrivateKey: s.bc.Cmb.CmbSm2Pik})
if err != nil {
return "", err
}
return string(rs), nil
}

View File

@ -1,44 +0,0 @@
package mixrepoimpl
import (
"context"
"fmt"
"voucher/internal/biz/mixrepos"
"voucher/internal/conf"
"voucher/internal/pkg/ding"
)
type DingMixRepoImpl struct {
bc *conf.Bootstrap
client *ding.TalkClient
}
func NewDingMixRepoImpl(bc *conf.Bootstrap) mixrepos.DingMixRepo {
client := ding.NewDingTalkClient(bc.Alarm.WebhookURL, bc.Alarm.Secret)
return &DingMixRepoImpl{bc: bc, client: client}
}
func (s *DingMixRepoImpl) SendMessage(_ context.Context, title, text string) error {
if err := s.client.SendMarkdownMessage(title, text, s.bc.Alarm.WarningMobiles, false); err != nil {
return fmt.Errorf("markdown 消息发送失败: %v", err)
}
return nil
}
func (s *DingMixRepoImpl) SendMarkdownMessage(_ context.Context, title, text string) error {
isAtAll := false
if len(s.bc.Alarm.AtMobiles) == 0 {
isAtAll = true
}
if err := s.client.SendMarkdownMessage(title, text, s.bc.Alarm.AtMobiles, isAtAll); err != nil {
return fmt.Errorf("markdown 消息发送失败: %v", err)
}
return nil
}

View File

@ -1,46 +0,0 @@
package mixrepoimpl
import (
"context"
"fmt"
"github.com/bwmarrin/snowflake"
"os"
"voucher/internal/biz/mixrepos"
"voucher/internal/pkg/helper"
)
type GenerateRepoImpl struct {
node *snowflake.Node
}
func NewGenerateMixRepoImpl() (mixrepos.GenerateMixRepo, error) {
g := &GenerateRepoImpl{}
name, err := os.Hostname()
if err != nil {
return nil, err
}
serverId := helper.HashMod(name)
node, err := snowflake.NewNode(int64(serverId))
if err != nil {
return nil, err
}
g.node = node
return g, nil
}
// GeneratorString 生成字符串
func (s *GenerateRepoImpl) GeneratorString(_ context.Context, uid string) string {
id := helper.HashMod(uid)
return fmt.Sprintf("%s%d", s.node.Generate().String(), id)
}
// GeneratorNumber 生成 int64
func (s *GenerateRepoImpl) GeneratorNumber(_ context.Context, uid string) int64 {
id := helper.HashMod(uid)
return s.node.Generate().Int64() + int64(id)
}

View File

@ -1,14 +0,0 @@
package mixrepoimpl
import (
"github.com/google/wire"
)
// ProviderMixRepoImplSet is providers.
var ProviderMixRepoImplSet = wire.NewSet(
NewGenerateMixRepoImpl,
NewMQSendMixRepoImpl,
NewCmbMixRepoImpl,
NewDingMixRepoImpl,
NewSmsMixRepoImpl,
)

View File

@ -1,35 +0,0 @@
package mixrepoimpl
import (
"context"
"voucher/internal/biz/mixrepos"
"voucher/internal/conf"
"voucher/internal/pkg/sms"
)
type SmsMixRepoImpl struct {
smsService sms.Service
}
func NewSmsMixRepoImpl(bc *conf.Bootstrap) (mixrepos.SmsMixRepo, error) {
config := sms.Config{
AccessKeyID: bc.AliYunSms.AccessKeyId,
AccessKeySecret: bc.AliYunSms.AccessKeySecret,
Endpoint: bc.AliYunSms.Endpoint,
SignName: bc.AliYunSms.SignName,
RetryTimes: 1,
Timeout: 15,
}
smsService, err := sms.NewService(config)
if err != nil {
return nil, err
}
return &SmsMixRepoImpl{smsService: smsService}, nil
}
func (s *SmsMixRepoImpl) Send(ctx context.Context, phoneNumbers []string, templateCode string, params map[string]string) error {
return s.smsService.SendSMS(ctx, phoneNumbers, templateCode, params)
}

View File

@ -12,28 +12,17 @@ const TableNameOrder = "order"
// Order mapped from table <order>
type Order struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
OrderNo string `gorm:"column:order_no;not null" json:"order_no"`
VoucherNo string `gorm:"column:voucher_no;not null" json:"voucher_no"`
OutBizNo string `gorm:"column:out_biz_no;not null;comment:外部交易号" json:"out_biz_no"` // 外部交易号
ProductNo string `gorm:"column:product_no;not null;comment:商品编号" json:"product_no"` // 商品编号
BatchNo string `gorm:"column:batch_no;not null;comment:立减金批次号" json:"batch_no"` // 立减金批次号
ActivityId string `gorm:"column:activity_id;not null;comment:activity_id" json:"activity_id"` // activity_id
Account string `gorm:"column:account;not null;comment:充值账号" json:"account"` // 充值账号
AccountType uint8 `gorm:"column:account_type;not null;comment:1:oepnid/userid 2:手机号" json:"account_type"` // 1:oepnid/userid 2:手机号
Type uint8 `gorm:"column:type;not null;comment:1:招行" json:"type"`
Status uint8 `gorm:"column:status;not null;comment:1:待发放 2:发放中 3:发放成功 4:发放失败" json:"status"` // 1:待发放 2:发放中 3:发放成功 4:发放失败
AppID string `gorm:"column:app_id;not null;comment:批次所属应用" json:"app_id"` // 批次所属应用
MerchantNo string `gorm:"column:merchant_no;not null;comment:创建批次号的商户号" json:"merchant_no"` // 创建批次号的商户号
NotifyUrl string `gorm:"column:notify_url;not null;comment:回调地址" json:"notify_url"`
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
Remark string `gorm:"column:remark;not null;comment:remark" json:"remark"`
Attach string `gorm:"column:attach;not null;comment:attach" json:"attach"`
ReceiveSuccessTime *time.Time `gorm:"column:receive_success_time" json:"receive_success_time"`
LastUseTime *time.Time `gorm:"column:last_use_time" json:"last_use_time"`
TransactionId string `gorm:"column:transaction_id;not null" json:"transaction_id"`
CreateTime *time.Time `gorm:"column:create_time" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName Order's table name

View File

@ -1,42 +0,0 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameOrderBak = "order_bak"
// Order mapped from table <order>
type OrderBak struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
OrderNo string `gorm:"column:order_no;not null" json:"order_no"`
VoucherNo string `gorm:"column:voucher_no;not null" json:"voucher_no"`
OutBizNo string `gorm:"column:out_biz_no;not null;comment:外部交易号" json:"out_biz_no"` // 外部交易号
ProductNo string `gorm:"column:product_no;not null;comment:商品编号" json:"product_no"` // 商品编号
BatchNo string `gorm:"column:batch_no;not null;comment:立减金批次号" json:"batch_no"` // 立减金批次号
ActivityId string `gorm:"column:activity_id;not null;comment:activity_id" json:"activity_id"` // activity_id
Account string `gorm:"column:account;not null;comment:充值账号" json:"account"` // 充值账号
AccountType uint8 `gorm:"column:account_type;not null;comment:1:oepnid/userid 2:手机号" json:"account_type"` // 1:oepnid/userid 2:手机号
Type uint8 `gorm:"column:type;not null;comment:1:招行" json:"type"`
Status uint8 `gorm:"column:status;not null;comment:1:待发放 2:发放中 3:发放成功 4:发放失败" json:"status"` // 1:待发放 2:发放中 3:发放成功 4:发放失败
AppID string `gorm:"column:app_id;not null;comment:批次所属应用" json:"app_id"` // 批次所属应用
MerchantNo string `gorm:"column:merchant_no;not null;comment:创建批次号的商户号" json:"merchant_no"` // 创建批次号的商户号
NotifyUrl string `gorm:"column:notify_url;not null;comment:回调地址" json:"notify_url"`
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
Remark string `gorm:"column:remark;not null;comment:remark" json:"remark"`
Attach string `gorm:"column:attach;not null;comment:attach" json:"attach"`
ReceiveSuccessTime *time.Time `gorm:"column:receive_success_time" json:"receive_success_time"`
LastUseTime *time.Time `gorm:"column:last_use_time" json:"last_use_time"`
TransactionId string `gorm:"column:transaction_id;not null" json:"transaction_id"`
CreateTime *time.Time `gorm:"column:create_time" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName Order's table name
func (*OrderBak) TableName() string {
return TableNameOrderBak
}

View File

@ -1,32 +0,0 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameOrderNotify = "order_notify"
// OrderNotify mapped from table <order_notify>
type OrderNotify struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
OrderNo string `gorm:"column:order_no;not null" json:"order_no"`
Status uint8 `gorm:"column:status;not null;comment:状态" json:"status"`
Event uint8 `gorm:"column:event;not null;comment:event" json:"event"`
Channel uint8 `gorm:"column:channel;not null;comment:channel" json:"channel"`
Type uint8 `gorm:"column:type;not null;comment:1:招行" json:"type"`
Request string `gorm:"column:request;not null" json:"request"`
Responses string `gorm:"column:responses" json:"responses"`
Remark string `gorm:"column:remark" json:"remark"`
NotifyUrl string `gorm:"column:notify_url;not null;comment:回调地址" json:"notify_url"`
CreateTime *time.Time `gorm:"column:create_time;not null" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName OrderNotify's table name
func (*OrderNotify) TableName() string {
return TableNameOrderNotify
}

View File

@ -1,39 +0,0 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameProduct = "product"
// Product mapped from table <product>
type Product struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
Name string `gorm:"column:name;not null;comment:商品名称" json:"name"` // 商品名称
ProductNo string `gorm:"column:product_no;not null;comment:商品编号" json:"product_no"` // 商品编号
BatchName string `gorm:"column:batch_name;not null;comment:批次名称" json:"batch_name"` // 批次名称
BatchNo string `gorm:"column:batch_no;not null;comment:立减金批次号" json:"batch_no"` // 立减金批次号
ActivityId string `gorm:"column:activity_id;not null;comment:activity_id" json:"activity_id"` // activity_id
MchId string `gorm:"column:mch_id;not null;comment:商户号,创建批次的商户号" json:"mch_id"` // 商户号,创建批次的商户号
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
AvailableType uint8 `gorm:"column:available_type;not null;comment:1:固定有效期 2:动态有效期" json:"available_type"`
AvailableDays uint32 `gorm:"column:available_days;not null;comment:领取后多少天内" json:"available_days"`
Amount int64 `gorm:"column:amount;not null;default:0" json:"amount"`
AllBudget int64 `gorm:"column:all_budget;not null;default:0" json:"all_budget"`
AvailableBudget int64 `gorm:"column:available_budget;not null;default:0" json:"available_budget"`
WarningBudget int64 `gorm:"column:warning_budget;not null;default:0" json:"warning_budget"` // 预警预算=0不做预警
WarningPerson string `gorm:"column:warning_person" json:"warning_person"`
StartTime *time.Time `gorm:"column:start_time;not null" json:"start_time"`
EndTime *time.Time `gorm:"column:end_time;not null" json:"end_time"`
CreateTime *time.Time `gorm:"column:create_time;not null" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName Product's table name
func (*Product) TableName() string {
return TableNameProduct
}

View File

@ -1,28 +0,0 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameWechatNotifyRegisterTag = "wechat_notify_register_tag"
// WechatNotifyRegisterTag mapped from table <wechat_notify_register_tag>
type WechatNotifyRegisterTag struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
StockID string `gorm:"column:stock_id;not null" json:"stock_id"`
StockCreatorMchID string `gorm:"column:stock_creator_mch_id;not null" json:"stock_creator_mch_id"`
Tag string `gorm:"column:tag;not null" json:"tag"`
Status uint8 `gorm:"column:status;not null" json:"status"`
Remark string `gorm:"column:remark;not null" json:"remark"`
CreateTime *time.Time `gorm:"column:create_time;not null" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName WechatNotifyRegisterTag's table name
func (*WechatNotifyRegisterTag) TableName() string {
return TableNameWechatNotifyRegisterTag
}

View File

@ -33,10 +33,8 @@ func buildMqProducer(c *conf.RocketMQ) (*mq.Producer, error) {
if c == nil {
return nil, nil
}
var p *mq.Producer
var err error
if c.AccessKey != "" && c.SecretKey != "" {
p, err = mq.NewProducer(c.Addr, producer.WithCredentials(primitive.Credentials{
AccessKey: c.AccessKey,
@ -45,18 +43,15 @@ func buildMqProducer(c *conf.RocketMQ) (*mq.Producer, error) {
} else {
p, err = mq.NewProducer(c.Addr)
}
if err != nil {
fmt.Println("创建 rocketMQ producer 失败: ", err)
return nil, err
}
err = p.Start()
if err != nil {
fmt.Println("rocketMQ producer start 失败: ", err)
return nil, err
}
//此时并没有发起连接,在使用时才会
return p, nil
}

Some files were not shown because too many files have changed in this diff Show More