new
This commit is contained in:
31
app/main/api/Dockerfile
Normal file
31
app/main/api/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
FROM golang:1.22.4-alpine AS builder
|
||||
|
||||
LABEL stage=gobuilder
|
||||
|
||||
ENV CGO_ENABLED 0
|
||||
ENV GOPROXY https://goproxy.cn,direct
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
|
||||
RUN apk update --no-cache && apk add --no-cache tzdata
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
ADD go.mod .
|
||||
ADD go.sum .
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
COPY app/main/api/etc /app/etc
|
||||
RUN go build -ldflags="-s -w" -o /app/main app/main/api/main.go
|
||||
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
|
||||
ENV TZ Asia/Shanghai
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/main /app/main
|
||||
COPY --from=builder /app/etc /app/etc
|
||||
|
||||
CMD ["./main", "-f", "etc/main.yaml"]
|
||||
BIN
app/main/api/__debug_bin796033340.exe
Normal file
BIN
app/main/api/__debug_bin796033340.exe
Normal file
Binary file not shown.
16
app/main/api/desc/front/auth/auth.api
Normal file
16
app/main/api/desc/front/auth/auth.api
Normal file
@@ -0,0 +1,16 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "认证服务"
|
||||
desc: "认证服务"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
)
|
||||
|
||||
type (
|
||||
sendSmsReq {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
ActionType string `json:"actionType" validate:"required,oneof=login register query"`
|
||||
}
|
||||
)
|
||||
|
||||
45
app/main/api/desc/front/pay.api
Normal file
45
app/main/api/desc/front/pay.api
Normal file
@@ -0,0 +1,45 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "支付服务"
|
||||
desc: "支付服务"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
version: "v1"
|
||||
)
|
||||
import (
|
||||
"pay/pay.api"
|
||||
)
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: pay
|
||||
)
|
||||
service main {
|
||||
|
||||
// 微信支付回调
|
||||
@handler WechatPayCallback
|
||||
post /pay/wechat/callback
|
||||
|
||||
// 支付宝支付回调
|
||||
@handler AlipayCallback
|
||||
post /pay/alipay/callback
|
||||
|
||||
// 微信退款回调
|
||||
@handler WechatPayRefundCallback
|
||||
post /pay/wechat/refund_callback
|
||||
}
|
||||
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: pay
|
||||
jwt: JwtAuth
|
||||
middleware: SourceInterceptor
|
||||
)
|
||||
service main {
|
||||
// 支付
|
||||
@handler Payment
|
||||
post /pay/payment (PaymentReq) returns (PaymentResp)
|
||||
|
||||
@handler IapCallback
|
||||
post /pay/iap_callback (IapCallbackReq)
|
||||
}
|
||||
28
app/main/api/desc/front/pay/pay.api
Normal file
28
app/main/api/desc/front/pay/pay.api
Normal file
@@ -0,0 +1,28 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "产品支付服务"
|
||||
desc: "产品支付服务"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
)
|
||||
|
||||
type (
|
||||
PaymentReq {
|
||||
Id string `json:"id"`
|
||||
PayMethod string `json:"pay_method"`
|
||||
}
|
||||
PaymentResp {
|
||||
prepayData interface{} `json:"prepay_data"`
|
||||
prepayId string `json:"prepay_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
IapCallbackReq {
|
||||
OrderID int64 `json:"order_id" validate:"required"`
|
||||
TransactionReceipt string `json:"transaction_receipt" validate:"required"`
|
||||
}
|
||||
)
|
||||
|
||||
35
app/main/api/desc/front/product.api
Normal file
35
app/main/api/desc/front/product.api
Normal file
@@ -0,0 +1,35 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "产品服务"
|
||||
desc: "产品服务"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
version: "v1"
|
||||
)
|
||||
import (
|
||||
"product/product.api"
|
||||
)
|
||||
@server (
|
||||
prefix: api/v1/product
|
||||
group: product
|
||||
jwt: JwtAuth
|
||||
)
|
||||
service main {
|
||||
@handler GetProductByID
|
||||
get /:id (GetProductByIDRequest) returns (ProductResponse)
|
||||
|
||||
@handler GetProductByEn
|
||||
get /en/:product_en (GetProductByEnRequest) returns (ProductResponse)
|
||||
}
|
||||
|
||||
@server (
|
||||
prefix: api/v1/product
|
||||
group: product
|
||||
middleware: SourceInterceptor
|
||||
)
|
||||
service main {
|
||||
@handler GetProductRenderList
|
||||
get /render_list/:module (GetProductRenderListRequest) returns (GetProductRenderListResponse)
|
||||
}
|
||||
|
||||
43
app/main/api/desc/front/product/product.api
Normal file
43
app/main/api/desc/front/product/product.api
Normal file
@@ -0,0 +1,43 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "产品查询服务"
|
||||
desc: "产品查询服务"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
)
|
||||
|
||||
type Product {
|
||||
ProductName string `json:"product_name"`
|
||||
ProductEn string `json:"product_en"`
|
||||
Description string `json:"description"`
|
||||
Notes string `json:"notes,optional"`
|
||||
SellPrice float64 `json:"sell_price"`
|
||||
Features []Feature `json:"features"` // 关联功能列表
|
||||
}
|
||||
|
||||
type Feature {
|
||||
ID int64 `json:"id"` // 功能ID
|
||||
ApiID string `json:"api_id"` // API标识
|
||||
Name string `json:"name"` // 功能描述
|
||||
}
|
||||
|
||||
type GetProductByIDRequest {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
|
||||
type GetProductByEnRequest {
|
||||
ProductEn string `path:"product_en"`
|
||||
}
|
||||
|
||||
type ProductResponse {
|
||||
Product
|
||||
}
|
||||
|
||||
type GetProductRenderListRequest{
|
||||
module string `path:"module"`
|
||||
}
|
||||
|
||||
type GetProductRenderListResponse {
|
||||
Product []Product
|
||||
}
|
||||
74
app/main/api/desc/front/query.api
Normal file
74
app/main/api/desc/front/query.api
Normal file
@@ -0,0 +1,74 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "产品查询服务"
|
||||
desc: "产品查询服务"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
version: "v1"
|
||||
)
|
||||
|
||||
import (
|
||||
"query/query.api"
|
||||
)
|
||||
|
||||
//============================> query v1 <============================
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: query
|
||||
jwt: JwtAuth
|
||||
)
|
||||
service main {
|
||||
@doc "query service"
|
||||
@handler queryService
|
||||
post /query/service/:product (QueryServiceReq) returns (QueryServiceResp)
|
||||
}
|
||||
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: query
|
||||
jwt: JwtAuth
|
||||
)
|
||||
service main {
|
||||
@doc "获取查询临时订单"
|
||||
@handler queryProvisionalOrder
|
||||
get /query/provisional_order/:id (QueryProvisionalOrderReq) returns (QueryProvisionalOrderResp)
|
||||
|
||||
@doc "查询示例"
|
||||
@handler queryExample
|
||||
get /query/example (QueryExampleReq) returns (QueryExampleResp)
|
||||
|
||||
@doc "查询列表"
|
||||
@handler queryList
|
||||
get /query/list (QueryListReq) returns (QueryListResp)
|
||||
|
||||
@doc "查询详情 按订单号 付款查询时"
|
||||
@handler queryDetailByOrderId
|
||||
get /query/orderId/:order_id (QueryDetailByOrderIdReq) returns (QueryDetailByOrderIdResp)
|
||||
|
||||
@doc "查询详情 按订单号"
|
||||
@handler queryDetailByOrderNo
|
||||
get /query/orderNo/:order_no (QueryDetailByOrderNoReq) returns (QueryDetailByOrderNoResp)
|
||||
|
||||
@doc "查询详情"
|
||||
@handler queryDetail
|
||||
get /query/:id (QueryDetailReq) returns (QueryDetailResp)
|
||||
|
||||
@doc "重试查询"
|
||||
@handler queryRetry
|
||||
post /query/retry/:id (QueryRetryReq) returns (QueryRetryResp)
|
||||
}
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: query
|
||||
)
|
||||
service main {
|
||||
@handler querySingleTest
|
||||
post /query/single/test (QuerySingleTestReq) returns (QuerySingleTestResp)
|
||||
|
||||
|
||||
@doc "更新查询数据"
|
||||
@handler updateQueryData
|
||||
post /query/update_data (UpdateQueryDataReq) returns (UpdateQueryDataResp)
|
||||
}
|
||||
|
||||
128
app/main/api/desc/front/query/query.api
Normal file
128
app/main/api/desc/front/query/query.api
Normal file
@@ -0,0 +1,128 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "产品查询服务"
|
||||
desc: "产品查询服务"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
)
|
||||
|
||||
type (
|
||||
QueryReq {
|
||||
Data string `json:"data" validate:"required"`
|
||||
}
|
||||
QueryResp {
|
||||
id string `json:"id"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
QueryServiceReq {
|
||||
Product string `path:"product"`
|
||||
Data string `json:"data" validate:"required"`
|
||||
}
|
||||
QueryServiceResp {
|
||||
id string `json:"id"`
|
||||
}
|
||||
)
|
||||
|
||||
type Query {
|
||||
Id int64 `json:"id"` // 主键ID
|
||||
OrderId int64 `json:"order_id"` // 订单ID
|
||||
UserId int64 `json:"user_id"` // 用户ID
|
||||
ProductName string `json:"product_name"` // 产品ID
|
||||
QueryParams map[string]interface{} `json:"query_params"`
|
||||
QueryData []QueryItem `json:"query_data"`
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
QueryState string `json:"query_state"` // 查询状态
|
||||
}
|
||||
type QueryItem {
|
||||
Feature interface{} `json:"feature"`
|
||||
Data interface{} `json:"data"` // 这里可以是 map 或 具体的 struct
|
||||
}
|
||||
// 获取查询临时订单
|
||||
type (
|
||||
QueryProvisionalOrderReq {
|
||||
Id string `path:"id"`
|
||||
}
|
||||
QueryProvisionalOrderResp {
|
||||
QueryParams map[string]interface{} `json:"query_params"`
|
||||
Product Product `json:"product"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
QueryListReq {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"page_size"` // 每页数据量
|
||||
}
|
||||
QueryListResp {
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
List []Query `json:"list"` // 查询列表
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
QueryExampleReq {
|
||||
feature string `form:"feature"`
|
||||
}
|
||||
QueryExampleResp {
|
||||
Query
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
QueryDetailReq {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
QueryDetailResp {
|
||||
Query
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
QueryDetailByOrderIdReq {
|
||||
OrderId int64 `path:"order_id"`
|
||||
}
|
||||
QueryDetailByOrderIdResp {
|
||||
Query
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
QueryDetailByOrderNoReq {
|
||||
OrderNo string `path:"order_no"`
|
||||
}
|
||||
QueryDetailByOrderNoResp {
|
||||
Query
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
QueryRetryReq {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
QueryRetryResp {}
|
||||
)
|
||||
|
||||
type QuerySingleTestReq {
|
||||
params map[string]interface{} `json:"params"`
|
||||
Api string `json:"api"`
|
||||
}
|
||||
|
||||
type QuerySingleTestResp {
|
||||
Data interface{} `json:"data"`
|
||||
Api string `json:"api"`
|
||||
}
|
||||
|
||||
type (
|
||||
UpdateQueryDataReq {
|
||||
Id int64 `json:"id"` // 查询ID
|
||||
QueryData string `json:"query_data"` // 查询数据(未加密的JSON)
|
||||
}
|
||||
UpdateQueryDataResp {
|
||||
Id int64 `json:"id"`
|
||||
UpdatedAt string `json:"updated_at"` // 更新时间
|
||||
}
|
||||
)
|
||||
85
app/main/api/desc/front/user.api
Normal file
85
app/main/api/desc/front/user.api
Normal file
@@ -0,0 +1,85 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "用户中心服务"
|
||||
desc: "用户中心服务"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
version: "v1"
|
||||
)
|
||||
|
||||
import (
|
||||
"user/user.api"
|
||||
"auth/auth.api"
|
||||
)
|
||||
|
||||
//============================> user v1 <============================
|
||||
//no need login
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: user
|
||||
)
|
||||
service main {
|
||||
@doc "register"
|
||||
@handler register
|
||||
post /user/register (RegisterReq) returns (RegisterResp)
|
||||
|
||||
@doc "mobile login"
|
||||
@handler mobileLogin
|
||||
post /user/mobileLogin (MobileLoginReq) returns (MobileLoginResp)
|
||||
|
||||
@doc "mobile code login"
|
||||
@handler mobileCodeLogin
|
||||
post /user/mobileCodeLogin (MobileCodeLoginReq) returns (MobileCodeLoginResp)
|
||||
|
||||
@doc "wechat mini auth"
|
||||
@handler wxMiniAuth
|
||||
post /user/wxMiniAuth (WXMiniAuthReq) returns (WXMiniAuthResp)
|
||||
|
||||
@doc "wechat h5 auth"
|
||||
@handler wxH5Auth
|
||||
post /user/wxh5Auth (WXH5AuthReq) returns (WXH5AuthResp)
|
||||
|
||||
@handler DecryptMobile
|
||||
post /user/decryptMobile
|
||||
}
|
||||
|
||||
//need login
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: user
|
||||
jwt: JwtAuth
|
||||
)
|
||||
service main {
|
||||
@doc "get user info"
|
||||
@handler detail
|
||||
get /user/detail returns (UserInfoResp)
|
||||
|
||||
@doc "get new token"
|
||||
@handler getToken
|
||||
post /user/getToken returns (MobileCodeLoginResp)
|
||||
}
|
||||
|
||||
//============================> auth v1 <============================
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: auth
|
||||
)
|
||||
service main {
|
||||
@doc "get mobile verify code"
|
||||
@handler sendSms
|
||||
post /auth/sendSms (sendSmsReq)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//============================> notification v1 <============================
|
||||
@server (
|
||||
prefix: api/v1
|
||||
group: notification
|
||||
)
|
||||
service main {
|
||||
@doc "get notifications"
|
||||
@handler getNotifications
|
||||
get /notification/list returns (GetNotificationsResp)
|
||||
}
|
||||
95
app/main/api/desc/front/user/user.api
Normal file
95
app/main/api/desc/front/user/user.api
Normal file
@@ -0,0 +1,95 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "用户实例"
|
||||
desc: "用户实例"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
)
|
||||
|
||||
type User {
|
||||
Id int64 `json:"id"`
|
||||
Mobile string `json:"mobile"`
|
||||
NickName string `json:"nickName"`
|
||||
}
|
||||
|
||||
type (
|
||||
RegisterReq {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Password string `json:"password" validate:"required,min=11,max=11,password"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
RegisterResp {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
MobileLoginReq {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
MobileLoginResp {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
MobileCodeLoginReq {
|
||||
Mobile string `json:"mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
MobileCodeLoginResp {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
WXMiniAuthReq {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
WXMiniAuthResp {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
)
|
||||
type (
|
||||
WXH5AuthReq {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
WXH5AuthResp {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
)
|
||||
type (
|
||||
UserInfoResp {
|
||||
UserInfo User `json:"userInfo"`
|
||||
}
|
||||
)
|
||||
|
||||
//============================> notification v1 <============================
|
||||
type Notification {
|
||||
Title string `json:"title"` // 通知标题
|
||||
Content string `json:"content"` // 通知内容 (富文本)
|
||||
NotificationPage string `json:"notificationPage"` // 通知页面
|
||||
StartDate string `json:"startDate"` // 通知开始日期,格式 "YYYY-MM-DD"
|
||||
EndDate string `json:"endDate"` // 通知结束日期,格式 "YYYY-MM-DD"
|
||||
StartTime string `json:"startTime"` // 每天通知开始时间,格式 "HH:MM:SS"
|
||||
EndTime string `json:"endTime"` // 每天通知结束时间,格式 "HH:MM:SS"
|
||||
}
|
||||
type (
|
||||
// 获取通知响应体(分页)
|
||||
GetNotificationsResp {
|
||||
Notifications []Notification `json:"notifications"` // 通知列表
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
}
|
||||
)
|
||||
14
app/main/api/desc/main.api
Normal file
14
app/main/api/desc/main.api
Normal file
@@ -0,0 +1,14 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "Main Service"
|
||||
desc: "Main Service"
|
||||
author: "Liangzai"
|
||||
email: "2440983361@qq.com"
|
||||
version: "v1"
|
||||
)
|
||||
|
||||
import "./front/user.api"
|
||||
import "./front/query.api"
|
||||
import "./front/pay.api"
|
||||
import "./front/product.api"
|
||||
61
app/main/api/etc/main.dev.yaml
Normal file
61
app/main/api/etc/main.dev.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
Name: main
|
||||
Host: 0.0.0.0
|
||||
Port: 8888
|
||||
DataSource: "tyc:5vg67b3UNHu8@tcp(127.0.0.1:20001)/tyc?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
CacheRedis:
|
||||
- Host: "127.0.0.1:20002"
|
||||
Pass: "3m3WsgyCKWqz" # Redis 密码,如果未设置则留空
|
||||
Type: "node" # 单节点模式
|
||||
JwtAuth:
|
||||
AccessSecret: "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA"
|
||||
AccessExpire: 2592000
|
||||
RefreshAfter: 1296000
|
||||
VerifyCode:
|
||||
AccessKeyID: "LTAI5tHKcV1RbC8t68UfsATy"
|
||||
AccessKeySecret: "wLWjMBnAlchFMa9gC8B7ZVBKaew4t5"
|
||||
EndpointURL: "dysmsapi.aliyuncs.com"
|
||||
SignName: "天远查"
|
||||
TemplateCode: "SMS_468895843"
|
||||
ValidTime: 300
|
||||
Encrypt:
|
||||
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
|
||||
WestConfig:
|
||||
Url: "http://proxy.tianyuanapi.com/api/invoke"
|
||||
Key: "121a1e41fc1690dd6b90afbcacd80cf4"
|
||||
SecretId: "449159"
|
||||
SecretSecondId: "296804"
|
||||
YushanConfig:
|
||||
ApiKey: "4c566c4a4b543164535455685655316c"
|
||||
AcctID: "YSSJ843926726"
|
||||
Url: "http://proxy.tianyuanapi.com/credit-gw/service"
|
||||
TianjuConfig:
|
||||
ApiKey: "479bcac2a77b56e976d044ff2bd996f4"
|
||||
BaseURL: "https://apis.tianapi.com"
|
||||
Alipay:
|
||||
AppID: "2021004161631930"
|
||||
PrivateKey: "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCOh6r6Ce7ZgMf5mWqVsU8sYNdU0HRlZbgYUQ2wF65PzWU3vL52+LG89ATbtHeKUy6esuuaAyYhuDrqU9f6mUl5QJGsWCF6JqqyberVIMJfdsdOTbgooSSUBxIsMYfoRZPdsZ9dHomDxfi2oGWJdAnaruTxKw7W8EPJwqc9/vopzdxLsT8t/eMzr4jvHIBVHtkMbmgNJ8b05tqJ1FViK3Yt78YBkt3PLdyjLzqRuKlVpmNceBEhzNbg4menGBzpJHTWO4uGk0KzUgY7wCSfyjrA9IPyWnDqlhws+9LwFKgkNpqxmwGgMihLysDRFOD5D1VLfry/yYT22UrwAzXFQYUvAgMBAAECggEAfKGfXXMwG5sjwltnuyvHepvXzz9UeJHgz8qYeIHMl9WYC4wM1IWVDhAm62M8IIWqP3Ve1VapklTBl8PnGSfO+qr+dsC9zU1geBPrg6BYtxdrIkqNnWGDVbl1J5XMLMwih3nZS5j3UHXdjPxmrTH9p+7FHSm0mTiRXizLB0gdwk0E+H+btJKLpCvqqIoEucowzbjY/ch86ASojpQGZiWapbi+03Xke6/wlGCbNzgGmQQo/C6XAbxl2ZKJxS/yp0JxGfO6gPJYx3xkuf+Pwr0U3imN8yDB/xo0LVamoxQggWua+mQXt72J4zD2imIU7TB4JFJV3kUd+45KLOccOsOuCQKBgQDLfsBxU/LiNn+zW6yus+dCu7JzQSN2K57XF+PSXhQTSjIu4K4y8v4WZHdSwkAGQSU3MLq66QD7ELYHGDBakFFj3sX7z2CHshFlu2I/DYP+0LPVRlIblsSDFn6rYBLOPXc3VkuEFcA0PpO3vW6yF7h2d9OpGaiYmpTvXa/6oPLQfQKBgQCzTgxTTrzZjLXCv85fzX3aYu9ceP4N0jaOwveEMvKNClSomUIDfHLn4sUTmAvHBdJz4Xqy/7+v/YpI0ifC0KouX0t6h3+YA6n4hCvpBMlUVACtHUfx+mzjj+KLxEvmm6ZJCmV5DC3yYvaDTANafM3PzyQRs/jw5WPWywezFfooGwKBgQC4Fq9TFkWYHQNDJ0C9PqSL+y7BEwFYireED+maSl9Q4AMr4zfTgX0YlsRXSsEOp2paVivmoJixh3mUS26azwnCFir21LCXsSAJ7w0+yyRIpVa6LoZizO5zRCtNL3lzt6kcl2VzVRXubVnGk4kLdWf7TAVfaYXan6TyMcfcLDPdJQKBgCwtV6YS6T6kh4fjICLEi3SKGbVr7hRTrbOA3+EHeHE9kVw03mnjeKAfZDUOqiAwFAkPDd8aWg0vZ6nHdZpvNO7V9c/LoKlAhdlAH117G2uWgtAkPbyl2bw7kDKle5nm0ZJ/aD7pvExTC6+Pw+fAhijkPVvLPtODgRTD0zLRgF/vAoGBAMdxes5p7u+Eoa41c3Y39dGvY30QiiGPB4oWLTdmyh4VQfC7DU7ihUCRsk5w4i/BuTNNlJauWM2MMX7YeRBt6ncq59vBAU2OeLd6oRsOkE2ZkH7tNDnGxZuOZkiSFnYUQLMBPnVe4JkS7ZvRWSyuIi9SE8TDpTLbH1kfEmIMq4dR"
|
||||
AlipayPublicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomw6g4rBmCr/QoX3NI3DVLyDpkaUytZ2uFhdfQaegIDAuUfZfgpTCASlAtO82t8ISAbSOSyp9CUpwdGV4EYOiCBbLxMYB6taaHPiIjJ1zNT1EakJzWgU53hz1AVeABB9kdAvMqSvjH6KLoVupmqm4Li8ZwDW9M2ANAmyDfKgiF0Lt4aUUnaZktoCrTWTkpmtfRZCHNACj851IllvN2wyC4OL7dJq5UzOFxmn07Dy/2z4UAhaaSAyRVawpOui5AIYJTXZERLYL3KMyRnMuZoFq3xltzVTzRPM06nRa9RfeVNVwWVtGBIe/r8tcg5wyhI57KUszGNOmUIm/se6G2lnAQIDAQAB"
|
||||
IsProduction: true
|
||||
NotifyUrl: "https://6m4685017o.goho.co/api/v1/pay/alipay/callback"
|
||||
ReturnURL: "http://localhost:5678/inquire"
|
||||
Wxpay:
|
||||
AppID: "wxba8424db4771cc18"
|
||||
AppSecret: "89646203d7f76eb7aef0d926b9efffaa"
|
||||
MchID: "1682635136"
|
||||
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61"
|
||||
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f"
|
||||
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
|
||||
NotifyUrl: "https://6m4685017o.goho.co/api/v1/pay/wechat/callback"
|
||||
RefundNotifyUrl: "https://6m4685017o.goho.co/api/v1/wechat/refund_callback"
|
||||
Applepay:
|
||||
ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt"
|
||||
SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt"
|
||||
Sandbox: false
|
||||
BundleID: "com.allinone.check"
|
||||
IssuerID: "bf828d85-5269-4914-9660-c066e09cd6ef"
|
||||
KeyID: "LAY65829DQ"
|
||||
LoadPrivateKeyPath: "etc/merchant/AuthKey_LAY65829DQ.p8"
|
||||
Ali:
|
||||
Code: "d55b58829efb41c8aa8e86769cba4844"
|
||||
SystemConfig:
|
||||
ThreeVerify: false
|
||||
63
app/main/api/etc/main.yaml
Normal file
63
app/main/api/etc/main.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
Name: main
|
||||
Host: 0.0.0.0
|
||||
Port: 8888
|
||||
DataSource: "tyc:5vg67b3UNHu8@tcp(tyc_mysql:3306)/tyc?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
CacheRedis:
|
||||
- Host: "tyc_redis:6379"
|
||||
Pass: "3m3WsgyCKWqz" # Redis 密码,如果未设置则留空
|
||||
Type: "node" # 单节点模式
|
||||
|
||||
JwtAuth:
|
||||
AccessSecret: "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA"
|
||||
AccessExpire: 2592000
|
||||
RefreshAfter: 1296000
|
||||
|
||||
VerifyCode:
|
||||
AccessKeyID: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
||||
AccessKeySecret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
||||
EndpointURL: "dysmsapi.aliyuncs.com"
|
||||
SignName: "全能查"
|
||||
TemplateCode: "SMS_302641455"
|
||||
ValidTime: 300
|
||||
Encrypt:
|
||||
SecretKey: "ff83609b2b24fc73196aac3d3dfb874f"
|
||||
WestConfig:
|
||||
Url: "https://apimaster.westdex.com.cn/api/invoke"
|
||||
Key: "121a1e41fc1690dd6b90afbcacd80cf4"
|
||||
SecretId: "449159"
|
||||
SecretSecondId: "296804"
|
||||
YushanConfig:
|
||||
ApiKey: "4c566c4a4b543164535455685655316c"
|
||||
AcctID: "YSSJ843926726"
|
||||
Url: "https://api.yushanshuju.com/credit-gw/service"
|
||||
TianjuConfig:
|
||||
ApiKey: "479bcac2a77b56e976d044ff2bd996f4"
|
||||
BaseURL: "https://apis.tianapi.com"
|
||||
Alipay:
|
||||
AppID: "2021004161631930"
|
||||
PrivateKey: "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCOh6r6Ce7ZgMf5mWqVsU8sYNdU0HRlZbgYUQ2wF65PzWU3vL52+LG89ATbtHeKUy6esuuaAyYhuDrqU9f6mUl5QJGsWCF6JqqyberVIMJfdsdOTbgooSSUBxIsMYfoRZPdsZ9dHomDxfi2oGWJdAnaruTxKw7W8EPJwqc9/vopzdxLsT8t/eMzr4jvHIBVHtkMbmgNJ8b05tqJ1FViK3Yt78YBkt3PLdyjLzqRuKlVpmNceBEhzNbg4menGBzpJHTWO4uGk0KzUgY7wCSfyjrA9IPyWnDqlhws+9LwFKgkNpqxmwGgMihLysDRFOD5D1VLfry/yYT22UrwAzXFQYUvAgMBAAECggEAfKGfXXMwG5sjwltnuyvHepvXzz9UeJHgz8qYeIHMl9WYC4wM1IWVDhAm62M8IIWqP3Ve1VapklTBl8PnGSfO+qr+dsC9zU1geBPrg6BYtxdrIkqNnWGDVbl1J5XMLMwih3nZS5j3UHXdjPxmrTH9p+7FHSm0mTiRXizLB0gdwk0E+H+btJKLpCvqqIoEucowzbjY/ch86ASojpQGZiWapbi+03Xke6/wlGCbNzgGmQQo/C6XAbxl2ZKJxS/yp0JxGfO6gPJYx3xkuf+Pwr0U3imN8yDB/xo0LVamoxQggWua+mQXt72J4zD2imIU7TB4JFJV3kUd+45KLOccOsOuCQKBgQDLfsBxU/LiNn+zW6yus+dCu7JzQSN2K57XF+PSXhQTSjIu4K4y8v4WZHdSwkAGQSU3MLq66QD7ELYHGDBakFFj3sX7z2CHshFlu2I/DYP+0LPVRlIblsSDFn6rYBLOPXc3VkuEFcA0PpO3vW6yF7h2d9OpGaiYmpTvXa/6oPLQfQKBgQCzTgxTTrzZjLXCv85fzX3aYu9ceP4N0jaOwveEMvKNClSomUIDfHLn4sUTmAvHBdJz4Xqy/7+v/YpI0ifC0KouX0t6h3+YA6n4hCvpBMlUVACtHUfx+mzjj+KLxEvmm6ZJCmV5DC3yYvaDTANafM3PzyQRs/jw5WPWywezFfooGwKBgQC4Fq9TFkWYHQNDJ0C9PqSL+y7BEwFYireED+maSl9Q4AMr4zfTgX0YlsRXSsEOp2paVivmoJixh3mUS26azwnCFir21LCXsSAJ7w0+yyRIpVa6LoZizO5zRCtNL3lzt6kcl2VzVRXubVnGk4kLdWf7TAVfaYXan6TyMcfcLDPdJQKBgCwtV6YS6T6kh4fjICLEi3SKGbVr7hRTrbOA3+EHeHE9kVw03mnjeKAfZDUOqiAwFAkPDd8aWg0vZ6nHdZpvNO7V9c/LoKlAhdlAH117G2uWgtAkPbyl2bw7kDKle5nm0ZJ/aD7pvExTC6+Pw+fAhijkPVvLPtODgRTD0zLRgF/vAoGBAMdxes5p7u+Eoa41c3Y39dGvY30QiiGPB4oWLTdmyh4VQfC7DU7ihUCRsk5w4i/BuTNNlJauWM2MMX7YeRBt6ncq59vBAU2OeLd6oRsOkE2ZkH7tNDnGxZuOZkiSFnYUQLMBPnVe4JkS7ZvRWSyuIi9SE8TDpTLbH1kfEmIMq4dR"
|
||||
AlipayPublicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomw6g4rBmCr/QoX3NI3DVLyDpkaUytZ2uFhdfQaegIDAuUfZfgpTCASlAtO82t8ISAbSOSyp9CUpwdGV4EYOiCBbLxMYB6taaHPiIjJ1zNT1EakJzWgU53hz1AVeABB9kdAvMqSvjH6KLoVupmqm4Li8ZwDW9M2ANAmyDfKgiF0Lt4aUUnaZktoCrTWTkpmtfRZCHNACj851IllvN2wyC4OL7dJq5UzOFxmn07Dy/2z4UAhaaSAyRVawpOui5AIYJTXZERLYL3KMyRnMuZoFq3xltzVTzRPM06nRa9RfeVNVwWVtGBIe/r8tcg5wyhI57KUszGNOmUIm/se6G2lnAQIDAQAB"
|
||||
IsProduction: true
|
||||
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
|
||||
ReturnURL: "https://www.tianyuancha.cn/report"
|
||||
Wxpay:
|
||||
AppID: "wxba8424db4771cc18"
|
||||
AppSecret: "89646203d7f76eb7aef0d926b9efffaa"
|
||||
MchID: "1682635136"
|
||||
MchCertificateSerialNumber: "5369B8AEEBDCF7AF274510252E6A8C0659C30F61"
|
||||
MchApiv3Key: "e3ea4cf0765f1e71b01bb387dfcdbc9f"
|
||||
MchPrivateKeyPath: "etc/merchant/apiclient_key.pem"
|
||||
NotifyUrl: "https://www.tianyuancha.cn/api/v1/pay/wechat/callback"
|
||||
RefundNotifyUrl: "https://www.tianyuancha.cn/api/v1/wechat/refund_callback"
|
||||
Applepay:
|
||||
ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt"
|
||||
SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt"
|
||||
Sandbox: true
|
||||
BundleID: "com.allinone.check"
|
||||
IssuerID: "bf828d85-5269-4914-9660-c066e09cd6ef"
|
||||
KeyID: "LAY65829DQ"
|
||||
LoadPrivateKeyPath: "etc/merchant/AuthKey_LAY65829DQ.p8"
|
||||
Ali:
|
||||
Code: "d55b58829efb41c8aa8e86769cba4844"
|
||||
SystemConfig:
|
||||
ThreeVerify: false
|
||||
6
app/main/api/etc/merchant/AuthKey_LAY65829DQ.p8
Normal file
6
app/main/api/etc/merchant/AuthKey_LAY65829DQ.p8
Normal file
@@ -0,0 +1,6 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgkidSHV1OeJN84sDD
|
||||
xWLGIVjTyhn6sAQDyHfqKW6lxnGgCgYIKoZIzj0DAQehRANCAAQSAlAcuuuRNFqk
|
||||
aMPVpXxsiR/pwhyM62tFhdFsbULq1C7MItQxKVMKCiwz3r5rZZy7HcbkqL47LPZ1
|
||||
q6V8Wyop
|
||||
-----END PRIVATE KEY-----
|
||||
28
app/main/api/etc/merchant/apiclient_key.pem
Normal file
28
app/main/api/etc/merchant/apiclient_key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCP6fWm1vXXybH
|
||||
m3Ne6PjacGrN2+iMrzWZlzdHCZ31udDPqSUYaZ+78b441KZK/CJFQWeSJ/1h//A+
|
||||
BGsQDKvE/fj2QzN1KkOuQ8WJXNGpixE5uu5bv/QTN/ukurGdA1aO2aFCANumlOmB
|
||||
HkB/B2so57ii8iQQjwK2xM4r3oOU/IfcFGKL+9/QjLGFFp9PJXCDBCgrxxlZGaj1
|
||||
3wowlfVOzlaX94gemQsCYVkuAFIYMAnFHs9cKNZQIU80somW/yy2Gy38N6n7NnbD
|
||||
nvFSaq4GoDROqRgKbRZ5e706d/p7A3aS/2oRqq1jomUIugK8g++LmoHFTgfhfQkI
|
||||
v1aG/nPzAgMBAAECggEAD2RN31J2J42xm/V0YdviBCUOQXugZK1peN8jkSxw6Myt
|
||||
gBbuCo4sCw9vvD8VYjGyYXx6QXmLuV03YyKkfSQT5EsflBvlEu6jaEaUe3rwXhfX
|
||||
6JQoWPrP00oHVZk5g7CFBlK2VW2N+hgonIOSJr6mvhoGZlr7gphiZasYjx9Vm9N3
|
||||
Pbnfru5ttzplYNniwH3DF6ph8VmdbD1nnbWSKLXvHCsXQT2wBcnsIagIH3vyq6K1
|
||||
pc5abWsQJrixOPebpI8jD5w0HxHAqVLx58H/OC2zW/roAw1WS2AkueJ1j7dQ7Z0C
|
||||
mc9Xexz5gcAP0nMAQv+LP7iYqsa/niFhfcTFWfdxkQKBgQD5JkKNmInU2/IVYCwO
|
||||
c483MCSv1+MnbRXlb7vut8T0IupHTU6hCge6C3q3HsjbKSBn8bRChtPUzvw9JFxK
|
||||
QWKiQqQDPLDJ08AIKhfQD2JiLtoikkZN0bF6OTL+Soney1yGx51mlfHM194+PcCJ
|
||||
jF7iWdMVbcBwHbgydNxxIS5cKQKBgQDHlvQ4lw6gvLILpGK494/vNYSJP/Jmd66V
|
||||
3oSGYi84YRKTSwH4NlbBVVieb3Dv+pPugbsXEuFHBif7WsivbYgNTE9++8Yvt0gh
|
||||
duB1G4yh7m/ylQeSuipgQU9tozrU/15cWwmcCRV50wWXBGoVEM0kf7mzEKSxmjYk
|
||||
Qzko/zxSuwKBgQCY6Bc+SViFz3qSDdTcBaXma+CIHsmlH7ipd9px1kzEvEzl95cD
|
||||
FGHLl1H34qfIgUQHJvrHPXHyEBoT+CW/2MMM7DM2XV/ubctT92ln4pkxwqlTQExv
|
||||
Y/s1FLesAtj8Z/hgK0/5bprYab9WmZV5lTGCXzhB1XqeFE9AgCHuODv4iQKBgQC8
|
||||
g2uwd5ytXQydymokYk9klJvWNrvw5GHV1BJAC0Smb6lnzZTSqCBRAxdsrb1yLK7E
|
||||
u2vGY2K7/qiM1DZw23eBd+4t9gg+0VIjqXBfq+GsoNTDvtckUwnrWER5PY831ut9
|
||||
N89fvYS3SAUjmlvIAdKBAtKWusWTqiAxJ/05J7oGOQKBgB5PSr5i0LlupIbKui9t
|
||||
XtXnRqGPxxrZZUpTkyrGOAnlCz/zq2QiwFpBWo/NMHOp0KmxzJpQ8yEY2LWlRZ61
|
||||
Oc9m0J/HtPw3Ohi1treBosEVG/0NOI9Tq1Obny23N51MVibdW6zEIyGUp/DbFS8h
|
||||
5DljdOYX9IYIHHn3Ig4GeTGe
|
||||
-----END PRIVATE KEY-----
|
||||
90
app/main/api/internal/config/config.go
Normal file
90
app/main/api/internal/config/config.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
rest.RestConf
|
||||
DataSource string
|
||||
CacheRedis cache.CacheConf
|
||||
JwtAuth JwtAuth // JWT 鉴权相关配置
|
||||
VerifyCode VerifyCode
|
||||
Encrypt Encrypt
|
||||
Alipay AlipayConfig
|
||||
Wxpay WxpayConfig
|
||||
Applepay ApplepayConfig
|
||||
Ali AliConfig
|
||||
WestConfig WestConfig
|
||||
YushanConfig YushanConfig
|
||||
TianjuConfig TianjuConfig
|
||||
SystemConfig SystemConfig
|
||||
}
|
||||
|
||||
// JwtAuth 用于 JWT 鉴权配置
|
||||
type JwtAuth struct {
|
||||
AccessSecret string // JWT 密钥,用于签发 Token
|
||||
AccessExpire int64 // Token 过期时间,单位为秒
|
||||
RefreshAfter int64
|
||||
}
|
||||
type VerifyCode struct {
|
||||
AccessKeyID string
|
||||
AccessKeySecret string
|
||||
EndpointURL string
|
||||
SignName string
|
||||
TemplateCode string
|
||||
ValidTime int
|
||||
}
|
||||
type Encrypt struct {
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
type AlipayConfig struct {
|
||||
AppID string
|
||||
PrivateKey string
|
||||
AlipayPublicKey string
|
||||
IsProduction bool
|
||||
NotifyUrl string
|
||||
ReturnURL string
|
||||
}
|
||||
type WxpayConfig struct {
|
||||
AppID string
|
||||
AppSecret string
|
||||
MchID string
|
||||
MchCertificateSerialNumber string
|
||||
MchApiv3Key string
|
||||
MchPrivateKeyPath string
|
||||
NotifyUrl string
|
||||
RefundNotifyUrl string
|
||||
}
|
||||
type AliConfig struct {
|
||||
Code string
|
||||
}
|
||||
type ApplepayConfig struct {
|
||||
ProductionVerifyURL string
|
||||
SandboxVerifyURL string // 沙盒环境的验证 URL
|
||||
Sandbox bool
|
||||
BundleID string
|
||||
IssuerID string
|
||||
KeyID string
|
||||
LoadPrivateKeyPath string
|
||||
}
|
||||
type WestConfig struct {
|
||||
Url string
|
||||
Key string
|
||||
SecretId string
|
||||
SecretSecondId string
|
||||
}
|
||||
type YushanConfig struct {
|
||||
ApiKey string
|
||||
AcctID string
|
||||
Url string
|
||||
}
|
||||
type TianjuConfig struct {
|
||||
ApiKey string
|
||||
BaseURL string
|
||||
}
|
||||
type SystemConfig struct {
|
||||
ThreeVerify bool
|
||||
}
|
||||
30
app/main/api/internal/handler/auth/sendsmshandler.go
Normal file
30
app/main/api/internal/handler/auth/sendsmshandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/auth"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func SendSmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.SendSmsReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := auth.NewSendSmsLogic(r.Context(), svcCtx)
|
||||
err := l.SendSms(&req)
|
||||
result.HttpResult(r, w, nil, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/notification"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/common/result"
|
||||
)
|
||||
|
||||
func GetNotificationsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := notification.NewGetNotificationsLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetNotifications()
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
17
app/main/api/internal/handler/pay/alipaycallbackhandler.go
Normal file
17
app/main/api/internal/handler/pay/alipaycallbackhandler.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/pay"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/common/result"
|
||||
)
|
||||
|
||||
func AlipayCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := pay.NewAlipayCallbackLogic(r.Context(), svcCtx)
|
||||
err := l.AlipayCallback(w, r)
|
||||
result.HttpResult(r, w, nil, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/pay/iapcallbackhandler.go
Normal file
30
app/main/api/internal/handler/pay/iapcallbackhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/pay"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func IapCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.IapCallbackReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := pay.NewIapCallbackLogic(r.Context(), svcCtx)
|
||||
err := l.IapCallback(&req)
|
||||
result.HttpResult(r, w, nil, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/pay/paymenthandler.go
Normal file
30
app/main/api/internal/handler/pay/paymenthandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/pay"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func PaymentHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.PaymentReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := pay.NewPaymentLogic(r.Context(), svcCtx)
|
||||
resp, err := l.Payment(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/pay"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/common/result"
|
||||
)
|
||||
|
||||
func WechatPayCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := pay.NewWechatPayCallbackLogic(r.Context(), svcCtx)
|
||||
err := l.WechatPayCallback(w, r)
|
||||
result.HttpResult(r, w, nil, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/pay"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/common/result"
|
||||
)
|
||||
|
||||
func WechatPayRefundCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := pay.NewWechatPayRefundCallbackLogic(r.Context(), svcCtx)
|
||||
err := l.WechatPayRefundCallback(w, r)
|
||||
result.HttpResult(r, w, nil, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/product"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func GetProductByEnHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.GetProductByEnRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := product.NewGetProductByEnLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetProductByEn(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/product"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func GetProductByIDHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.GetProductByIDRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := product.NewGetProductByIDLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetProductByID(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/product"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func GetProductRenderListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.GetProductRenderListRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := product.NewGetProductRenderListLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetProductRenderList(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QueryDetailByOrderIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryDetailByOrderIdReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQueryDetailByOrderIdLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryDetailByOrderId(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QueryDetailByOrderNoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryDetailByOrderNoReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQueryDetailByOrderNoLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryDetailByOrderNo(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/query/querydetailhandler.go
Normal file
30
app/main/api/internal/handler/query/querydetailhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QueryDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryDetailReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQueryDetailLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryDetail(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/query/queryexamplehandler.go
Normal file
30
app/main/api/internal/handler/query/queryexamplehandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QueryExampleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryExampleReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQueryExampleLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryExample(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/query/querylisthandler.go
Normal file
30
app/main/api/internal/handler/query/querylisthandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QueryListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryListReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQueryListLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryList(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QueryProvisionalOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryProvisionalOrderReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQueryProvisionalOrderLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryProvisionalOrder(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/query/queryretryhandler.go
Normal file
30
app/main/api/internal/handler/query/queryretryhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QueryRetryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryRetryReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQueryRetryLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryRetry(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
25
app/main/api/internal/handler/query/queryservicehandler.go
Normal file
25
app/main/api/internal/handler/query/queryservicehandler.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QueryServiceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryServiceReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQueryServiceLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryService(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func QuerySingleTestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QuerySingleTestReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewQuerySingleTestLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QuerySingleTest(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/query"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func UpdateQueryDataHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.UpdateQueryDataReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := query.NewUpdateQueryDataLogic(r.Context(), svcCtx)
|
||||
resp, err := l.UpdateQueryData(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
253
app/main/api/internal/handler/routes.go
Normal file
253
app/main/api/internal/handler/routes.go
Normal file
@@ -0,0 +1,253 @@
|
||||
// Code generated by goctl. DO NOT EDIT.
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
auth "tyc-server/app/main/api/internal/handler/auth"
|
||||
notification "tyc-server/app/main/api/internal/handler/notification"
|
||||
pay "tyc-server/app/main/api/internal/handler/pay"
|
||||
product "tyc-server/app/main/api/internal/handler/product"
|
||||
query "tyc-server/app/main/api/internal/handler/query"
|
||||
user "tyc-server/app/main/api/internal/handler/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
// get mobile verify code
|
||||
Method: http.MethodPost,
|
||||
Path: "/auth/sendSms",
|
||||
Handler: auth.SendSmsHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
// get notifications
|
||||
Method: http.MethodGet,
|
||||
Path: "/notification/list",
|
||||
Handler: notification.GetNotificationsHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/pay/alipay/callback",
|
||||
Handler: pay.AlipayCallbackHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/pay/wechat/callback",
|
||||
Handler: pay.WechatPayCallbackHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/pay/wechat/refund_callback",
|
||||
Handler: pay.WechatPayRefundCallbackHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
rest.WithMiddlewares(
|
||||
[]rest.Middleware{serverCtx.SourceInterceptor},
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/pay/iap_callback",
|
||||
Handler: pay.IapCallbackHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/pay/payment",
|
||||
Handler: pay.PaymentHandler(serverCtx),
|
||||
},
|
||||
}...,
|
||||
),
|
||||
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/:id",
|
||||
Handler: product.GetProductByIDHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/en/:product_en",
|
||||
Handler: product.GetProductByEnHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
||||
rest.WithPrefix("/api/v1/product"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
rest.WithMiddlewares(
|
||||
[]rest.Middleware{serverCtx.SourceInterceptor},
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/render_list/:module",
|
||||
Handler: product.GetProductRenderListHandler(serverCtx),
|
||||
},
|
||||
}...,
|
||||
),
|
||||
rest.WithPrefix("/api/v1/product"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
// query service
|
||||
Method: http.MethodPost,
|
||||
Path: "/query/service/:product",
|
||||
Handler: query.QueryServiceHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
// 查询详情
|
||||
Method: http.MethodGet,
|
||||
Path: "/query/:id",
|
||||
Handler: query.QueryDetailHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// 查询示例
|
||||
Method: http.MethodGet,
|
||||
Path: "/query/example",
|
||||
Handler: query.QueryExampleHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// 查询列表
|
||||
Method: http.MethodGet,
|
||||
Path: "/query/list",
|
||||
Handler: query.QueryListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// 查询详情 按订单号 付款查询时
|
||||
Method: http.MethodGet,
|
||||
Path: "/query/orderId/:order_id",
|
||||
Handler: query.QueryDetailByOrderIdHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// 查询详情 按订单号
|
||||
Method: http.MethodGet,
|
||||
Path: "/query/orderNo/:order_no",
|
||||
Handler: query.QueryDetailByOrderNoHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// 获取查询临时订单
|
||||
Method: http.MethodGet,
|
||||
Path: "/query/provisional_order/:id",
|
||||
Handler: query.QueryProvisionalOrderHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// 重试查询
|
||||
Method: http.MethodPost,
|
||||
Path: "/query/retry/:id",
|
||||
Handler: query.QueryRetryHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/query/single/test",
|
||||
Handler: query.QuerySingleTestHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// 更新查询数据
|
||||
Method: http.MethodPost,
|
||||
Path: "/query/update_data",
|
||||
Handler: query.UpdateQueryDataHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/user/decryptMobile",
|
||||
Handler: user.DecryptMobileHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// mobile code login
|
||||
Method: http.MethodPost,
|
||||
Path: "/user/mobileCodeLogin",
|
||||
Handler: user.MobileCodeLoginHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// mobile login
|
||||
Method: http.MethodPost,
|
||||
Path: "/user/mobileLogin",
|
||||
Handler: user.MobileLoginHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// register
|
||||
Method: http.MethodPost,
|
||||
Path: "/user/register",
|
||||
Handler: user.RegisterHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// wechat mini auth
|
||||
Method: http.MethodPost,
|
||||
Path: "/user/wxMiniAuth",
|
||||
Handler: user.WxMiniAuthHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// wechat h5 auth
|
||||
Method: http.MethodPost,
|
||||
Path: "/user/wxh5Auth",
|
||||
Handler: user.WxH5AuthHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
// get user info
|
||||
Method: http.MethodGet,
|
||||
Path: "/user/detail",
|
||||
Handler: user.DetailHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
// get new token
|
||||
Method: http.MethodPost,
|
||||
Path: "/user/getToken",
|
||||
Handler: user.GetTokenHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret),
|
||||
rest.WithPrefix("/api/v1"),
|
||||
)
|
||||
}
|
||||
16
app/main/api/internal/handler/user/decryptmobilehandler.go
Normal file
16
app/main/api/internal/handler/user/decryptmobilehandler.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"tyc-server/app/main/api/internal/logic/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/common/result"
|
||||
)
|
||||
|
||||
func DecryptMobileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := user.NewDecryptMobileLogic(r.Context(), svcCtx)
|
||||
err := l.DecryptMobile()
|
||||
result.HttpResult(r, w, nil, err)
|
||||
}
|
||||
}
|
||||
17
app/main/api/internal/handler/user/detailhandler.go
Normal file
17
app/main/api/internal/handler/user/detailhandler.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/common/result"
|
||||
)
|
||||
|
||||
func DetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := user.NewDetailLogic(r.Context(), svcCtx)
|
||||
resp, err := l.Detail()
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
17
app/main/api/internal/handler/user/gettokenhandler.go
Normal file
17
app/main/api/internal/handler/user/gettokenhandler.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/common/result"
|
||||
)
|
||||
|
||||
func GetTokenHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := user.NewGetTokenLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetToken()
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/user/mobilecodeloginhandler.go
Normal file
30
app/main/api/internal/handler/user/mobilecodeloginhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func MobileCodeLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.MobileCodeLoginReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := user.NewMobileCodeLoginLogic(r.Context(), svcCtx)
|
||||
resp, err := l.MobileCodeLogin(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/user/mobileloginhandler.go
Normal file
30
app/main/api/internal/handler/user/mobileloginhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func MobileLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.MobileLoginReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := user.NewMobileLoginLogic(r.Context(), svcCtx)
|
||||
resp, err := l.MobileLogin(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/user/registerhandler.go
Normal file
30
app/main/api/internal/handler/user/registerhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.RegisterReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := user.NewRegisterLogic(r.Context(), svcCtx)
|
||||
resp, err := l.Register(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/user/wxh5authhandler.go
Normal file
30
app/main/api/internal/handler/user/wxh5authhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func WxH5AuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.WXH5AuthReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := user.NewWxH5AuthLogic(r.Context(), svcCtx)
|
||||
resp, err := l.WxH5Auth(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
30
app/main/api/internal/handler/user/wxminiauthhandler.go
Normal file
30
app/main/api/internal/handler/user/wxminiauthhandler.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"tyc-server/app/main/api/internal/logic/user"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/result"
|
||||
"tyc-server/pkg/lzkit/validator"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func WxMiniAuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.WXMiniAuthReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
if err := validator.Validate(req); err != nil {
|
||||
result.ParamValidateErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
l := user.NewWxMiniAuthLogic(r.Context(), svcCtx)
|
||||
resp, err := l.WxMiniAuth(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
||||
105
app/main/api/internal/logic/auth/sendsmslogic.go
Normal file
105
app/main/api/internal/logic/auth/sendsmslogic.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
dysmsapi "github.com/alibabacloud-go/dysmsapi-20170525/v3/client"
|
||||
"github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type SendSmsLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLogic {
|
||||
return &SendSmsLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %+v", err)
|
||||
}
|
||||
// 检查手机号是否在一分钟内已发送过验证码
|
||||
limitCodeKey := fmt.Sprintf("limit:%s:%s", req.ActionType, encryptedMobile)
|
||||
exists, err := l.svcCtx.Redis.Exists(limitCodeKey)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 读取redis缓存失败: %s", encryptedMobile)
|
||||
}
|
||||
|
||||
if exists {
|
||||
// 如果 Redis 中已经存在标记,说明在 1 分钟内请求过,返回错误
|
||||
return errors.Wrapf(xerr.NewErrMsg("一分钟内不能重复发送验证码"), "短信发送, 手机号1分钟内重复请求发送验证码: %s", encryptedMobile)
|
||||
}
|
||||
|
||||
code := fmt.Sprintf("%06d", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(1000000))
|
||||
|
||||
// 发送短信
|
||||
smsResp, err := l.sendSmsRequest(req.Mobile, code)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 调用阿里客户端失败: %+v", err)
|
||||
}
|
||||
if *smsResp.Body.Code != "OK" {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 阿里客户端响应失败: %s", *smsResp.Body.Message)
|
||||
}
|
||||
codeKey := fmt.Sprintf("%s:%s", req.ActionType, encryptedMobile)
|
||||
// 将验证码保存到 Redis,设置过期时间
|
||||
err = l.svcCtx.Redis.Setex(codeKey, code, l.svcCtx.Config.VerifyCode.ValidTime) // 验证码有效期5分钟
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置过期时间失败: %+v", err)
|
||||
}
|
||||
// 在 Redis 中设置 1 分钟的标记,限制重复请求
|
||||
err = l.svcCtx.Redis.Setex(limitCodeKey, code, 60) // 标记 1 分钟内不能重复请求
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置限制重复请求失败: %+v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateClient 创建阿里云短信客户端
|
||||
func (l *SendSmsLogic) CreateClient() (*dysmsapi.Client, error) {
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: &l.svcCtx.Config.VerifyCode.AccessKeyID,
|
||||
AccessKeySecret: &l.svcCtx.Config.VerifyCode.AccessKeySecret,
|
||||
}
|
||||
config.Endpoint = tea.String(l.svcCtx.Config.VerifyCode.EndpointURL)
|
||||
return dysmsapi.NewClient(config)
|
||||
}
|
||||
|
||||
// sendSmsRequest 发送短信请求
|
||||
func (l *SendSmsLogic) sendSmsRequest(mobile, code string) (*dysmsapi.SendSmsResponse, error) {
|
||||
// 初始化阿里云短信客户端
|
||||
cli, err := l.CreateClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := &dysmsapi.SendSmsRequest{
|
||||
SignName: tea.String(l.svcCtx.Config.VerifyCode.SignName),
|
||||
TemplateCode: tea.String(l.svcCtx.Config.VerifyCode.TemplateCode),
|
||||
PhoneNumbers: tea.String(mobile),
|
||||
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", code)),
|
||||
}
|
||||
runtime := &service.RuntimeOptions{}
|
||||
return cli.SendSmsWithOptions(request, runtime)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetNotificationsLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetNotificationsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNotificationsLogic {
|
||||
return &GetNotificationsLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetNotificationsLogic) GetNotifications() (resp *types.GetNotificationsResp, err error) {
|
||||
// 获取今天的日期
|
||||
now := time.Now()
|
||||
|
||||
// 获取开始和结束日期的时间戳
|
||||
todayStart := now.Format("2006-01-02") + " 00:00:00"
|
||||
todayEnd := now.Format("2006-01-02") + " 23:59:59"
|
||||
|
||||
// 构建查询条件
|
||||
builder := l.svcCtx.GlobalNotificationsModel.SelectBuilder().
|
||||
Where("status = ?", "active").
|
||||
Where("(start_date IS NULL OR start_date <= ?)", todayEnd). // start_date 是 NULL 或者小于等于今天结束时间
|
||||
Where("(end_date IS NULL OR end_date >= ?)", todayStart) // end_date 是 NULL 或者大于等于今天开始时间
|
||||
|
||||
notificationsModelList, findErr := l.svcCtx.GlobalNotificationsModel.FindAll(l.ctx, builder, "")
|
||||
if findErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "全局通知, 查找通知失败, err:%+v", findErr)
|
||||
}
|
||||
|
||||
var notifications []types.Notification
|
||||
copyErr := copier.Copy(¬ifications, ¬ificationsModelList)
|
||||
if copyErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "全局通知, 复制结构体失败, err:%+v", copyErr)
|
||||
}
|
||||
|
||||
return &types.GetNotificationsResp{Notifications: notifications}, nil
|
||||
}
|
||||
76
app/main/api/internal/logic/pay/alipaycallbacklogic.go
Normal file
76
app/main/api/internal/logic/pay/alipaycallbacklogic.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AlipayCallbackLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAlipayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AlipayCallbackLogic {
|
||||
return &AlipayCallbackLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AlipayCallbackLogic) AlipayCallback(w http.ResponseWriter, r *http.Request) error {
|
||||
notification, err := l.svcCtx.AlipayService.HandleAliPaymentNotification(r)
|
||||
if err != nil {
|
||||
logx.Errorf("支付宝支付回调,%+v", err)
|
||||
return nil
|
||||
}
|
||||
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, notification.OutTradeNo)
|
||||
if findOrderErr != nil {
|
||||
logx.Errorf("支付宝支付回调,查找订单失败: %+v", findOrderErr)
|
||||
return nil
|
||||
}
|
||||
if order.Status != "pending" {
|
||||
alipay.ACKNotification(w)
|
||||
return nil
|
||||
}
|
||||
amount := lzUtils.ToAlipayAmount(order.Amount)
|
||||
// 确保订单金额和状态正确,防止重复更新
|
||||
if amount != notification.TotalAmount {
|
||||
logx.Errorf("支付宝支付回调,金额不一致")
|
||||
return nil
|
||||
}
|
||||
if order.Status != "pending" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("success")) // 确保只写入一次响应
|
||||
return nil
|
||||
}
|
||||
switch notification.TradeStatus {
|
||||
case alipay.TradeStatusSuccess:
|
||||
order.Status = "paid"
|
||||
order.PayTime = lzUtils.TimeToNullTime(time.Now())
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
order.PlatformOrderId = lzUtils.StringToNullString(notification.TradeNo)
|
||||
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||
logx.Errorf("支付宝支付回调,修改订单信息失败: %+v", updateErr)
|
||||
return nil
|
||||
}
|
||||
if order.Status == "paid" {
|
||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||
logx.Errorf("异步任务调度失败: %v", asyncErr)
|
||||
return asyncErr
|
||||
}
|
||||
}
|
||||
alipay.ACKNotification(w)
|
||||
return nil
|
||||
}
|
||||
82
app/main/api/internal/logic/pay/iapcallbacklogic.go
Normal file
82
app/main/api/internal/logic/pay/iapcallbacklogic.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type IapCallbackLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewIapCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *IapCallbackLogic {
|
||||
return &IapCallbackLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *IapCallbackLogic) IapCallback(req *types.IapCallbackReq) error {
|
||||
// Step 1: 查找订单
|
||||
order, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, req.OrderID)
|
||||
if findOrderErr != nil {
|
||||
logx.Errorf("苹果内购支付回调,查找订单失败: %+v", findOrderErr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step 2: 验证订单状态
|
||||
if order.Status != "pending" {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 订单状态异常: %+v", order)
|
||||
}
|
||||
|
||||
// Step 3: 调用 VerifyReceipt 验证苹果支付凭证
|
||||
//receipt := req.TransactionReceipt // 从请求中获取支付凭证
|
||||
//verifyResponse, verifyErr := l.svcCtx.ApplePayService.VerifyReceipt(l.ctx, receipt)
|
||||
//if verifyErr != nil {
|
||||
// return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 验证订单异常: %+v", verifyErr)
|
||||
//}
|
||||
|
||||
// Step 4: 验证订单
|
||||
//product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, order.Id)
|
||||
//if findProductErr != nil {
|
||||
// return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "苹果内购支付回调, 获取订单相关商品失败: %+v", findProductErr)
|
||||
//}
|
||||
//isProductMatched := false
|
||||
//appleProductID := l.svcCtx.ApplePayService.GetIappayAppID(product.ProductEn)
|
||||
//for _, item := range verifyResponse.Receipt.InApp {
|
||||
// if item.ProductID == appleProductID {
|
||||
// isProductMatched = true
|
||||
// order.PlatformOrderId = lzUtils.StringToNullString(item.TransactionID) // 记录交易 ID
|
||||
// break
|
||||
// }
|
||||
//}
|
||||
//if !isProductMatched {
|
||||
// return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 商品 ID 不匹配,订单 ID: %d, 回调苹果商品 ID: %s", order.Id, verifyResponse.Receipt.InApp[0].ProductID)
|
||||
//}
|
||||
|
||||
// Step 5: 更新订单状态 mm
|
||||
order.Status = "paid"
|
||||
order.PayTime = lzUtils.TimeToNullTime(time.Now())
|
||||
|
||||
// 更新订单到数据库
|
||||
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调, 修改订单信息失败: %+v", updateErr)
|
||||
}
|
||||
|
||||
// Step 6: 处理订单完成后的逻辑
|
||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "苹果内购支付回调,异步任务调度失败: %v", asyncErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
114
app/main/api/internal/logic/pay/paymentlogic.go
Normal file
114
app/main/api/internal/logic/pay/paymentlogic.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/common/ctxdata"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type PaymentLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
|
||||
return &PaymentLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, err error) {
|
||||
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if getUidErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr)
|
||||
}
|
||||
|
||||
brand, ok := l.ctx.Value("brand").(string)
|
||||
if !ok {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取平台失败, %+v", getUidErr)
|
||||
}
|
||||
outTradeNo := req.Id
|
||||
redisKey := fmt.Sprintf("%d:%s", userID, outTradeNo)
|
||||
cache, cacheErr := l.svcCtx.Redis.GetCtx(l.ctx, redisKey)
|
||||
if cacheErr != nil {
|
||||
return nil, cacheErr
|
||||
}
|
||||
var data types.QueryCacheLoad
|
||||
err = json.Unmarshal([]byte(cache), &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析缓存内容失败, %+v", err)
|
||||
}
|
||||
|
||||
product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 查找产品错误: %+v", err)
|
||||
}
|
||||
var prepayData interface{}
|
||||
var amount float64
|
||||
user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取用户信息失败: %+v", err)
|
||||
}
|
||||
|
||||
if user.Inside == 1 {
|
||||
amount = 0.01
|
||||
} else {
|
||||
amount = product.SellPrice
|
||||
}
|
||||
|
||||
var createOrderErr error
|
||||
if req.PayMethod == "wechat" {
|
||||
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, amount, product.ProductName, outTradeNo)
|
||||
} else if req.PayMethod == "alipay" {
|
||||
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, amount, product.ProductName, outTradeNo, brand)
|
||||
} else if req.PayMethod == "appleiap" {
|
||||
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(product.ProductEn)
|
||||
}
|
||||
if createOrderErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 创建支付订单失败: %+v", createOrderErr)
|
||||
}
|
||||
var orderID int64
|
||||
transErr := l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
order := model.Order{
|
||||
OrderNo: outTradeNo,
|
||||
UserId: userID,
|
||||
ProductId: product.Id,
|
||||
PaymentPlatform: req.PayMethod,
|
||||
PaymentScene: "tyc",
|
||||
Amount: amount,
|
||||
Status: "pending",
|
||||
}
|
||||
orderInsertResult, insertOrderErr := l.svcCtx.OrderModel.Insert(ctx, session, &order)
|
||||
if insertOrderErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 保存订单失败: %+v", insertOrderErr)
|
||||
}
|
||||
insertedOrderID, lastInsertIdErr := orderInsertResult.LastInsertId()
|
||||
if lastInsertIdErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取保存订单ID失败: %+v", lastInsertIdErr)
|
||||
}
|
||||
orderID = insertedOrderID
|
||||
return nil
|
||||
})
|
||||
if transErr != nil {
|
||||
return nil, transErr
|
||||
}
|
||||
switch v := prepayData.(type) {
|
||||
case string:
|
||||
// 如果 prepayData 是字符串类型,直接返回
|
||||
return &types.PaymentResp{PrepayId: v, OrderID: orderID}, nil
|
||||
default:
|
||||
return &types.PaymentResp{PrepayData: prepayData, OrderID: orderID}, nil
|
||||
}
|
||||
}
|
||||
79
app/main/api/internal/logic/pay/wechatpaycallbacklogic.go
Normal file
79
app/main/api/internal/logic/pay/wechatpaycallbacklogic.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/service"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type WechatPayCallbackLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewWechatPayCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatPayCallbackLogic {
|
||||
return &WechatPayCallbackLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *WechatPayCallbackLogic) WechatPayCallback(w http.ResponseWriter, r *http.Request) error {
|
||||
notification, err := l.svcCtx.WechatPayService.HandleWechatPayNotification(l.ctx, r)
|
||||
if err != nil {
|
||||
logx.Errorf("微信支付回调,%+v", err)
|
||||
return nil
|
||||
}
|
||||
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *notification.OutTradeNo)
|
||||
if findOrderErr != nil {
|
||||
logx.Errorf("微信支付回调,查找订单信息失败: %+v", findOrderErr)
|
||||
return nil
|
||||
}
|
||||
|
||||
amount := lzUtils.ToWechatAmount(order.Amount)
|
||||
if amount != *notification.Amount.Total {
|
||||
logx.Errorf("微信支付回调,金额不一致")
|
||||
return nil
|
||||
}
|
||||
if order.Status != "pending" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("success")) // 确保只写入一次响应
|
||||
return nil
|
||||
}
|
||||
switch *notification.TradeState {
|
||||
case service.TradeStateSuccess:
|
||||
order.Status = "paid"
|
||||
order.PayTime = lzUtils.TimeToNullTime(time.Now())
|
||||
case service.TradeStateClosed:
|
||||
order.Status = "closed"
|
||||
order.CloseTime = lzUtils.TimeToNullTime(time.Now())
|
||||
case service.TradeStateRevoked:
|
||||
order.Status = "failed"
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
order.PlatformOrderId = lzUtils.StringToNullString(*notification.TransactionId)
|
||||
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||
logx.Errorf("微信支付回调,更新订单失败%+v", updateErr)
|
||||
return nil
|
||||
}
|
||||
if order.Status == "paid" {
|
||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(order.Id); asyncErr != nil {
|
||||
logx.Errorf("异步任务调度失败: %v", asyncErr)
|
||||
return asyncErr
|
||||
}
|
||||
}
|
||||
|
||||
// 响应微信回调成功
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("success")) // 确保只写入一次响应
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type WechatPayRefundCallbackLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewWechatPayRefundCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WechatPayRefundCallbackLogic {
|
||||
return &WechatPayRefundCallbackLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *WechatPayRefundCallbackLogic) WechatPayRefundCallback(w http.ResponseWriter, r *http.Request) error {
|
||||
notification, err := l.svcCtx.WechatPayService.HandleRefundNotification(l.ctx, r)
|
||||
if err != nil {
|
||||
logx.Errorf("微信退款回调,%+v", err)
|
||||
return nil
|
||||
}
|
||||
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *notification.OutTradeNo)
|
||||
if findOrderErr != nil {
|
||||
logx.Errorf("微信退款回调,查找订单信息失败: %+v", findOrderErr)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch *notification.Status {
|
||||
case refunddomestic.STATUS_SUCCESS:
|
||||
order.Status = "refunded"
|
||||
case refunddomestic.STATUS_ABNORMAL:
|
||||
// 异常
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
|
||||
logx.Errorf("微信退款回调,更新订单失败%+v", updateErr)
|
||||
return nil
|
||||
}
|
||||
// 响应微信回调成功
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("success")) // 确保只写入一次响应
|
||||
return nil
|
||||
}
|
||||
76
app/main/api/internal/logic/product/getproductbyenlogic.go
Normal file
76
app/main/api/internal/logic/product/getproductbyenlogic.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/mr"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetProductByEnLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetProductByEnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductByEnLogic {
|
||||
return &GetProductByEnLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetProductByEnLogic) GetProductByEn(req *types.GetProductByEnRequest) (resp *types.ProductResponse, err error) {
|
||||
productModel, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, req.ProductEn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "产品查询, 查找产品错误: %+v", err)
|
||||
}
|
||||
|
||||
build := l.svcCtx.ProductFeatureModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"product_id": productModel.Id,
|
||||
})
|
||||
productFeatureAll, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, build, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "产品查询, 查找产品关联错误: %+v", err)
|
||||
}
|
||||
var product types.Product
|
||||
err = copier.Copy(&product, productModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 用户信息结构体复制失败, %+v", err)
|
||||
}
|
||||
|
||||
mr.MapReduceVoid(func(source chan<- interface{}) {
|
||||
for _, productFeature := range productFeatureAll {
|
||||
source <- productFeature.FeatureId
|
||||
}
|
||||
}, func(item interface{}, writer mr.Writer[*model.Feature], cancel func(error)) {
|
||||
id := item.(int64)
|
||||
|
||||
feature, findFeatureErr := l.svcCtx.FeatureModel.FindOne(l.ctx, id)
|
||||
if findFeatureErr != nil {
|
||||
logx.WithContext(l.ctx).Errorf("产品查询, 查找关联feature错误: %d, err:%v", id, findFeatureErr)
|
||||
return
|
||||
}
|
||||
if feature != nil && feature.Id > 0 {
|
||||
writer.Write(feature)
|
||||
}
|
||||
}, func(pipe <-chan *model.Feature, cancel func(error)) {
|
||||
for item := range pipe {
|
||||
var feature types.Feature
|
||||
_ = copier.Copy(&feature, item)
|
||||
product.Features = append(product.Features, feature)
|
||||
}
|
||||
})
|
||||
|
||||
return &types.ProductResponse{Product: product}, nil
|
||||
}
|
||||
30
app/main/api/internal/logic/product/getproductbyidlogic.go
Normal file
30
app/main/api/internal/logic/product/getproductbyidlogic.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetProductByIDLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetProductByIDLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductByIDLogic {
|
||||
return &GetProductByIDLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetProductByIDLogic) GetProductByID(req *types.GetProductByIDRequest) (resp *types.ProductResponse, err error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/mr"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetProductRenderListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetProductRenderListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductRenderListLogic {
|
||||
return &GetProductRenderListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetProductRenderListLogic) GetProductRenderList(req *types.GetProductRenderListRequest) (resp *types.GetProductRenderListResponse, err error) {
|
||||
platform, platformOk := l.ctx.Value("platform").(string)
|
||||
if !platformOk || platform == "" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取渲染列表失败,没有相关平台信息")
|
||||
}
|
||||
// 从上下文中获取品牌信息
|
||||
brand, brandOk := l.ctx.Value("brand").(string)
|
||||
if !brandOk || brand == "" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取渲染列表失败,没有相关品牌信息")
|
||||
}
|
||||
|
||||
var platformKey = brand + "_" + platform
|
||||
builder := l.svcCtx.ProductRenderModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"platform": platformKey,
|
||||
"module": req.Module,
|
||||
})
|
||||
productRenderModelList, err := l.svcCtx.ProductRenderModel.FindAll(l.ctx, builder, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取渲染列表失败,获取渲染列表失败%s", err.Error())
|
||||
}
|
||||
var productList []types.Product
|
||||
mr.MapReduceVoid(func(source chan<- interface{}) {
|
||||
for _, productRender := range productRenderModelList {
|
||||
if productRender.IsRendered == 1 {
|
||||
source <- productRender.ProductId
|
||||
}
|
||||
}
|
||||
}, func(item interface{}, writer mr.Writer[*model.Product], cancel func(error)) {
|
||||
id := item.(int64)
|
||||
product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, id)
|
||||
if findProductErr != nil {
|
||||
logx.WithContext(l.ctx).Errorf("获取渲染列表失败, 查找关联product错误: %d, err:%v", id, findProductErr)
|
||||
return
|
||||
}
|
||||
if product != nil && product.Id > 0 {
|
||||
product.Description = ""
|
||||
product.SellPrice = 0
|
||||
writer.Write(product)
|
||||
}
|
||||
}, func(pipe <-chan *model.Product, cancel func(error)) {
|
||||
for item := range pipe {
|
||||
var product types.Product
|
||||
_ = copier.Copy(&product, item)
|
||||
productList = append(productList, product)
|
||||
}
|
||||
})
|
||||
return &types.GetProductRenderListResponse{
|
||||
Product: productList,
|
||||
}, nil
|
||||
}
|
||||
261
app/main/api/internal/logic/query/querydetailbyorderidlogic.go
Normal file
261
app/main/api/internal/logic/query/querydetailbyorderidlogic.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
"tyc-server/pkg/lzkit/delay"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QueryDetailByOrderIdLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryDetailByOrderIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryDetailByOrderIdLogic {
|
||||
return &QueryDetailByOrderIdLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryDetailByOrderIdLogic) QueryDetailByOrderId(req *types.QueryDetailByOrderIdReq) (resp *types.QueryDetailByOrderIdResp, err error) {
|
||||
// 获取当前用户ID
|
||||
// userId, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err)
|
||||
// }
|
||||
|
||||
// 获取订单信息
|
||||
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.OrderId)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "报告查询, 订单不存在: %v", err)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err)
|
||||
}
|
||||
// 安全验证:确保订单属于当前用户
|
||||
// if order.UserId != userId {
|
||||
// return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "无权查看此订单报告")
|
||||
// }
|
||||
// 创建渐进式延迟策略实例
|
||||
progressiveDelayOrder, err := delay.New(200*time.Millisecond, 3*time.Second, 10*time.Second, 1.5)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "初始化渐进式延迟策略失败: %+v", err)
|
||||
}
|
||||
|
||||
// 等待订单状态变为 "paid"
|
||||
startTime := time.Now()
|
||||
for order.Status == "pending" {
|
||||
if time.Since(startTime) > 10*time.Second {
|
||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.LOGIC_QUERY_WAIT, ""), "")
|
||||
}
|
||||
|
||||
// 使用渐进式延迟,获取下次延迟时间
|
||||
nextDelay, _ := progressiveDelayOrder.NextDelay()
|
||||
|
||||
// 等待一段时间后再查一次订单状态
|
||||
time.Sleep(nextDelay)
|
||||
|
||||
// 再次查找订单
|
||||
order, err = l.svcCtx.OrderModel.FindOne(l.ctx, req.OrderId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找订单错误: %+v", err)
|
||||
}
|
||||
}
|
||||
if order.Status != "paid" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.LOGIC_QUERY_ERROR, ""), "")
|
||||
}
|
||||
// 获取报告信息
|
||||
queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, req.OrderId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err)
|
||||
}
|
||||
|
||||
// 创建渐进式延迟实例
|
||||
progressiveDelayQuery, err := delay.New(200*time.Millisecond, 3*time.Second, 10*time.Second, 1.5)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "初始化渐进式延迟策略失败: %+v", err)
|
||||
}
|
||||
|
||||
// 等待 queryModel.QueryState 不再是 "pending"
|
||||
startTime = time.Now()
|
||||
for queryModel.QueryState == "pending" {
|
||||
if time.Since(startTime) > 10*time.Second {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询超时,查询状态长时间为 'pending'")
|
||||
}
|
||||
|
||||
// 使用渐进式延迟,获取下次延迟时间
|
||||
nextDelay, _ := progressiveDelayQuery.NextDelay()
|
||||
|
||||
// 每隔一段时间检查一次查询状态
|
||||
time.Sleep(nextDelay)
|
||||
|
||||
// 再次查询 report 状态
|
||||
queryModel, err = l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, req.OrderId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 QueryState 做后续处理
|
||||
if queryModel.QueryState == "failed" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.LOGIC_QUERY_ERROR, ""), "")
|
||||
}
|
||||
|
||||
var query types.Query
|
||||
query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05")
|
||||
query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05")
|
||||
|
||||
// 解密查询数据
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取AES解密解药失败, %+v", err)
|
||||
}
|
||||
processParamsErr := ProcessQueryParams(queryModel.QueryParams, &query.QueryParams, key)
|
||||
if processParamsErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告参数处理失败: %v", processParamsErr)
|
||||
}
|
||||
processErr := ProcessQueryData(queryModel.QueryData, &query.QueryData, key)
|
||||
if processErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", processErr)
|
||||
}
|
||||
updateFeatureAndProductFeatureErr := l.UpdateFeatureAndProductFeature(queryModel.ProductId, &query.QueryData)
|
||||
if updateFeatureAndProductFeatureErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", updateFeatureAndProductFeatureErr)
|
||||
}
|
||||
// 复制报告数据
|
||||
err = copier.Copy(&query, queryModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体复制失败, %v", err)
|
||||
}
|
||||
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err)
|
||||
}
|
||||
query.ProductName = product.ProductName
|
||||
return &types.QueryDetailByOrderIdResp{
|
||||
Query: query,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ProcessQueryData 解密和反序列化 QueryData
|
||||
func ProcessQueryData(queryData sql.NullString, target *[]types.QueryItem, key []byte) error {
|
||||
queryDataStr := lzUtils.NullStringToString(queryData)
|
||||
if queryDataStr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解密数据
|
||||
decryptedData, decryptErr := crypto.AesDecrypt(queryDataStr, key)
|
||||
if decryptErr != nil {
|
||||
return decryptErr
|
||||
}
|
||||
|
||||
// 解析 JSON 数组
|
||||
var decryptedArray []map[string]interface{}
|
||||
unmarshalErr := json.Unmarshal(decryptedData, &decryptedArray)
|
||||
if unmarshalErr != nil {
|
||||
return unmarshalErr
|
||||
}
|
||||
|
||||
// 确保 target 具有正确的长度
|
||||
if len(*target) == 0 {
|
||||
*target = make([]types.QueryItem, len(decryptedArray))
|
||||
}
|
||||
|
||||
// 填充解密后的数据到 target
|
||||
for i := 0; i < len(decryptedArray); i++ {
|
||||
// 直接填充解密数据到 Data 字段
|
||||
(*target)[i].Data = decryptedArray[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessQueryParams解密和反序列化 QueryParams
|
||||
func ProcessQueryParams(QueryParams string, target *map[string]interface{}, key []byte) error {
|
||||
// 解密 QueryParams
|
||||
decryptedData, decryptErr := crypto.AesDecrypt(QueryParams, key)
|
||||
if decryptErr != nil {
|
||||
return decryptErr
|
||||
}
|
||||
|
||||
// 反序列化解密后的数据
|
||||
unmarshalErr := json.Unmarshal(decryptedData, target)
|
||||
if unmarshalErr != nil {
|
||||
return unmarshalErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (l *QueryDetailByOrderIdLogic) UpdateFeatureAndProductFeature(productID int64, target *[]types.QueryItem) error {
|
||||
// 遍历 target 数组,使用倒序遍历,以便删除元素时不影响索引
|
||||
for i := len(*target) - 1; i >= 0; i-- {
|
||||
queryItem := &(*target)[i]
|
||||
|
||||
// 确保 Data 为 map 类型
|
||||
data, ok := queryItem.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("queryItem.Data 必须是 map[string]interface{} 类型")
|
||||
}
|
||||
|
||||
// 从 Data 中获取 apiID
|
||||
apiID, ok := data["apiID"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("queryItem.Data 中的 apiID 必须是字符串类型")
|
||||
}
|
||||
|
||||
// 查询 Feature
|
||||
feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, apiID)
|
||||
if err != nil {
|
||||
// 如果 Feature 查不到,也要删除当前 QueryItem
|
||||
*target = append((*target)[:i], (*target)[i+1:]...)
|
||||
continue
|
||||
}
|
||||
|
||||
// 查询 ProductFeatureModel
|
||||
builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", productID)
|
||||
productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询 ProductFeatureModel 错误: %v", err)
|
||||
}
|
||||
|
||||
// 遍历 productFeatures,找到与 feature.ID 关联且 enable == 1 的项
|
||||
var featureData map[string]interface{}
|
||||
// foundFeature := false
|
||||
sort := 0
|
||||
for _, pf := range productFeatures {
|
||||
if pf.FeatureId == feature.Id { // 确保和 Feature 关联
|
||||
sort = int(pf.Sort)
|
||||
break // 找到第一个符合条件的就退出循环
|
||||
}
|
||||
}
|
||||
featureData = map[string]interface{}{
|
||||
"featureName": feature.Name,
|
||||
"sort": sort,
|
||||
}
|
||||
|
||||
// 更新 queryItem 的 Feature 字段(不是数组)
|
||||
queryItem.Feature = featureData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
209
app/main/api/internal/logic/query/querydetailbyordernologic.go
Normal file
209
app/main/api/internal/logic/query/querydetailbyordernologic.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
"tyc-server/common/ctxdata"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/delay"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QueryDetailByOrderNoLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryDetailByOrderNoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryDetailByOrderNoLogic {
|
||||
return &QueryDetailByOrderNoLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryDetailByOrderNoLogic) QueryDetailByOrderNo(req *types.QueryDetailByOrderNoReq) (resp *types.QueryDetailByOrderNoResp, err error) {
|
||||
// 获取当前用户ID
|
||||
userId, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取订单信息
|
||||
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "报告查询, 订单不存在: %v", err)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err)
|
||||
}
|
||||
|
||||
// 安全验证:确保订单属于当前用户
|
||||
if order.UserId != userId {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.LOGIC_QUERY_NOT_FOUND), "无权查看此订单报告")
|
||||
}
|
||||
// 创建渐进式延迟策略实例
|
||||
progressiveDelayOrder, err := delay.New(200*time.Millisecond, 3*time.Second, 10*time.Second, 1.5)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "初始化渐进式延迟策略失败: %+v", err)
|
||||
}
|
||||
|
||||
// 等待订单状态变为 "paid"
|
||||
startTime := time.Now()
|
||||
for order.Status == "pending" {
|
||||
if time.Since(startTime) > 10*time.Second {
|
||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.LOGIC_QUERY_WAIT, ""), "")
|
||||
}
|
||||
|
||||
// 使用渐进式延迟,获取下次延迟时间
|
||||
nextDelay, _ := progressiveDelayOrder.NextDelay()
|
||||
|
||||
// 等待一段时间后再查一次订单状态
|
||||
time.Sleep(nextDelay)
|
||||
|
||||
// 再次查找订单
|
||||
order, err = l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找订单错误: %+v", err)
|
||||
}
|
||||
}
|
||||
if order.Status != "paid" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.LOGIC_QUERY_ERROR, ""), "")
|
||||
}
|
||||
// 获取报告信息
|
||||
queryModel, err := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err)
|
||||
}
|
||||
|
||||
// 创建渐进式延迟实例
|
||||
progressiveDelayQuery, err := delay.New(200*time.Millisecond, 3*time.Second, 10*time.Second, 1.5)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "初始化渐进式延迟策略失败: %+v", err)
|
||||
}
|
||||
|
||||
// 等待 queryModel.QueryState 不再是 "pending"
|
||||
startTime = time.Now()
|
||||
for queryModel.QueryState == "pending" {
|
||||
if time.Since(startTime) > 10*time.Second {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询超时,查询状态长时间为 'pending'")
|
||||
}
|
||||
|
||||
// 使用渐进式延迟,获取下次延迟时间
|
||||
nextDelay, _ := progressiveDelayQuery.NextDelay()
|
||||
|
||||
// 每隔一段时间检查一次查询状态
|
||||
time.Sleep(nextDelay)
|
||||
|
||||
// 再次查询 report 状态
|
||||
queryModel, err = l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, order.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 QueryState 做后续处理
|
||||
if queryModel.QueryState == "failed" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.LOGIC_QUERY_ERROR, ""), "")
|
||||
}
|
||||
|
||||
var query types.Query
|
||||
query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05")
|
||||
query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05")
|
||||
|
||||
// 解密查询数据
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取AES解密解药失败, %+v", err)
|
||||
}
|
||||
processParamsErr := ProcessQueryParams(queryModel.QueryParams, &query.QueryParams, key)
|
||||
if processParamsErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告参数处理失败: %v", processParamsErr)
|
||||
}
|
||||
processErr := ProcessQueryData(queryModel.QueryData, &query.QueryData, key)
|
||||
if processErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", processErr)
|
||||
}
|
||||
updateFeatureAndProductFeatureErr := l.UpdateFeatureAndProductFeature(queryModel.ProductId, &query.QueryData)
|
||||
if updateFeatureAndProductFeatureErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果处理失败: %v", updateFeatureAndProductFeatureErr)
|
||||
}
|
||||
// 复制报告数据
|
||||
err = copier.Copy(&query, queryModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体复制失败, %v", err)
|
||||
}
|
||||
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err)
|
||||
}
|
||||
query.ProductName = product.ProductName
|
||||
return &types.QueryDetailByOrderNoResp{
|
||||
Query: query,
|
||||
}, nil
|
||||
}
|
||||
func (l *QueryDetailByOrderNoLogic) UpdateFeatureAndProductFeature(productID int64, target *[]types.QueryItem) error {
|
||||
// 遍历 target 数组,使用倒序遍历,以便删除元素时不影响索引
|
||||
for i := len(*target) - 1; i >= 0; i-- {
|
||||
queryItem := &(*target)[i]
|
||||
|
||||
// 确保 Data 为 map 类型
|
||||
data, ok := queryItem.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("queryItem.Data 必须是 map[string]interface{} 类型")
|
||||
}
|
||||
|
||||
// 从 Data 中获取 apiID
|
||||
apiID, ok := data["apiID"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("queryItem.Data 中的 apiID 必须是字符串类型")
|
||||
}
|
||||
|
||||
// 查询 Feature
|
||||
feature, err := l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, apiID)
|
||||
if err != nil {
|
||||
// 如果 Feature 查不到,也要删除当前 QueryItem
|
||||
*target = append((*target)[:i], (*target)[i+1:]...)
|
||||
continue
|
||||
}
|
||||
|
||||
// 查询 ProductFeatureModel
|
||||
builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", productID)
|
||||
productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询 ProductFeatureModel 错误: %v", err)
|
||||
}
|
||||
|
||||
// 遍历 productFeatures,找到与 feature.ID 关联且 enable == 1 的项
|
||||
var featureData map[string]interface{}
|
||||
// foundFeature := false
|
||||
sort := 0
|
||||
for _, pf := range productFeatures {
|
||||
if pf.FeatureId == feature.Id { // 确保和 Feature 关联
|
||||
sort = int(pf.Sort)
|
||||
break // 找到第一个符合条件的就退出循环
|
||||
}
|
||||
}
|
||||
featureData = map[string]interface{}{
|
||||
"featureName": feature.Name,
|
||||
"sort": sort,
|
||||
}
|
||||
|
||||
// 更新 queryItem 的 Feature 字段(不是数组)
|
||||
queryItem.Feature = featureData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
70
app/main/api/internal/logic/query/querydetaillogic.go
Normal file
70
app/main/api/internal/logic/query/querydetaillogic.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QueryDetailLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryDetailLogic {
|
||||
return &QueryDetailLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryDetailLogic) QueryDetail(req *types.QueryDetailReq) (resp *types.QueryDetailResp, err error) {
|
||||
queryModel, err := l.svcCtx.QueryModel.FindOne(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询, 查找报告错误: %+v", err)
|
||||
}
|
||||
|
||||
var query types.Query
|
||||
query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05")
|
||||
query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05")
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取AES解密解药失败, %+v", err)
|
||||
}
|
||||
if lzUtils.NullStringToString(queryModel.QueryData) != "" {
|
||||
queryData, decryptErr := crypto.AesDecrypt(lzUtils.NullStringToString(queryModel.QueryData), key)
|
||||
if decryptErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结果解密失败, %+v", decryptErr)
|
||||
}
|
||||
unmarshalErr := json.Unmarshal(queryData, &query.QueryData)
|
||||
if unmarshalErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体处理失败, %+v", unmarshalErr)
|
||||
}
|
||||
}
|
||||
err = copier.Copy(&query, queryModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 报告结构体复制失败, %+v", err)
|
||||
}
|
||||
product, err := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %+v", err)
|
||||
}
|
||||
query.ProductName = product.ProductName
|
||||
return &types.QueryDetailResp{
|
||||
Query: query,
|
||||
}, nil
|
||||
}
|
||||
118
app/main/api/internal/logic/query/queryexamplelogic.go
Normal file
118
app/main/api/internal/logic/query/queryexamplelogic.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QueryExampleLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryExampleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryExampleLogic {
|
||||
return &QueryExampleLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryExampleLogic) QueryExample(req *types.QueryExampleReq) (resp *types.QueryExampleResp, err error) {
|
||||
// 根据产品特性标识获取产品信息
|
||||
product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, req.Feature)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 获取商品信息失败, %v", err)
|
||||
}
|
||||
|
||||
// 创建一个空的Query结构体来存储结果
|
||||
query := types.Query{
|
||||
ProductName: product.ProductName,
|
||||
QueryData: make([]types.QueryItem, 0),
|
||||
QueryParams: make(map[string]interface{}),
|
||||
}
|
||||
exampleParams, err := l.svcCtx.ExampleParamsModel.FindOneByProductId(l.ctx, product.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 获取示例参数失败, %v", err)
|
||||
}
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 获取AES解密解药失败, %+v", err)
|
||||
}
|
||||
processParamsErr := ProcessQueryParams(exampleParams.QueryParams, &query.QueryParams, key)
|
||||
if processParamsErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 报告参数处理失败: %v", processParamsErr)
|
||||
}
|
||||
// 查询ProductFeatureModel获取产品相关的功能列表
|
||||
builder := l.svcCtx.ProductFeatureModel.SelectBuilder().Where("product_id = ?", product.Id)
|
||||
productFeatures, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, builder, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 查询 ProductFeatureModel 错误: %v", err)
|
||||
}
|
||||
// 从每个启用的特性获取示例数据并合并
|
||||
for _, pf := range productFeatures {
|
||||
if pf.Enable != 1 {
|
||||
continue // 跳过未启用的特性
|
||||
}
|
||||
|
||||
// 根据特性ID查找示例数据
|
||||
example, err := l.svcCtx.ExampleModel.FindOneByFeatureId(l.ctx, pf.FeatureId)
|
||||
if err != nil {
|
||||
logx.Infof("示例报告, 特性ID %d 无示例数据: %v", pf.FeatureId, err)
|
||||
continue // 如果没有示例数据就跳过
|
||||
}
|
||||
|
||||
// 获取对应的Feature信息
|
||||
feature, err := l.svcCtx.FeatureModel.FindOne(l.ctx, pf.FeatureId)
|
||||
if err != nil {
|
||||
logx.Infof("示例报告, 无法获取特性ID %d 的信息: %v", pf.FeatureId, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var queryItem types.QueryItem
|
||||
|
||||
// 解密查询数据
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 获取AES解密解药失败, %v", err)
|
||||
}
|
||||
// 解析示例内容
|
||||
if example.Content == "000" {
|
||||
queryItem.Data = example.Content
|
||||
} else {
|
||||
// 解密数据
|
||||
decryptedData, decryptErr := crypto.AesDecrypt(example.Content, key)
|
||||
if decryptErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 解密数据失败: %v", decryptErr)
|
||||
}
|
||||
err = sonic.Unmarshal([]byte(decryptedData), &queryItem.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "示例报告, 解析示例内容失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加特性信息
|
||||
queryItem.Feature = map[string]interface{}{
|
||||
"featureName": feature.Name,
|
||||
"sort": pf.Sort,
|
||||
}
|
||||
// 添加到查询数据中
|
||||
query.QueryData = append(query.QueryData, queryItem)
|
||||
}
|
||||
|
||||
return &types.QueryExampleResp{
|
||||
Query: query,
|
||||
}, nil
|
||||
}
|
||||
70
app/main/api/internal/logic/query/querylistlogic.go
Normal file
70
app/main/api/internal/logic/query/querylistlogic.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/ctxdata"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QueryListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryListLogic {
|
||||
return &QueryListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryListLogic) QueryList(req *types.QueryListReq) (resp *types.QueryListResp, err error) {
|
||||
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if getUidErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 获取用户信息失败, %+v", getUidErr)
|
||||
}
|
||||
|
||||
// 直接构建查询query表的条件
|
||||
build := l.svcCtx.QueryModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"user_id": userID,
|
||||
})
|
||||
|
||||
// 直接从query表分页查询
|
||||
queryList, total, err := l.svcCtx.QueryModel.FindPageListByPageWithTotal(l.ctx, build, req.Page, req.PageSize, "create_time DESC")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告列表查询, 查找报告列表错误, %+v", err)
|
||||
}
|
||||
|
||||
var list []types.Query
|
||||
if len(queryList) > 0 {
|
||||
for _, queryModel := range queryList {
|
||||
var query types.Query
|
||||
query.CreateTime = queryModel.CreateTime.Format("2006-01-02 15:04:05")
|
||||
query.UpdateTime = queryModel.UpdateTime.Format("2006-01-02 15:04:05")
|
||||
copyErr := copier.Copy(&query, queryModel)
|
||||
if copyErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告列表查询, 报告结构体复制失败, %+v", err)
|
||||
}
|
||||
product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, queryModel.ProductId)
|
||||
if findProductErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告列表查询, 获取商品信息失败, %+v", err)
|
||||
}
|
||||
query.ProductName = product.ProductName
|
||||
list = append(list, query)
|
||||
}
|
||||
}
|
||||
|
||||
return &types.QueryListResp{
|
||||
Total: total,
|
||||
List: list,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/common/ctxdata"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QueryProvisionalOrderLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryProvisionalOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryProvisionalOrderLogic {
|
||||
return &QueryProvisionalOrderLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryProvisionalOrderLogic) QueryProvisionalOrder(req *types.QueryProvisionalOrderReq) (resp *types.QueryProvisionalOrderResp, err error) {
|
||||
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if getUidErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取临时订单, 获取用户信息失败, %+v", getUidErr)
|
||||
}
|
||||
redisKey := fmt.Sprintf("%d:%s", userID, req.Id)
|
||||
cache, cacheErr := l.svcCtx.Redis.GetCtx(l.ctx, redisKey)
|
||||
if cacheErr != nil {
|
||||
return nil, cacheErr
|
||||
}
|
||||
var data types.QueryCacheLoad
|
||||
err = json.Unmarshal([]byte(cache), &data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取临时订单, 解析缓存内容失败, %+v", err)
|
||||
}
|
||||
|
||||
productModel, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取临时订单, 查找产品错误: %+v", err)
|
||||
}
|
||||
var product types.Product
|
||||
err = copier.Copy(&product, productModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取临时订单, 用户信息结构体复制失败: %+v", err)
|
||||
}
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return nil, fmt.Errorf("获取AES密钥失败: %+v", decodeErr)
|
||||
}
|
||||
decryptData, aesdecryptErr := crypto.AesDecrypt(data.Params, key)
|
||||
if aesdecryptErr != nil {
|
||||
return nil, fmt.Errorf("解密参数失败: %+v", aesdecryptErr)
|
||||
}
|
||||
queryParams := make(map[string]interface{})
|
||||
err = json.Unmarshal(decryptData, &queryParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析解密数据失败: %+v", err)
|
||||
}
|
||||
return &types.QueryProvisionalOrderResp{
|
||||
QueryParams: queryParams,
|
||||
Product: product,
|
||||
}, nil
|
||||
}
|
||||
44
app/main/api/internal/logic/query/queryretrylogic.go
Normal file
44
app/main/api/internal/logic/query/queryretrylogic.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QueryRetryLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryRetryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryRetryLogic {
|
||||
return &QueryRetryLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryRetryLogic) QueryRetry(req *types.QueryRetryReq) (resp *types.QueryRetryResp, err error) {
|
||||
|
||||
query, err := l.svcCtx.QueryModel.FindOne(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询重试, 查找报告失败, %+v", err)
|
||||
}
|
||||
if query.QueryState == "success" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.LOGIN_FAILED, "该报告不能重试"), "报告查询重试, 该报告不能重试, %d", query.Id)
|
||||
}
|
||||
|
||||
if asyncErr := l.svcCtx.AsynqService.SendQueryTask(query.OrderId); asyncErr != nil {
|
||||
logx.Errorf("异步任务调度失败: %v", asyncErr)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "报告查询重试, 异步任务调度失败, %+v", asyncErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
2069
app/main/api/internal/logic/query/queryservicelogic.go
Normal file
2069
app/main/api/internal/logic/query/queryservicelogic.go
Normal file
File diff suppressed because it is too large
Load Diff
60
app/main/api/internal/logic/query/querysingletestlogic.go
Normal file
60
app/main/api/internal/logic/query/querysingletestlogic.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QuerySingleTestLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQuerySingleTestLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QuerySingleTestLogic {
|
||||
return &QuerySingleTestLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QuerySingleTestLogic) QuerySingleTest(req *types.QuerySingleTestReq) (resp *types.QuerySingleTestResp, err error) {
|
||||
// _, err = l.svcCtx.FeatureModel.FindOneByApiId(l.ctx, req.Api)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 获取接口失败 : %d", err)
|
||||
// }
|
||||
|
||||
marshalParams, err := json.Marshal(req.Params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 序列化参数失败 : %d", err)
|
||||
}
|
||||
|
||||
// 创建一个30分钟超时的上下文
|
||||
timeoutCtx, cancel := context.WithTimeout(l.ctx, 30*time.Minute)
|
||||
defer cancel() // 确保在函数返回时取消上下文,防止资源泄漏
|
||||
|
||||
// 使用新的超时上下文
|
||||
apiResp, err := l.svcCtx.ApiRequestService.PreprocessRequestApi(timeoutCtx, marshalParams, req.Api)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 接口请求失败 : %d", err)
|
||||
}
|
||||
var respData interface{}
|
||||
err = json.Unmarshal(apiResp.Data, &respData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "单查测试, 反序列化接口失败 : %d", err)
|
||||
}
|
||||
return &types.QuerySingleTestResp{
|
||||
Data: respData,
|
||||
Api: req.Api,
|
||||
}, nil
|
||||
}
|
||||
70
app/main/api/internal/logic/query/updatequerydatalogic.go
Normal file
70
app/main/api/internal/logic/query/updatequerydatalogic.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type UpdateQueryDataLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewUpdateQueryDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateQueryDataLogic {
|
||||
return &UpdateQueryDataLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UpdateQueryDataLogic) UpdateQueryData(req *types.UpdateQueryDataReq) (resp *types.UpdateQueryDataResp, err error) {
|
||||
// 1. 从数据库中获取查询记录
|
||||
query, err := l.svcCtx.QueryModel.FindOne(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
if err == model.ErrNotFound {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询记录不存在, 查询ID: %d", req.Id)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询数据库失败, 查询ID: %d, err: %v", req.Id, err)
|
||||
}
|
||||
|
||||
// 2. 获取加密密钥
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取AES密钥失败: %v", decodeErr)
|
||||
}
|
||||
|
||||
// 3. 加密数据 - 传入的是JSON,需要加密处理
|
||||
encryptData, aesEncryptErr := crypto.AesEncrypt([]byte(req.QueryData), key)
|
||||
if aesEncryptErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密查询数据失败: %v", aesEncryptErr)
|
||||
}
|
||||
|
||||
// 4. 更新数据库记录
|
||||
query.QueryData = sql.NullString{
|
||||
String: encryptData,
|
||||
Valid: true,
|
||||
}
|
||||
updateErr := l.svcCtx.QueryModel.UpdateWithVersion(l.ctx, nil, query)
|
||||
if updateErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新查询数据失败: %v", updateErr)
|
||||
}
|
||||
|
||||
// 5. 返回结果
|
||||
return &types.UpdateQueryDataResp{
|
||||
Id: query.Id,
|
||||
UpdatedAt: query.UpdateTime.Format("2006-01-02 15:04:05"),
|
||||
}, nil
|
||||
}
|
||||
29
app/main/api/internal/logic/user/decryptmobilelogic.go
Normal file
29
app/main/api/internal/logic/user/decryptmobilelogic.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type DecryptMobileLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewDecryptMobileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DecryptMobileLogic {
|
||||
return &DecryptMobileLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DecryptMobileLogic) DecryptMobile() error {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return nil
|
||||
}
|
||||
56
app/main/api/internal/logic/user/detaillogic.go
Normal file
56
app/main/api/internal/logic/user/detaillogic.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/common/ctxdata"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type DetailLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic {
|
||||
return &DetailLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DetailLogic) Detail() (resp *types.UserInfoResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %+v", err)
|
||||
}
|
||||
user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_NOT_FOUND), "用户信息, 用户不存在, %v", err)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户信息, 数据库查询用户信息失败, %+v", err)
|
||||
}
|
||||
var userInfo types.User
|
||||
err = copier.Copy(&userInfo, user)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 用户信息结构体复制失败, %+v", err)
|
||||
}
|
||||
userInfo.Mobile, err = crypto.DecryptMobile(userInfo.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 解密手机号失败, %v", err)
|
||||
}
|
||||
return &types.UserInfoResp{
|
||||
UserInfo: userInfo,
|
||||
}, nil
|
||||
}
|
||||
49
app/main/api/internal/logic/user/gettokenlogic.go
Normal file
49
app/main/api/internal/logic/user/gettokenlogic.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tyc-server/common/ctxdata"
|
||||
jwtx "tyc-server/common/jwt"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetTokenLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetTokenLogic {
|
||||
return &GetTokenLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetTokenLogic) GetToken() (resp *types.MobileCodeLoginResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %+v", err)
|
||||
}
|
||||
token, generaErr := jwtx.GenerateJwtToken(userID, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire)
|
||||
if generaErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "更新token, 生成token失败 : %d", userID)
|
||||
}
|
||||
// 获取当前时间戳
|
||||
now := time.Now().Unix()
|
||||
return &types.MobileCodeLoginResp{
|
||||
AccessToken: token,
|
||||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||
}, nil
|
||||
|
||||
}
|
||||
103
app/main/api/internal/logic/user/mobilecodeloginlogic.go
Normal file
103
app/main/api/internal/logic/user/mobilecodeloginlogic.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
jwtx "tyc-server/common/jwt"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type MobileCodeLoginLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewMobileCodeLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MobileCodeLoginLogic {
|
||||
return &MobileCodeLoginLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (resp *types.MobileCodeLoginResp, err error) {
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err)
|
||||
}
|
||||
|
||||
// 检查手机号是否在一分钟内已发送过验证码
|
||||
redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile)
|
||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机登录, 验证码过期: %s", encryptedMobile)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
if cacheCode != req.Code {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile)
|
||||
}
|
||||
|
||||
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, encryptedMobile)
|
||||
if findUserErr != nil && findUserErr != model.ErrNotFound {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
if user == nil {
|
||||
user = &model.User{Mobile: encryptedMobile}
|
||||
if user.Nickname.Valid && user.Nickname.String != "" {
|
||||
user.Nickname = sql.NullString{
|
||||
String: encryptedMobile,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user)
|
||||
if userInsertErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入新用户失败, mobile: %s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
lastId, lastInsertIdErr := insertResult.LastInsertId()
|
||||
if lastInsertIdErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 获取新用户ID失败, err:%+v, user:%+v", lastInsertIdErr, user)
|
||||
}
|
||||
user.Id = lastId
|
||||
|
||||
userAuth := new(model.UserAuth)
|
||||
userAuth.UserId = lastId
|
||||
userAuth.AuthKey = encryptedMobile
|
||||
userAuth.AuthType = model.UserAuthTypeAppMobile
|
||||
if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); userAuthInsertErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入用户认证失败, err:%+v", userAuthInsertErr)
|
||||
}
|
||||
return nil
|
||||
}); transErr != nil {
|
||||
return nil, transErr
|
||||
}
|
||||
}
|
||||
token, generaErr := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire)
|
||||
if generaErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", user.Id)
|
||||
}
|
||||
|
||||
// 获取当前时间戳
|
||||
now := time.Now().Unix()
|
||||
return &types.MobileCodeLoginResp{
|
||||
AccessToken: token,
|
||||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||
}, nil
|
||||
}
|
||||
64
app/main/api/internal/logic/user/mobileloginlogic.go
Normal file
64
app/main/api/internal/logic/user/mobileloginlogic.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tyc-server/app/main/model"
|
||||
jwtx "tyc-server/common/jwt"
|
||||
"tyc-server/common/tool"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type MobileLoginLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewMobileLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MobileLoginLogic {
|
||||
return &MobileLoginLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *MobileLoginLogic) MobileLogin(req *types.MobileLoginReq) (resp *types.MobileCodeLoginResp, err error) {
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err)
|
||||
}
|
||||
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, encryptedMobile)
|
||||
if findUserErr != nil && findUserErr != model.ErrNotFound {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile%s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
if user == nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("手机号码未注册"), "手机登录, 手机号未注册:%s", encryptedMobile)
|
||||
}
|
||||
if !(tool.Md5ByString(req.Password) == lzUtils.NullStringToString(user.Password)) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("密码不正确"), "手机登录, 密码匹配不正确%s", encryptedMobile)
|
||||
}
|
||||
|
||||
token, generaErr := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire)
|
||||
if generaErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", user.Id)
|
||||
}
|
||||
|
||||
// 获取当前时间戳
|
||||
now := time.Now().Unix()
|
||||
return &types.MobileCodeLoginResp{
|
||||
AccessToken: token,
|
||||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||
}, nil
|
||||
}
|
||||
109
app/main/api/internal/logic/user/registerlogic.go
Normal file
109
app/main/api/internal/logic/user/registerlogic.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
jwtx "tyc-server/common/jwt"
|
||||
"tyc-server/common/tool"
|
||||
"tyc-server/common/xerr"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type RegisterLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
|
||||
return &RegisterLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterResp, err error) {
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机注册, 加密手机号失败: %+v", err)
|
||||
}
|
||||
// 检查手机号是否在一分钟内已发送过验证码
|
||||
redisKey := fmt.Sprintf("%s:%s", "register", encryptedMobile)
|
||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机注册, 验证码过期: %s", encryptedMobile)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
if cacheCode != req.Code {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机注册, 验证码不正确: %s", encryptedMobile)
|
||||
}
|
||||
hasUser, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, encryptedMobile)
|
||||
if findUserErr != nil && findUserErr != model.ErrNotFound {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 读取数据库获取用户失败, mobile%s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
if hasUser != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("该手机号码已注册"), "手机注册, 手机号码已注册, mobile:%s", encryptedMobile)
|
||||
}
|
||||
var userId int64
|
||||
if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
user := new(model.User)
|
||||
user.Mobile = encryptedMobile
|
||||
if user.Nickname.Valid && user.Nickname.String != "" {
|
||||
user.Nickname = sql.NullString{
|
||||
String: encryptedMobile,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if len(req.Password) > 0 {
|
||||
user.Password = lzUtils.StringToNullString(tool.Md5ByString(req.Password))
|
||||
}
|
||||
insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user)
|
||||
if userInsertErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入新用户失败, mobile%s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
lastId, lastInsertIdErr := insertResult.LastInsertId()
|
||||
if lastInsertIdErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 获取新用户ID失败, err:%+v, user:%+v", lastInsertIdErr, user)
|
||||
}
|
||||
userId = lastId
|
||||
|
||||
userAuth := new(model.UserAuth)
|
||||
userAuth.UserId = lastId
|
||||
userAuth.AuthKey = encryptedMobile
|
||||
userAuth.AuthType = model.UserAuthTypeAppMobile
|
||||
if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); userAuthInsertErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入用户认证失败, err:%+v", userAuthInsertErr)
|
||||
}
|
||||
return nil
|
||||
}); transErr != nil {
|
||||
return nil, transErr
|
||||
}
|
||||
|
||||
token, generaErr := jwtx.GenerateJwtToken(userId, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire)
|
||||
if generaErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机注册, 生成jwt token失败, userid: %d, err:%+v", userId, generaErr)
|
||||
}
|
||||
// 获取当前时间戳
|
||||
now := time.Now().Unix()
|
||||
return &types.RegisterResp{
|
||||
AccessToken: token,
|
||||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||
}, nil
|
||||
}
|
||||
136
app/main/api/internal/logic/user/wxh5authlogic.go
Normal file
136
app/main/api/internal/logic/user/wxh5authlogic.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
"tyc-server/app/main/model"
|
||||
jwtx "tyc-server/common/jwt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type WxH5AuthLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewWxH5AuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxH5AuthLogic {
|
||||
return &WxH5AuthLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthResp, err error) {
|
||||
// Step 1: 使用code获取access_token
|
||||
accessTokenResp, err := GetAccessToken(req.Code)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "获取access_token失败")
|
||||
}
|
||||
|
||||
// Step 2: 查找用户授权信息
|
||||
userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, accessTokenResp.Openid, "h5-weixin")
|
||||
if findErr != nil && !errors.Is(findErr, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(findErr, "查询用户授权失败,openid: %s", accessTokenResp.Openid)
|
||||
}
|
||||
|
||||
// Step 3: 查找或创建用户
|
||||
var user *model.User
|
||||
if userAuth != nil {
|
||||
// 授权信息存在,查找用户
|
||||
userModel, findUserErr := l.svcCtx.UserModel.FindOne(l.ctx, userAuth.UserId)
|
||||
if findUserErr != nil {
|
||||
return nil, errors.Wrapf(findUserErr, "查询用户失败,userId: %d", userAuth.UserId)
|
||||
}
|
||||
user = userModel
|
||||
} else {
|
||||
// 授权信息不存在,创建新用户
|
||||
user = &model.User{}
|
||||
if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(context context.Context, session sqlx.Session) error {
|
||||
// 插入数据库
|
||||
insertResult, insertErr := l.svcCtx.UserModel.Insert(l.ctx, session, user)
|
||||
if insertErr != nil {
|
||||
return errors.Wrapf(insertErr, "创建新用户失败,openid: %s", accessTokenResp.Openid)
|
||||
}
|
||||
// 获取插入后生成的 user.Id
|
||||
lastInsertId, lastInsertIdErr := insertResult.LastInsertId()
|
||||
if lastInsertIdErr != nil {
|
||||
return errors.Wrapf(lastInsertIdErr, "获取新用户ID失败,openid: %s", accessTokenResp.Openid)
|
||||
}
|
||||
user.Id = lastInsertId
|
||||
// 创建用户授权信息
|
||||
userAuth = &model.UserAuth{
|
||||
UserId: user.Id,
|
||||
AuthKey: accessTokenResp.Openid,
|
||||
AuthType: "mp-weixin", // 微信小程序
|
||||
}
|
||||
if _, insertUserAuthErr := l.svcCtx.UserAuthModel.Insert(l.ctx, session, userAuth); insertUserAuthErr != nil {
|
||||
return errors.Wrapf(insertUserAuthErr, "创建用户授权失败,openid: %s", accessTokenResp.Openid)
|
||||
}
|
||||
return nil
|
||||
}); transErr != nil {
|
||||
return nil, transErr
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Step 4: 生成JWT Token
|
||||
token, genErr := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire)
|
||||
if genErr != nil {
|
||||
return nil, errors.Wrap(genErr, "生成JWT token失败")
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
return &types.WXH5AuthResp{
|
||||
AccessToken: token,
|
||||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type AccessTokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
Openid string `json:"openid"`
|
||||
}
|
||||
|
||||
// GetAccessToken 通过code获取access_token
|
||||
func GetAccessToken(code string) (*AccessTokenResp, error) {
|
||||
appID := "wxd1554b7a57cecc9e"
|
||||
appSecret := "fb8026c0bc66625b580453300d4b43db"
|
||||
|
||||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appID, appSecret, code)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "获取access_token失败")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "读取access_token响应失败")
|
||||
}
|
||||
|
||||
var accessTokenResp AccessTokenResp
|
||||
if err := json.Unmarshal(body, &accessTokenResp); err != nil {
|
||||
return nil, errors.Wrap(err, "解析access_token响应失败")
|
||||
}
|
||||
|
||||
if accessTokenResp.AccessToken == "" {
|
||||
return nil, errors.New("获取access_token失败")
|
||||
}
|
||||
|
||||
return &accessTokenResp, nil
|
||||
}
|
||||
151
app/main/api/internal/logic/user/wxminiauthlogic.go
Normal file
151
app/main/api/internal/logic/user/wxminiauthlogic.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
jwtx "tyc-server/common/jwt"
|
||||
"tyc-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type WxMiniAuthLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewWxMiniAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxMiniAuthLogic {
|
||||
return &WxMiniAuthLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) {
|
||||
// 1. 使用微信提供的 code 换取 session_key 和 openid
|
||||
weChatResponse, err := l.exchangeCodeForSession(req.Code)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("微信登录失败"), "微信登录, code 换取 session 失败: %s, err: %+v", req.Code, err)
|
||||
}
|
||||
|
||||
// 2. 根据 openid 查找用户
|
||||
userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMini, weChatResponse.OpenId)
|
||||
if findErr != nil && findErr != model.ErrNotFound {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "微信登录, 读取用户认证信息失败, openid: %s, err: %+v", weChatResponse.OpenId, findErr)
|
||||
}
|
||||
|
||||
var user *model.User
|
||||
if findErr == model.ErrNotFound {
|
||||
// 用户不存在,创建新用户
|
||||
user = &model.User{}
|
||||
user.Mobile = weChatResponse.OpenId
|
||||
if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
// 插入新用户
|
||||
insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user)
|
||||
if userInsertErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "微信登录, 数据库插入新用户失败, openid: %s, err: %+v", weChatResponse.OpenId, userInsertErr)
|
||||
}
|
||||
|
||||
// 获取新用户的 ID
|
||||
lastId, lastInsertIdErr := insertResult.LastInsertId()
|
||||
if lastInsertIdErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "微信登录, 获取新用户ID失败, err:%+v, user:%+v", lastInsertIdErr, user)
|
||||
}
|
||||
user.Id = lastId
|
||||
|
||||
// 创建用户认证信息
|
||||
newUserAuth := &model.UserAuth{
|
||||
UserId: lastId,
|
||||
AuthKey: weChatResponse.OpenId,
|
||||
AuthType: model.UserAuthTypeWxMini,
|
||||
}
|
||||
if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(ctx, session, newUserAuth); userAuthInsertErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "微信登录, 数据库插入用户认证信息失败, err:%+v", userAuthInsertErr)
|
||||
}
|
||||
return nil
|
||||
}); transErr != nil {
|
||||
return nil, transErr
|
||||
}
|
||||
} else {
|
||||
// 获取用户信息
|
||||
user, err = l.svcCtx.UserModel.FindOne(l.ctx, userAuth.UserId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "微信登录, 读取用户信息失败, userId: %d, err: %+v", userAuth.UserId, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 生成 JWT 令牌
|
||||
token, generateErr := jwtx.GenerateJwtToken(user.Id, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire)
|
||||
if generateErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "微信登录, 生成token失败 : %d", user.Id)
|
||||
}
|
||||
|
||||
// 4. 获取当前时间戳
|
||||
now := time.Now().Unix()
|
||||
|
||||
return &types.WXMiniAuthResp{
|
||||
AccessToken: token,
|
||||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type WxLoginResp struct {
|
||||
OpenId string `json:"openid"`
|
||||
SessionKey string `json:"session_key"`
|
||||
Unionid string `json:"unionid"`
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
func (l *WxMiniAuthLogic) exchangeCodeForSession(code string) (response WxLoginResp, err error) {
|
||||
|
||||
// 向微信发出登录请求
|
||||
baseURL := "https://api.weixin.qq.com/sns/jscode2session"
|
||||
// 创建查询参数
|
||||
params := url.Values{}
|
||||
params.Add("appid", l.svcCtx.Config.Wxpay.AppID)
|
||||
params.Add("secret", l.svcCtx.Config.Wxpay.AppSecret)
|
||||
params.Add("js_code", code)
|
||||
params.Add("grant_type", "authorization_code")
|
||||
|
||||
// 构建完整的请求 URL
|
||||
requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
|
||||
|
||||
// 发送 GET 请求
|
||||
resp, err := http.Get(requestURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 将响应体解析为结构体
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if response.ErrCode != 0 {
|
||||
err = errors.New(response.ErrMsg)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
BrandKey = "X-Brand"
|
||||
PlatformKey = "X-Platform"
|
||||
)
|
||||
|
||||
type SourceInterceptorMiddleware struct {
|
||||
}
|
||||
|
||||
func NewSourceInterceptorMiddleware() *SourceInterceptorMiddleware {
|
||||
return &SourceInterceptorMiddleware{}
|
||||
}
|
||||
|
||||
func (m *SourceInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取请求头 X-Brand 和 X-Platform 的值
|
||||
brand := r.Header.Get(BrandKey)
|
||||
platform := r.Header.Get(PlatformKey)
|
||||
|
||||
// 将值放入新的 context 中
|
||||
ctx := r.Context()
|
||||
if brand != "" {
|
||||
ctx = context.WithValue(ctx, "brand", brand)
|
||||
}
|
||||
if platform != "" {
|
||||
ctx = context.WithValue(ctx, "platform", platform)
|
||||
}
|
||||
|
||||
// 通过 r.WithContext 将更新后的 ctx 传递给后续的处理函数
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
// 传递给下一个处理器
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
184
app/main/api/internal/queue/carMaintenanceQuery.go
Normal file
184
app/main/api/internal/queue/carMaintenanceQuery.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
// CarMaintenanceQueryHandler 车辆维保记录查询任务处理器
|
||||
type CarMaintenanceQueryHandler struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewCarMaintenanceQueryHandler 创建车辆维保记录查询处理器
|
||||
func NewCarMaintenanceQueryHandler(svcCtx *svc.ServiceContext) *CarMaintenanceQueryHandler {
|
||||
return &CarMaintenanceQueryHandler{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessTask 处理车辆维保记录查询任务
|
||||
func (h *CarMaintenanceQueryHandler) ProcessTask(ctx context.Context, task *asynq.Task) error {
|
||||
// var payload types.MsgCarMaintenanceQueryPayload
|
||||
// if err := json.Unmarshal(task.Payload(), &payload); err != nil {
|
||||
// logx.Errorf("解析车辆维保记录查询任务参数失败: %v", err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// logx.Infof("处理车辆维保记录查询任务,订单号: %s, 重试次数: %d, 查询ID: %d",
|
||||
// payload.OrderID, payload.RetryCount, payload.QueryID)
|
||||
|
||||
// // 调用apirequestService的ProcessCAR059Request方法
|
||||
// request := map[string]interface{}{
|
||||
// "order_id": payload.OrderID,
|
||||
// }
|
||||
// requestBytes, err := json.Marshal(request)
|
||||
// if err != nil {
|
||||
// logx.Errorf("车辆维保记录查询任务,编码请求参数失败: %v", err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // 调用API处理
|
||||
// result, err := h.svcCtx.ApiRequestService.ProcessCAR059Request(requestBytes)
|
||||
|
||||
// // 处理结果
|
||||
// if err != nil {
|
||||
// if result != nil {
|
||||
// // 使用gjson解析返回结果中的状态码
|
||||
// retcode := gjson.GetBytes(result.Data, "retcode").String()
|
||||
// logx.Infof("车辆维保记录查询任务,订单号: %s, 状态码: %s", payload.OrderID, retcode)
|
||||
|
||||
// // 根据不同状态码进行不同处理
|
||||
// switch retcode {
|
||||
// case "warn_029": // 未查询到结果,请稍后重试
|
||||
// logx.Infof("车辆维保记录查询任务,订单号: %s, 未查询到结果,需要重试", payload.OrderID)
|
||||
|
||||
// // 安排重试
|
||||
// return h.scheduleRetryWithBackoff(payload.OrderID, payload.RetryCount, payload.QueryID)
|
||||
|
||||
// case "warn_013": // 查无记录
|
||||
// logx.Infof("车辆维保记录查询任务,订单号: %s, 查无记录", payload.OrderID)
|
||||
// // 更新query表为查无记录
|
||||
// if payload.QueryID > 0 {
|
||||
// err = h.updateQueryResult(ctx, payload.QueryID, result.Data, "completed_no_data")
|
||||
// if err != nil {
|
||||
// logx.Errorf("更新query表失败(查无记录): %v, 查询ID: %d", err, payload.QueryID)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// logx.Infof("车辆维保记录查询任务,订单号: %s, 查询失败", payload.OrderID)
|
||||
// // 更新query表为查询失败
|
||||
// if payload.QueryID > 0 {
|
||||
// err = h.updateQueryResult(ctx, payload.QueryID, result.Data, "failed")
|
||||
// if err != nil {
|
||||
// logx.Errorf("更新query表失败(查询失败): %v, 查询ID: %d", err, payload.QueryID)
|
||||
// }
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
|
||||
// logx.Infof("车辆维保记录查询任务完成,订单号: %s, 结果数据长度: %d", payload.OrderID, len(result.Data))
|
||||
// // 更新query表为查询成功
|
||||
// if payload.QueryID > 0 {
|
||||
// err = h.updateQueryResult(ctx, payload.QueryID, result.Data, "completed")
|
||||
// if err != nil {
|
||||
// logx.Errorf("更新query表失败(查询成功): %v, 查询ID: %d", err, payload.QueryID)
|
||||
// }
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// // scheduleRetryWithBackoff 安排指数退避重试任务
|
||||
// func (h *CarMaintenanceQueryHandler) scheduleRetryWithBackoff(orderID string, retryCount int, queryID int64) error {
|
||||
// // 最大重试次数限制
|
||||
// maxRetries := 10
|
||||
// if retryCount >= maxRetries {
|
||||
// logx.Infof("车辆维保记录查询任务已达最大重试次数 %d,停止重试,订单号: %s", maxRetries, orderID)
|
||||
// // 更新query表为查询超时
|
||||
// if queryID > 0 {
|
||||
// err := h.updateQueryErrorState(context.Background(), queryID, "查询超时,已达最大重试次数")
|
||||
// if err != nil {
|
||||
// logx.Errorf("更新query表超时状态失败: %v, 查询ID: %d", err, queryID)
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // 使用指数退避策略的异步任务
|
||||
// err := h.svcCtx.AsynqService.SendCarMaintenanceQueryTaskWithBackoff(orderID, retryCount, queryID)
|
||||
// if err != nil {
|
||||
// logx.Errorf("安排指数退避重试任务失败: %v, 订单号: %s", err, orderID)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// logx.Infof("已安排指数退避重试任务, 当前重试次数: %d, 订单号: %s, 查询ID: %d", retryCount, orderID, queryID)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // updateQueryResult 更新query表的查询结果
|
||||
// func (h *CarMaintenanceQueryHandler) updateQueryResult(ctx context.Context, queryID int64, resultData []byte, state string) error {
|
||||
// // 先查询当前query记录
|
||||
// query, err := h.svcCtx.QueryModel.FindOne(ctx, queryID)
|
||||
// if err != nil {
|
||||
// if err == model.ErrNotFound {
|
||||
// return fmt.Errorf("查询记录不存在, ID: %d", queryID)
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // 更新查询结果
|
||||
// query.QueryData = sql.NullString{
|
||||
// String: string(resultData),
|
||||
// Valid: true,
|
||||
// }
|
||||
// query.QueryState = state
|
||||
// query.Version++
|
||||
|
||||
// // 更新记录
|
||||
// err = h.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("更新查询记录失败: %v", err)
|
||||
// }
|
||||
|
||||
// logx.Infof("成功更新查询记录,查询ID: %d, 状态: %s", queryID, state)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // updateQueryErrorState 更新query表为错误状态
|
||||
// func (h *CarMaintenanceQueryHandler) updateQueryErrorState(ctx context.Context, queryID int64, errorMsg string) error {
|
||||
// // 先查询当前query记录
|
||||
// query, err := h.svcCtx.QueryModel.FindOne(ctx, queryID)
|
||||
// if err != nil {
|
||||
// if err == model.ErrNotFound {
|
||||
// return fmt.Errorf("查询记录不存在, ID: %d", queryID)
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // 构造错误结果
|
||||
// errorResult := map[string]interface{}{
|
||||
// "error": errorMsg,
|
||||
// }
|
||||
// resultBytes, _ := json.Marshal(errorResult)
|
||||
|
||||
// // 更新查询结果
|
||||
// query.QueryData = sql.NullString{
|
||||
// String: string(resultBytes),
|
||||
// Valid: true,
|
||||
// }
|
||||
// query.QueryState = "failed"
|
||||
// query.Version++
|
||||
|
||||
// // 更新记录
|
||||
// err = h.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("更新查询记录失败: %v", err)
|
||||
// }
|
||||
|
||||
// logx.Infof("成功更新查询记录为失败状态,查询ID: %d, 错误: %s", queryID, errorMsg)
|
||||
// return nil
|
||||
// }
|
||||
43
app/main/api/internal/queue/cleanQueryData.go
Normal file
43
app/main/api/internal/queue/cleanQueryData.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
const TASKTIME = "0 3 * * *"
|
||||
|
||||
type CleanQueryDataHandler struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewCleanQueryDataHandler(svcCtx *svc.ServiceContext) *CleanQueryDataHandler {
|
||||
return &CleanQueryDataHandler{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CleanQueryDataHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
logx.Infof("%s - 开始执行查询数据清理任务", now)
|
||||
|
||||
// 计算7天前的时间
|
||||
sevenDaysAgo := time.Now().AddDate(0, 0, -7)
|
||||
|
||||
// 创建查询条件,排除product_id为4的记录
|
||||
conditions := l.svcCtx.QueryModel.SelectBuilder().Where("product_id != ?", 4)
|
||||
|
||||
// 调用QueryModel删除7天前的数据,排除product_id为4的记录
|
||||
result, err := l.svcCtx.QueryModel.DeleteBefore(ctx, sevenDaysAgo, conditions)
|
||||
if err != nil {
|
||||
logx.Errorf("%s - 清理查询数据失败: %v", time.Now().Format("2006-01-02 15:04:05"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
logx.Infof("%s - 查询数据清理完成,共删除 %d 条记录", time.Now().Format("2006-01-02 15:04:05"), result)
|
||||
return nil
|
||||
}
|
||||
366
app/main/api/internal/queue/paySuccessNotify.go
Normal file
366
app/main/api/internal/queue/paySuccessNotify.go
Normal file
@@ -0,0 +1,366 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type PaySuccessNotifyUserHandler struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewPaySuccessNotifyUserHandler(svcCtx *svc.ServiceContext) *PaySuccessNotifyUserHandler {
|
||||
return &PaySuccessNotifyUserHandler{
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
OrderID int64 `json:"order_id"`
|
||||
}
|
||||
|
||||
func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.Task) error {
|
||||
// 从任务的负载中解码数据
|
||||
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
|
||||
return fmt.Errorf("解析任务负载失败: %w", err)
|
||||
}
|
||||
|
||||
order, err := l.svcCtx.OrderModel.FindOne(ctx, payload.OrderID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无效的订单ID: %d, %v", payload.OrderID, err)
|
||||
}
|
||||
if order.Status != "paid" {
|
||||
err = fmt.Errorf("无效的订单: %d", payload.OrderID)
|
||||
logx.Errorf("处理任务失败,原因: %v", err)
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
product, err := l.svcCtx.ProductModel.FindOne(ctx, order.ProductId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("找不到相关产品: orderID: %d, productID: %d", payload.OrderID, order.ProductId)
|
||||
}
|
||||
|
||||
redisKey := fmt.Sprintf("%d:%s", order.UserId, order.OrderNo)
|
||||
cache, cacheErr := l.svcCtx.Redis.GetCtx(ctx, redisKey)
|
||||
if cacheErr != nil {
|
||||
return fmt.Errorf("获取缓存内容失败: %+v", cacheErr)
|
||||
}
|
||||
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
key, decodeErr := hex.DecodeString(secretKey)
|
||||
if decodeErr != nil {
|
||||
return fmt.Errorf("获取AES密钥失败: %+v", decodeErr)
|
||||
}
|
||||
var data types.QueryCacheLoad
|
||||
err = json.Unmarshal([]byte(cache), &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析缓存内容失败: %+v", err)
|
||||
}
|
||||
decryptData, aesdecryptErr := crypto.AesDecrypt(data.Params, key)
|
||||
if aesdecryptErr != nil {
|
||||
return fmt.Errorf("解密参数失败: %+v", aesdecryptErr)
|
||||
}
|
||||
// 敏感数据脱敏处理
|
||||
desensitizedParams, err := l.desensitizeParams(decryptData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("脱敏处理失败: %+v", err)
|
||||
}
|
||||
|
||||
// 对脱敏后的数据进行AES加密
|
||||
encryptedParams, encryptErr := crypto.AesEncrypt(desensitizedParams, key)
|
||||
if encryptErr != nil {
|
||||
return fmt.Errorf("加密脱敏数据失败: %+v", encryptErr)
|
||||
}
|
||||
|
||||
query := &model.Query{
|
||||
OrderId: order.Id,
|
||||
UserId: order.UserId,
|
||||
ProductId: product.Id,
|
||||
QueryParams: encryptedParams,
|
||||
QueryState: model.QueryStateProcessing,
|
||||
}
|
||||
result, insertQueryErr := l.svcCtx.QueryModel.Insert(ctx, nil, query)
|
||||
if insertQueryErr != nil {
|
||||
return fmt.Errorf("保存查询失败: %+v", insertQueryErr)
|
||||
}
|
||||
|
||||
// 获取插入后的ID
|
||||
queryId, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取插入的查询ID失败: %+v", err)
|
||||
}
|
||||
|
||||
// 从数据库中查询完整的查询记录
|
||||
query, err = l.svcCtx.QueryModel.FindOne(ctx, queryId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取插入后的查询记录失败: %+v", err)
|
||||
}
|
||||
combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(ctx, decryptData, product.Id)
|
||||
if err != nil {
|
||||
handleErrorErr := fmt.Errorf("处理请求失败: %v", err)
|
||||
return l.handleError(ctx, handleErrorErr, order, query)
|
||||
}
|
||||
// 加密返回响应
|
||||
encryptData, aesEncryptErr := crypto.AesEncrypt(combinedResponse, key)
|
||||
if aesEncryptErr != nil {
|
||||
err = fmt.Errorf("加密响应信息失败: %v", aesEncryptErr)
|
||||
return l.handleError(ctx, err, order, query)
|
||||
}
|
||||
query.QueryData = lzUtils.StringToNullString(encryptData)
|
||||
updateErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
|
||||
if updateErr != nil {
|
||||
err = fmt.Errorf("保存响应数据失败: %v", updateErr)
|
||||
return l.handleError(ctx, err, order, query)
|
||||
}
|
||||
|
||||
query.QueryState = "success"
|
||||
updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
|
||||
if updateQueryErr != nil {
|
||||
updateQueryErr = fmt.Errorf("修改查询状态失败: %v", updateQueryErr)
|
||||
return l.handleError(ctx, updateQueryErr, order, query)
|
||||
}
|
||||
|
||||
_, delErr := l.svcCtx.Redis.DelCtx(ctx, redisKey)
|
||||
if delErr != nil {
|
||||
logx.Errorf("删除Redis缓存失败,但任务已成功处理,订单ID: %d, 错误: %v", order.Id, delErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 定义一个中间件函数
|
||||
func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error, order *model.Order, query *model.Query) error {
|
||||
logx.Errorf("处理任务失败,原因: %v", err)
|
||||
redisKey := fmt.Sprintf("%d:%s", order.UserId, order.OrderNo)
|
||||
_, delErr := l.svcCtx.Redis.DelCtx(ctx, redisKey)
|
||||
if delErr != nil {
|
||||
logx.Errorf("删除Redis缓存失败,订单ID: %d, 错误: %v", order.Id, delErr)
|
||||
}
|
||||
if order.Status == "paid" && query.QueryState == model.QueryStateProcessing {
|
||||
// 更新查询状态为失败
|
||||
query.QueryState = model.QueryStateFailed
|
||||
updateQueryErr := l.svcCtx.QueryModel.UpdateWithVersion(ctx, nil, query)
|
||||
if updateQueryErr != nil {
|
||||
logx.Errorf("更新查询状态失败,订单ID: %d, 错误: %v", order.Id, updateQueryErr)
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
|
||||
// 退款
|
||||
if order.PaymentPlatform == "wechat" {
|
||||
refundErr := l.svcCtx.WechatPayService.WeChatRefund(ctx, order.OrderNo, order.Amount, order.Amount)
|
||||
if refundErr != nil {
|
||||
logx.Error(refundErr)
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
} else {
|
||||
refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount)
|
||||
if refundErr != nil {
|
||||
logx.Error(refundErr)
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
if refund.IsSuccess() {
|
||||
logx.Errorf("支付宝退款成功, orderID: %d", order.Id)
|
||||
// 更新订单状态为退款
|
||||
order.Status = "refunded"
|
||||
updateOrderErr := l.svcCtx.OrderModel.UpdateWithVersion(ctx, nil, order)
|
||||
if updateOrderErr != nil {
|
||||
logx.Errorf("更新订单状态失败,订单ID: %d, 错误: %v", order.Id, updateOrderErr)
|
||||
return fmt.Errorf("更新订单状态失败: %v", updateOrderErr)
|
||||
}
|
||||
return asynq.SkipRetry
|
||||
} else {
|
||||
logx.Errorf("支付宝退款失败:%v", refundErr)
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
// 直接成功
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
|
||||
// desensitizeParams 对敏感数据进行脱敏处理
|
||||
func (l *PaySuccessNotifyUserHandler) desensitizeParams(data []byte) ([]byte, error) {
|
||||
// 解析JSON数据到map
|
||||
var paramsMap map[string]interface{}
|
||||
if err := json.Unmarshal(data, ¶msMap); err != nil {
|
||||
return nil, fmt.Errorf("解析JSON数据失败: %v", err)
|
||||
}
|
||||
|
||||
// 处理可能包含敏感信息的字段
|
||||
for key, value := range paramsMap {
|
||||
if strValue, ok := value.(string); ok {
|
||||
// 根据字段名和内容判断并脱敏
|
||||
if isNameField(key) && len(strValue) > 0 {
|
||||
// 姓名脱敏
|
||||
paramsMap[key] = maskName(strValue)
|
||||
} else if isIDCardField(key) && len(strValue) > 10 {
|
||||
// 身份证号脱敏
|
||||
paramsMap[key] = maskIDCard(strValue)
|
||||
} else if isPhoneField(key) && len(strValue) >= 8 {
|
||||
// 手机号脱敏
|
||||
paramsMap[key] = maskPhone(strValue)
|
||||
} else if len(strValue) > 3 {
|
||||
// 其他所有未匹配的字段都进行通用脱敏
|
||||
paramsMap[key] = maskGeneral(strValue)
|
||||
}
|
||||
} else if mapValue, ok := value.(map[string]interface{}); ok {
|
||||
// 递归处理嵌套的map
|
||||
for subKey, subValue := range mapValue {
|
||||
if subStrValue, ok := subValue.(string); ok {
|
||||
if isNameField(subKey) && len(subStrValue) > 0 {
|
||||
mapValue[subKey] = maskName(subStrValue)
|
||||
} else if isIDCardField(subKey) && len(subStrValue) > 10 {
|
||||
mapValue[subKey] = maskIDCard(subStrValue)
|
||||
} else if isPhoneField(subKey) && len(subStrValue) >= 8 {
|
||||
mapValue[subKey] = maskPhone(subStrValue)
|
||||
} else if len(subStrValue) > 3 {
|
||||
// 其他所有未匹配的字段都进行通用脱敏
|
||||
mapValue[subKey] = maskGeneral(subStrValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将处理后的map重新序列化为JSON
|
||||
return json.Marshal(paramsMap)
|
||||
}
|
||||
|
||||
// 判断是否为姓名字段
|
||||
func isNameField(key string) bool {
|
||||
key = strings.ToLower(key)
|
||||
return strings.Contains(key, "name") || strings.Contains(key, "姓名") ||
|
||||
strings.Contains(key, "owner") || strings.Contains(key, "user")
|
||||
}
|
||||
|
||||
// 判断是否为身份证字段
|
||||
func isIDCardField(key string) bool {
|
||||
key = strings.ToLower(key)
|
||||
return strings.Contains(key, "idcard") || strings.Contains(key, "id_card") ||
|
||||
strings.Contains(key, "身份证") || strings.Contains(key, "证件号")
|
||||
}
|
||||
|
||||
// 判断是否为手机号字段
|
||||
func isPhoneField(key string) bool {
|
||||
key = strings.ToLower(key)
|
||||
return strings.Contains(key, "phone") || strings.Contains(key, "mobile") ||
|
||||
strings.Contains(key, "手机") || strings.Contains(key, "电话")
|
||||
}
|
||||
|
||||
// 判断是否包含敏感数据模式
|
||||
func containsSensitivePattern(value string) bool {
|
||||
// 检查是否包含连续的数字或字母模式
|
||||
numPattern := regexp.MustCompile(`\d{6,}`)
|
||||
return numPattern.MatchString(value)
|
||||
}
|
||||
|
||||
// 姓名脱敏
|
||||
func maskName(name string) string {
|
||||
// 将字符串转换为rune切片以正确处理中文字符
|
||||
runes := []rune(name)
|
||||
length := len(runes)
|
||||
|
||||
if length <= 1 {
|
||||
return name
|
||||
}
|
||||
|
||||
if length == 2 {
|
||||
// 两个字:保留第一个字,第二个字用*替代
|
||||
return string(runes[0]) + "*"
|
||||
}
|
||||
|
||||
// 三个字及以上:保留首尾字,中间用*替代
|
||||
first := string(runes[0])
|
||||
last := string(runes[length-1])
|
||||
mask := strings.Repeat("*", length-2)
|
||||
|
||||
return first + mask + last
|
||||
}
|
||||
|
||||
// 身份证号脱敏
|
||||
func maskIDCard(idCard string) string {
|
||||
length := len(idCard)
|
||||
if length <= 10 {
|
||||
return idCard // 如果长度太短,可能不是身份证,不处理
|
||||
}
|
||||
// 保留前3位和后4位
|
||||
return idCard[:3] + strings.Repeat("*", length-7) + idCard[length-4:]
|
||||
}
|
||||
|
||||
// 手机号脱敏
|
||||
func maskPhone(phone string) string {
|
||||
length := len(phone)
|
||||
if length < 8 {
|
||||
return phone // 如果长度太短,可能不是手机号,不处理
|
||||
}
|
||||
// 保留前3位和后4位
|
||||
return phone[:3] + strings.Repeat("*", length-7) + phone[length-4:]
|
||||
}
|
||||
|
||||
// 通用敏感信息脱敏 - 根据字符串长度比例进行脱敏
|
||||
func maskGeneral(value string) string {
|
||||
length := len(value)
|
||||
|
||||
// 小于3个字符的不脱敏
|
||||
if length <= 3 {
|
||||
return value
|
||||
}
|
||||
|
||||
// 根据字符串长度计算保留字符数
|
||||
var prefixLen, suffixLen int
|
||||
|
||||
switch {
|
||||
case length <= 6: // 短字符串
|
||||
// 保留首尾各1个字符
|
||||
prefixLen, suffixLen = 1, 1
|
||||
case length <= 10: // 中等长度字符串
|
||||
// 保留首部30%和尾部20%的字符
|
||||
prefixLen = int(float64(length) * 0.3)
|
||||
suffixLen = int(float64(length) * 0.2)
|
||||
case length <= 20: // 较长字符串
|
||||
// 保留首部25%和尾部15%的字符
|
||||
prefixLen = int(float64(length) * 0.25)
|
||||
suffixLen = int(float64(length) * 0.15)
|
||||
default: // 非常长的字符串
|
||||
// 保留首部20%和尾部10%的字符
|
||||
prefixLen = int(float64(length) * 0.2)
|
||||
suffixLen = int(float64(length) * 0.1)
|
||||
}
|
||||
|
||||
// 确保至少有一个字符被保留
|
||||
if prefixLen < 1 {
|
||||
prefixLen = 1
|
||||
}
|
||||
if suffixLen < 1 {
|
||||
suffixLen = 1
|
||||
}
|
||||
|
||||
// 确保前缀和后缀总长不超过总长度的80%
|
||||
if prefixLen+suffixLen > int(float64(length)*0.8) {
|
||||
// 调整为总长度的80%
|
||||
totalVisible := int(float64(length) * 0.8)
|
||||
// 前缀占60%,后缀占40%
|
||||
prefixLen = int(float64(totalVisible) * 0.6)
|
||||
suffixLen = totalVisible - prefixLen
|
||||
}
|
||||
|
||||
// 创建脱敏后的字符串
|
||||
prefix := value[:prefixLen]
|
||||
suffix := value[length-suffixLen:]
|
||||
masked := strings.Repeat("*", length-prefixLen-suffixLen)
|
||||
|
||||
return prefix + masked + suffix
|
||||
}
|
||||
41
app/main/api/internal/queue/routes.go
Normal file
41
app/main/api/internal/queue/routes.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
type CronJob struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewCronJob(ctx context.Context, svcCtx *svc.ServiceContext) *CronJob {
|
||||
return &CronJob{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CronJob) Register() *asynq.ServeMux {
|
||||
redisClientOpt := asynq.RedisClientOpt{Addr: l.svcCtx.Config.CacheRedis[0].Host, Password: l.svcCtx.Config.CacheRedis[0].Pass}
|
||||
scheduler := asynq.NewScheduler(redisClientOpt, nil)
|
||||
task := asynq.NewTask(types.MsgCleanQueryData, nil, nil)
|
||||
_, err := scheduler.Register(TASKTIME, task)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("定时任务注册失败:%v", err))
|
||||
}
|
||||
scheduler.Start()
|
||||
fmt.Println("定时任务启动!!!")
|
||||
|
||||
mux := asynq.NewServeMux()
|
||||
mux.Handle(types.MsgPaySuccessQuery, NewPaySuccessNotifyUserHandler(l.svcCtx))
|
||||
mux.Handle(types.MsgCleanQueryData, NewCleanQueryDataHandler(l.svcCtx))
|
||||
mux.Handle(types.MsgCarMaintenanceQuery, NewCarMaintenanceQueryHandler(l.svcCtx))
|
||||
|
||||
return mux
|
||||
}
|
||||
196
app/main/api/internal/service/alipayService.go
Normal file
196
app/main/api/internal/service/alipayService.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
)
|
||||
|
||||
type AliPayService struct {
|
||||
config config.AlipayConfig
|
||||
AlipayClient *alipay.Client
|
||||
}
|
||||
|
||||
// NewAliPayService 是一个构造函数,用于初始化 AliPayService
|
||||
func NewAliPayService(c config.Config) *AliPayService {
|
||||
client, err := alipay.New(c.Alipay.AppID, c.Alipay.PrivateKey, c.Alipay.IsProduction)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("创建支付宝客户端失败: %v", err))
|
||||
}
|
||||
|
||||
// 加载支付宝公钥
|
||||
err = client.LoadAliPayPublicKey(c.Alipay.AlipayPublicKey)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("加载支付宝公钥失败: %v", err))
|
||||
}
|
||||
return &AliPayService{
|
||||
config: c.Alipay,
|
||||
AlipayClient: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AliPayService) CreateAlipayAppOrder(amount float64, subject string, outTradeNo string) (string, error) {
|
||||
client := a.AlipayClient
|
||||
totalAmount := lzUtils.ToAlipayAmount(amount)
|
||||
// 构造移动支付请求
|
||||
p := alipay.TradeAppPay{
|
||||
Trade: alipay.Trade{
|
||||
Subject: subject,
|
||||
OutTradeNo: outTradeNo,
|
||||
TotalAmount: totalAmount,
|
||||
ProductCode: "QUICK_MSECURITY_PAY", // 移动端支付专用代码
|
||||
NotifyURL: a.config.NotifyUrl, // 异步回调通知地址
|
||||
},
|
||||
}
|
||||
|
||||
// 获取APP支付字符串,这里会签名
|
||||
payStr, err := client.TradeAppPay(p)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建支付宝订单失败: %v", err)
|
||||
}
|
||||
|
||||
return payStr, nil
|
||||
}
|
||||
|
||||
// CreateAlipayH5Order 创建支付宝H5支付订单
|
||||
func (a *AliPayService) CreateAlipayH5Order(amount float64, subject string, outTradeNo string, brand string) (string, error) {
|
||||
var returnURL string
|
||||
var notifyURL string
|
||||
if brand == "tyc" {
|
||||
returnURL = "https://www.tianyuancha.cn/report"
|
||||
notifyURL = "https://www.tianyuancha.cn/api/v1/pay/alipay/callback"
|
||||
} else {
|
||||
returnURL = a.config.ReturnURL
|
||||
notifyURL = a.config.NotifyUrl
|
||||
}
|
||||
client := a.AlipayClient
|
||||
totalAmount := lzUtils.ToAlipayAmount(amount)
|
||||
// 构造H5支付请求
|
||||
p := alipay.TradeWapPay{
|
||||
Trade: alipay.Trade{
|
||||
Subject: subject,
|
||||
OutTradeNo: outTradeNo,
|
||||
TotalAmount: totalAmount,
|
||||
ProductCode: "QUICK_WAP_PAY", // H5支付专用产品码
|
||||
NotifyURL: notifyURL, // 异步回调通知地址
|
||||
ReturnURL: returnURL,
|
||||
},
|
||||
}
|
||||
// 获取H5支付请求字符串,这里会签名
|
||||
payUrl, err := client.TradeWapPay(p)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建支付宝H5订单失败: %v", err)
|
||||
}
|
||||
|
||||
return payUrl.String(), nil
|
||||
}
|
||||
|
||||
// CreateAlipayOrder 根据平台类型创建支付宝支付订单
|
||||
func (a *AliPayService) CreateAlipayOrder(ctx context.Context, amount float64, subject string, outTradeNo string, brand string) (string, error) {
|
||||
// 根据 ctx 中的 platform 判断平台
|
||||
platform, platformOk := ctx.Value("platform").(string)
|
||||
if !platformOk {
|
||||
return "", fmt.Errorf("无的支付平台: %s", platform)
|
||||
}
|
||||
switch platform {
|
||||
case "app":
|
||||
// 调用App支付的创建方法
|
||||
return a.CreateAlipayAppOrder(amount, subject, outTradeNo)
|
||||
case "h5":
|
||||
// 调用H5支付的创建方法,并传入 returnUrl
|
||||
return a.CreateAlipayH5Order(amount, subject, outTradeNo, brand)
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的支付平台: %s", platform)
|
||||
}
|
||||
}
|
||||
|
||||
// AliRefund 发起支付宝退款
|
||||
func (a *AliPayService) AliRefund(ctx context.Context, outTradeNo string, refundAmount float64) (*alipay.TradeRefundRsp, error) {
|
||||
refund := alipay.TradeRefund{
|
||||
OutTradeNo: outTradeNo,
|
||||
RefundAmount: lzUtils.ToAlipayAmount(refundAmount),
|
||||
OutRequestNo: fmt.Sprintf("%s-refund", outTradeNo),
|
||||
}
|
||||
|
||||
// 发起退款请求
|
||||
refundResp, err := a.AlipayClient.TradeRefund(ctx, refund)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("支付宝退款请求错误:%v", err)
|
||||
}
|
||||
return refundResp, nil
|
||||
}
|
||||
|
||||
// HandleAliPaymentNotification 支付宝支付回调
|
||||
func (a *AliPayService) HandleAliPaymentNotification(r *http.Request) (*alipay.Notification, error) {
|
||||
// 解析表单
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析请求表单失败:%v", err)
|
||||
}
|
||||
// 解析并验证通知,DecodeNotification 会自动验证签名
|
||||
notification, err := a.AlipayClient.DecodeNotification(r.Form)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("验证签名失败: %v", err)
|
||||
}
|
||||
return notification, nil
|
||||
}
|
||||
func (a *AliPayService) QueryOrderStatus(ctx context.Context, outTradeNo string) (*alipay.TradeQueryRsp, error) {
|
||||
queryRequest := alipay.TradeQuery{
|
||||
OutTradeNo: outTradeNo,
|
||||
}
|
||||
|
||||
// 发起查询请求
|
||||
resp, err := a.AlipayClient.TradeQuery(ctx, queryRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询支付宝订单失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回交易状态
|
||||
if resp.IsSuccess() {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("查询支付宝订单失败: %v", resp.SubMsg)
|
||||
}
|
||||
|
||||
// 添加全局原子计数器
|
||||
var alipayOrderCounter uint32 = 0
|
||||
|
||||
// GenerateOutTradeNo 生成唯一订单号的函数 - 优化版本
|
||||
func (a *AliPayService) GenerateOutTradeNo() string {
|
||||
|
||||
// 获取当前时间戳(毫秒级)
|
||||
timestamp := time.Now().UnixMilli()
|
||||
timeStr := strconv.FormatInt(timestamp, 10)
|
||||
|
||||
// 原子递增计数器
|
||||
counter := atomic.AddUint32(&alipayOrderCounter, 1)
|
||||
|
||||
// 生成4字节真随机数
|
||||
randomBytes := make([]byte, 4)
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
// 如果随机数生成失败,回退到使用时间纳秒数据
|
||||
randomBytes = []byte(strconv.FormatInt(time.Now().UnixNano()%1000000, 16))
|
||||
}
|
||||
randomHex := hex.EncodeToString(randomBytes)
|
||||
|
||||
// 组合所有部分: 前缀 + 时间戳 + 计数器 + 随机数
|
||||
orderNo := fmt.Sprintf("%s%06x%s", timeStr[:10], counter%0xFFFFFF, randomHex[:6])
|
||||
|
||||
// 确保长度不超过32字符(大多数支付平台的限制)
|
||||
if len(orderNo) > 32 {
|
||||
orderNo = orderNo[:32]
|
||||
}
|
||||
|
||||
return orderNo
|
||||
}
|
||||
2628
app/main/api/internal/service/apirequestService.go
Normal file
2628
app/main/api/internal/service/apirequestService.go
Normal file
File diff suppressed because it is too large
Load Diff
169
app/main/api/internal/service/applepayService.go
Normal file
169
app/main/api/internal/service/applepayService.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
// ApplePayService 是 Apple IAP 支付服务的结构体
|
||||
type ApplePayService struct {
|
||||
config config.ApplepayConfig // 配置项
|
||||
}
|
||||
|
||||
// NewApplePayService 是一个构造函数,用于初始化 ApplePayService
|
||||
func NewApplePayService(c config.Config) *ApplePayService {
|
||||
return &ApplePayService{
|
||||
config: c.Applepay,
|
||||
}
|
||||
}
|
||||
func (a *ApplePayService) GetIappayAppID(productName string) string {
|
||||
return fmt.Sprintf("%s.%s", a.config.BundleID, productName)
|
||||
}
|
||||
|
||||
// VerifyReceipt 验证苹果支付凭证
|
||||
func (a *ApplePayService) VerifyReceipt(ctx context.Context, receipt string) (*AppleVerifyResponse, error) {
|
||||
var reqUrl string
|
||||
if a.config.Sandbox {
|
||||
reqUrl = a.config.SandboxVerifyURL
|
||||
} else {
|
||||
reqUrl = a.config.ProductionVerifyURL
|
||||
}
|
||||
|
||||
// 读取私钥
|
||||
privateKey, err := loadPrivateKey(a.config.LoadPrivateKeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("加载私钥失败:%v", err)
|
||||
}
|
||||
|
||||
// 生成 JWT
|
||||
token, err := generateJWT(privateKey, a.config.KeyID, a.config.IssuerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成JWT失败:%v", err)
|
||||
}
|
||||
|
||||
// 构造查询参数
|
||||
queryParams := fmt.Sprintf("?receipt-data=%s", receipt)
|
||||
fullUrl := reqUrl + queryParams
|
||||
|
||||
// 构建 HTTP GET 请求
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullUrl, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建 HTTP 请求失败:%v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求苹果验证接口失败:%v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 解析响应
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应体失败:%v", err)
|
||||
}
|
||||
|
||||
var verifyResponse AppleVerifyResponse
|
||||
err = json.Unmarshal(body, &verifyResponse)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析响应体失败:%v", err)
|
||||
}
|
||||
|
||||
// 根据实际响应处理逻辑
|
||||
if verifyResponse.Status != 0 {
|
||||
return nil, fmt.Errorf("验证失败,状态码:%d", verifyResponse.Status)
|
||||
}
|
||||
|
||||
return &verifyResponse, nil
|
||||
}
|
||||
|
||||
func loadPrivateKey(path string) (*ecdsa.PrivateKey, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil || block.Type != "PRIVATE KEY" {
|
||||
return nil, fmt.Errorf("无效的私钥数据")
|
||||
}
|
||||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdsaKey, ok := key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("私钥类型错误")
|
||||
}
|
||||
return ecdsaKey, nil
|
||||
}
|
||||
|
||||
func generateJWT(privateKey *ecdsa.PrivateKey, keyID, issuerID string) (string, error) {
|
||||
now := time.Now()
|
||||
claims := jwt.RegisteredClaims{
|
||||
Issuer: issuerID,
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(1 * time.Hour)),
|
||||
Audience: jwt.ClaimStrings{"appstoreconnect-v1"},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
|
||||
token.Header["kid"] = keyID
|
||||
tokenString, err := token.SignedString(privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
// GenerateOutTradeNo 生成唯一订单号
|
||||
func (a *ApplePayService) GenerateOutTradeNo() string {
|
||||
length := 16
|
||||
timestamp := time.Now().UnixNano()
|
||||
timeStr := strconv.FormatInt(timestamp, 10)
|
||||
randomPart := strconv.Itoa(int(timestamp % 1e6))
|
||||
combined := timeStr + randomPart
|
||||
|
||||
if len(combined) >= length {
|
||||
return combined[:length]
|
||||
}
|
||||
|
||||
for len(combined) < length {
|
||||
combined += strconv.Itoa(int(timestamp % 10))
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
|
||||
// AppleVerifyResponse 定义苹果验证接口的响应结构
|
||||
type AppleVerifyResponse struct {
|
||||
Status int `json:"status"` // 验证状态码:0 表示收据有效
|
||||
Receipt *Receipt `json:"receipt"` // 收据信息
|
||||
}
|
||||
|
||||
// Receipt 定义收据的精简结构
|
||||
type Receipt struct {
|
||||
BundleID string `json:"bundle_id"` // 应用的 Bundle ID
|
||||
InApp []InAppItem `json:"in_app"` // 应用内购买记录
|
||||
}
|
||||
|
||||
// InAppItem 定义单条交易记录
|
||||
type InAppItem struct {
|
||||
ProductID string `json:"product_id"` // 商品 ID
|
||||
TransactionID string `json:"transaction_id"` // 交易 ID
|
||||
PurchaseDate string `json:"purchase_date"` // 购买日期 (ISO 8601)
|
||||
OriginalTransID string `json:"original_transaction_id"` // 原始交易 ID
|
||||
}
|
||||
132
app/main/api/internal/service/asynqService.go
Normal file
132
app/main/api/internal/service/asynqService.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// asynq_service.go
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type AsynqService struct {
|
||||
client *asynq.Client
|
||||
config config.Config
|
||||
}
|
||||
|
||||
// NewAsynqService 创建并初始化 Asynq 客户端
|
||||
func NewAsynqService(c config.Config) *AsynqService {
|
||||
client := asynq.NewClient(asynq.RedisClientOpt{
|
||||
Addr: c.CacheRedis[0].Host,
|
||||
Password: c.CacheRedis[0].Pass,
|
||||
})
|
||||
|
||||
return &AsynqService{client: client, config: c}
|
||||
}
|
||||
|
||||
// Close 关闭 Asynq 客户端
|
||||
func (s *AsynqService) Close() error {
|
||||
return s.client.Close()
|
||||
}
|
||||
|
||||
func (s *AsynqService) SendQueryTask(orderID int64) error {
|
||||
// 准备任务的 payload
|
||||
payload := types.MsgPaySuccessQueryPayload{
|
||||
OrderID: orderID,
|
||||
}
|
||||
payloadBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
logx.Errorf("发送异步任务失败 (无法编码 payload): %v, 订单号: %d", err, orderID)
|
||||
return err // 直接返回错误,避免继续执行
|
||||
}
|
||||
|
||||
options := []asynq.Option{
|
||||
asynq.MaxRetry(5), // 设置最大重试次数
|
||||
}
|
||||
// 创建任务
|
||||
task := asynq.NewTask(types.MsgPaySuccessQuery, payloadBytes, options...)
|
||||
|
||||
// 将任务加入队列并获取任务信息
|
||||
info, err := s.client.Enqueue(task)
|
||||
if err != nil {
|
||||
logx.Errorf("发送异步任务失败 (加入队列失败): %+v, 订单号: %d", err, orderID)
|
||||
return err
|
||||
}
|
||||
|
||||
// 记录成功日志,带上任务 ID 和队列信息
|
||||
logx.Infof("发送异步任务成功,任务ID: %s, 队列: %s, 订单号: %d", info.ID, info.Queue, orderID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendCarMaintenanceQueryTaskWithBackoff 发送带有指数退避重试策略的车辆维保记录查询的异步任务
|
||||
func (s *AsynqService) SendCarMaintenanceQueryTaskWithBackoff(orderID string, retryCount int, queryID int64) error {
|
||||
// 准备任务的 payload
|
||||
payload := types.MsgCarMaintenanceQueryPayload{
|
||||
OrderID: orderID,
|
||||
RetryCount: retryCount + 1, // 增加重试次数
|
||||
QueryID: queryID, // 关联的query表记录ID
|
||||
}
|
||||
payloadBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
logx.Errorf("发送重试车辆维保记录查询任务失败 (无法编码 payload): %v, 订单号: %s", err, orderID)
|
||||
return err
|
||||
}
|
||||
|
||||
// 确定延迟时间
|
||||
var delay time.Duration
|
||||
|
||||
if retryCount == 0 {
|
||||
// 第一次执行,立即进行
|
||||
delay = 0
|
||||
} else {
|
||||
// 非首次执行,使用指数退避策略
|
||||
// 基础延迟为3秒,每次重试后延迟时间翻倍,最长不超过1小时
|
||||
baseDelay := 3 * time.Second
|
||||
maxDelay := 1 * time.Hour
|
||||
|
||||
delay = baseDelay
|
||||
for i := 1; i < retryCount; i++ {
|
||||
delay = delay * 2
|
||||
if delay > maxDelay {
|
||||
delay = maxDelay
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存延迟时间到payload
|
||||
payload.DelayDuration = int64(delay)
|
||||
payloadBytes, _ = json.Marshal(payload)
|
||||
|
||||
options := []asynq.Option{
|
||||
asynq.MaxRetry(0), // 使用我们自己的重试逻辑,不使用asynq的自动重试
|
||||
}
|
||||
|
||||
// 如果有延迟,添加延迟选项
|
||||
if delay > 0 {
|
||||
options = append(options, asynq.ProcessIn(delay))
|
||||
}
|
||||
|
||||
// 创建任务
|
||||
task := asynq.NewTask(types.MsgCarMaintenanceQuery, payloadBytes, options...)
|
||||
|
||||
// 将任务加入队列并获取任务信息
|
||||
info, err := s.client.Enqueue(task)
|
||||
if err != nil {
|
||||
logx.Errorf("发送重试车辆维保记录查询任务失败 (加入队列失败): %+v, 订单号: %s", err, orderID)
|
||||
return err
|
||||
}
|
||||
|
||||
// 记录成功日志,带上任务 ID 和队列信息
|
||||
if delay == 0 {
|
||||
logx.Infof("发送车辆维保记录查询任务成功,立即执行,任务ID: %s, 队列: %s, 订单号: %s, 查询ID: %d",
|
||||
info.ID, info.Queue, orderID, queryID)
|
||||
} else {
|
||||
logx.Infof("发送重试车辆维保记录查询任务成功,任务ID: %s, 队列: %s, 延迟: %v, 重试次数: %d, 订单号: %s, 查询ID: %d",
|
||||
info.ID, info.Queue, delay, retryCount+1, orderID, queryID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
88
app/main/api/internal/service/tianjuService.go
Normal file
88
app/main/api/internal/service/tianjuService.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type TianjuService struct {
|
||||
config config.TianjuConfig
|
||||
}
|
||||
|
||||
func NewTianjuService(c config.Config) *TianjuService {
|
||||
return &TianjuService{
|
||||
config: c.TianjuConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TianjuService) Request(apiPath string, params map[string]interface{}) ([]byte, error) {
|
||||
// 确保params中包含key参数
|
||||
reqParams := make(map[string]interface{})
|
||||
|
||||
// 复制用户参数
|
||||
for k, v := range params {
|
||||
reqParams[k] = v
|
||||
}
|
||||
|
||||
// 如果未提供key,则使用配置中的ApiKey
|
||||
if _, ok := reqParams["key"]; !ok {
|
||||
reqParams["key"] = t.config.ApiKey
|
||||
}
|
||||
|
||||
// 构建完整的URL,假设BaseURL已包含https://前缀
|
||||
fullURL := fmt.Sprintf("%s/%s/index", strings.TrimRight(t.config.BaseURL, "/"), apiPath)
|
||||
|
||||
// 构建表单数据
|
||||
formData := url.Values{}
|
||||
for k, v := range reqParams {
|
||||
// 将不同类型的值转换为字符串
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
formData.Add(k, val)
|
||||
case int, int64, float64:
|
||||
formData.Add(k, fmt.Sprintf("%v", val))
|
||||
default:
|
||||
// 对于复杂类型,转为JSON字符串
|
||||
jsonBytes, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("参数值序列化失败: %w", err)
|
||||
}
|
||||
formData.Add(k, string(jsonBytes))
|
||||
}
|
||||
}
|
||||
|
||||
// 发起HTTP请求 - 使用表单数据
|
||||
resp, err := http.PostForm(fullURL, formData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送请求失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
|
||||
// 检查响应状态码
|
||||
code := gjson.GetBytes(body, "code").Int()
|
||||
if code != 200 {
|
||||
msg := gjson.GetBytes(body, "msg").String()
|
||||
return nil, fmt.Errorf("天聚请求失败: 状态码 %d, 信息: %s", code, msg)
|
||||
}
|
||||
|
||||
// 获取结果数据
|
||||
result := gjson.GetBytes(body, "result")
|
||||
if !result.Exists() {
|
||||
return nil, fmt.Errorf("天聚请求result为空: %s", string(body))
|
||||
}
|
||||
|
||||
return []byte(result.Raw), nil
|
||||
}
|
||||
218
app/main/api/internal/service/verificationService.go
Normal file
218
app/main/api/internal/service/verificationService.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type VerificationService struct {
|
||||
c config.Config
|
||||
westDexService *WestDexService
|
||||
apiRequestService *ApiRequestService
|
||||
}
|
||||
|
||||
func NewVerificationService(c config.Config, westDexService *WestDexService, apiRequestService *ApiRequestService) *VerificationService {
|
||||
return &VerificationService{
|
||||
c: c,
|
||||
westDexService: westDexService,
|
||||
apiRequestService: apiRequestService,
|
||||
}
|
||||
}
|
||||
|
||||
// 二要素
|
||||
type TwoFactorVerificationRequest struct {
|
||||
Name string
|
||||
IDCard string
|
||||
}
|
||||
type TwoFactorVerificationResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Success bool `json:"success"`
|
||||
Code int `json:"code"`
|
||||
Data *TwoFactorVerificationData `json:"data"` //
|
||||
}
|
||||
type TwoFactorVerificationData struct {
|
||||
Birthday string `json:"birthday"`
|
||||
Result int `json:"result"`
|
||||
Address string `json:"address"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Sex string `json:"sex"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// 三要素
|
||||
type ThreeFactorVerificationRequest struct {
|
||||
Name string
|
||||
IDCard string
|
||||
Mobile string
|
||||
}
|
||||
|
||||
// VerificationResult 定义校验结果结构体
|
||||
type VerificationResult struct {
|
||||
Passed bool
|
||||
Err error
|
||||
}
|
||||
|
||||
// ValidationError 定义校验错误类型
|
||||
type ValidationError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (r *VerificationService) TwoFactorVerification(request TwoFactorVerificationRequest) (*VerificationResult, error) {
|
||||
appCode := r.c.Ali.Code
|
||||
requestUrl := "https://kzidcardv1.market.alicloudapi.com/api-mall/api/id_card/check"
|
||||
|
||||
// 构造查询参数
|
||||
data := url.Values{}
|
||||
data.Add("name", request.Name)
|
||||
data.Add("idcard", request.IDCard)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, requestUrl, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建请求失败: %+v", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "APPCODE "+appCode)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %+v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("请求失败, 状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("响应体读取失败:%v", err)
|
||||
}
|
||||
var twoFactorVerificationResp TwoFactorVerificationResp
|
||||
err = json.Unmarshal(respBody, &twoFactorVerificationResp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("二要素解析错误: %v", err)
|
||||
}
|
||||
|
||||
if !twoFactorVerificationResp.Success {
|
||||
return &VerificationResult{
|
||||
Passed: false,
|
||||
Err: &ValidationError{Message: "请输入有效的身份证号码"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if twoFactorVerificationResp.Code != 200 {
|
||||
return &VerificationResult{
|
||||
Passed: false,
|
||||
Err: &ValidationError{Message: twoFactorVerificationResp.Msg},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if twoFactorVerificationResp.Data.Result == 1 {
|
||||
return &VerificationResult{
|
||||
Passed: false,
|
||||
Err: &ValidationError{Message: "姓名与身份证不一致"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &VerificationResult{Passed: true, Err: nil}, nil
|
||||
}
|
||||
func (r *VerificationService) TwoFactorVerificationWest(ctx context.Context, request TwoFactorVerificationRequest) (*VerificationResult, error) {
|
||||
|
||||
params := map[string]interface{}{
|
||||
"name": request.Name,
|
||||
"id_card": request.IDCard,
|
||||
}
|
||||
marshal, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("二要素参数创建错误: %v", err)
|
||||
}
|
||||
resp, err := r.apiRequestService.ProcessLayoutIdcardRequest(ctx, marshal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
|
||||
respStr := string(resp.Data)
|
||||
if respStr != "0" {
|
||||
return &VerificationResult{
|
||||
Passed: false,
|
||||
Err: &ValidationError{Message: "姓名与身份证不一致"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &VerificationResult{Passed: true, Err: nil}, nil
|
||||
}
|
||||
func (r *VerificationService) ThreeFactorVerification(request ThreeFactorVerificationRequest) (*VerificationResult, error) {
|
||||
westName, err := crypto.WestDexEncrypt(request.Name, r.c.WestConfig.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
westIDCard, err := crypto.WestDexEncrypt(request.IDCard, r.c.WestConfig.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
westPhone, err := crypto.WestDexEncrypt(request.Mobile, r.c.WestConfig.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
threeElementsReq := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": westName,
|
||||
"idNo": westIDCard,
|
||||
"phone": westPhone,
|
||||
},
|
||||
}
|
||||
resp, err := r.westDexService.CallAPI("G15BJ02", threeElementsReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataResult := gjson.GetBytes(resp, "data.code")
|
||||
if !dataResult.Exists() {
|
||||
return nil, fmt.Errorf("code 字段不存在")
|
||||
}
|
||||
code := dataResult.Int()
|
||||
switch code {
|
||||
case 1000:
|
||||
case 1002:
|
||||
return &VerificationResult{
|
||||
Passed: false,
|
||||
Err: &ValidationError{Message: "姓名、证件号、手机号信息不一致"},
|
||||
}, nil
|
||||
case 1003:
|
||||
return &VerificationResult{
|
||||
Passed: false,
|
||||
Err: &ValidationError{Message: "姓名、证件号、手机号信息不一致"},
|
||||
}, nil
|
||||
case 1004:
|
||||
return &VerificationResult{
|
||||
Passed: false,
|
||||
Err: &ValidationError{Message: "姓名不正确"},
|
||||
}, nil
|
||||
case 1005:
|
||||
return &VerificationResult{
|
||||
Passed: false,
|
||||
Err: &ValidationError{Message: "证件号码不正确"},
|
||||
}, nil
|
||||
default:
|
||||
dataResultMsg := gjson.GetBytes(resp, "data.msg")
|
||||
if !dataResultMsg.Exists() {
|
||||
return nil, fmt.Errorf("msg字段不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("三要素核验错误状态响应: %s", dataResultMsg.String())
|
||||
}
|
||||
|
||||
return &VerificationResult{Passed: true, Err: nil}, nil
|
||||
}
|
||||
260
app/main/api/internal/service/wechatpayService.go
Normal file
260
app/main/api/internal/service/wechatpayService.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/app/main/model"
|
||||
"tyc-server/common/ctxdata"
|
||||
"tyc-server/pkg/lzkit/lzUtils"
|
||||
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/app"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/utils"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
const (
|
||||
TradeStateSuccess = "SUCCESS" // 支付成功
|
||||
TradeStateRefund = "REFUND" // 转入退款
|
||||
TradeStateNotPay = "NOTPAY" // 未支付
|
||||
TradeStateClosed = "CLOSED" // 已关闭
|
||||
TradeStateRevoked = "REVOKED" // 已撤销(付款码支付)
|
||||
TradeStateUserPaying = "USERPAYING" // 用户支付中(付款码支付)
|
||||
TradeStatePayError = "PAYERROR" // 支付失败(其他原因,如银行返回失败)
|
||||
)
|
||||
|
||||
type WechatPayService struct {
|
||||
config config.WxpayConfig
|
||||
wechatClient *core.Client
|
||||
notifyHandler *notify.Handler
|
||||
userAuthModel model.UserAuthModel
|
||||
}
|
||||
|
||||
// NewWechatPayService 初始化微信支付服务
|
||||
func NewWechatPayService(c config.Config, userAuthModel model.UserAuthModel) *WechatPayService {
|
||||
// 从配置中加载商户信息
|
||||
mchID := c.Wxpay.MchID
|
||||
mchCertificateSerialNumber := c.Wxpay.MchCertificateSerialNumber
|
||||
mchAPIv3Key := c.Wxpay.MchApiv3Key
|
||||
|
||||
// 从文件中加载商户私钥
|
||||
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(c.Wxpay.MchPrivateKeyPath)
|
||||
if err != nil {
|
||||
logx.Errorf("加载商户私钥失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
|
||||
}
|
||||
|
||||
// 使用商户私钥和其他参数初始化微信支付客户端
|
||||
opts := []core.ClientOption{
|
||||
option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key),
|
||||
}
|
||||
client, err := core.NewClient(context.Background(), opts...)
|
||||
if err != nil {
|
||||
logx.Errorf("创建微信支付客户端失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
|
||||
}
|
||||
// 在初始化时获取证书访问器并创建 notifyHandler
|
||||
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
||||
notifyHandler, err := notify.NewRSANotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
|
||||
if err != nil {
|
||||
logx.Errorf("获取证书访问器失败: %v", err)
|
||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err)) // 记录错误并停止程序
|
||||
}
|
||||
return &WechatPayService{
|
||||
config: c.Wxpay,
|
||||
wechatClient: client,
|
||||
notifyHandler: notifyHandler,
|
||||
userAuthModel: userAuthModel,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWechatAppOrder 创建微信APP支付订单
|
||||
func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount float64, description string, outTradeNo string) (string, error) {
|
||||
totalAmount := lzUtils.ToWechatAmount(amount)
|
||||
|
||||
// 构建支付请求参数
|
||||
payRequest := app.PrepayRequest{
|
||||
Appid: core.String(w.config.AppID),
|
||||
Mchid: core.String(w.config.MchID),
|
||||
Description: core.String(description),
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
NotifyUrl: core.String(w.config.NotifyUrl),
|
||||
Amount: &app.Amount{
|
||||
Total: core.Int64(totalAmount),
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化 AppApiService
|
||||
svc := app.AppApiService{Client: w.wechatClient}
|
||||
|
||||
// 发起预支付请求
|
||||
resp, result, err := svc.Prepay(ctx, payRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
|
||||
// 返回预支付交易会话标识
|
||||
return *resp.PrepayId, nil
|
||||
}
|
||||
|
||||
// CreateWechatMiniProgramOrder 创建微信小程序支付订单
|
||||
func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amount float64, description string, outTradeNo string, openid string) (interface{}, error) {
|
||||
totalAmount := lzUtils.ToWechatAmount(amount)
|
||||
|
||||
// 构建支付请求参数
|
||||
payRequest := jsapi.PrepayRequest{
|
||||
Appid: core.String(w.config.AppID),
|
||||
Mchid: core.String(w.config.MchID),
|
||||
Description: core.String(description),
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
NotifyUrl: core.String(w.config.NotifyUrl),
|
||||
Amount: &jsapi.Amount{
|
||||
Total: core.Int64(totalAmount),
|
||||
},
|
||||
Payer: &jsapi.Payer{
|
||||
Openid: core.String(openid), // 用户的 OpenID,通过前端传入
|
||||
}}
|
||||
|
||||
// 初始化 AppApiService
|
||||
svc := jsapi.JsapiApiService{Client: w.wechatClient}
|
||||
|
||||
// 发起预支付请求
|
||||
resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
|
||||
// 返回预支付交易会话标识
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序)
|
||||
func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) {
|
||||
// 根据 ctx 中的 platform 判断平台
|
||||
platform := ctx.Value("platform").(string)
|
||||
|
||||
var prepayData interface{}
|
||||
var err error
|
||||
|
||||
switch platform {
|
||||
case "mp-weixin":
|
||||
userID, getUidErr := ctxdata.GetUidFromCtx(ctx)
|
||||
if getUidErr != nil {
|
||||
return "", fmt.Errorf("获取用户信息失败: %s", getUidErr)
|
||||
}
|
||||
userAuthModel, findUserAuthErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, "wx_mini")
|
||||
if findUserAuthErr != nil {
|
||||
return "", fmt.Errorf("获取用户认证信息失败: %s", findUserAuthErr)
|
||||
}
|
||||
// 如果是小程序平台,调用小程序支付订单创建
|
||||
prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey)
|
||||
case "app":
|
||||
// 如果是 APP 平台,调用 APP 支付订单创建
|
||||
prepayData, err = w.CreateWechatAppOrder(ctx, amount, description, outTradeNo)
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的支付平台: %s", platform)
|
||||
}
|
||||
|
||||
// 如果创建支付订单失败,返回错误
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("支付订单创建失败: %v", err)
|
||||
}
|
||||
|
||||
// 返回预支付ID
|
||||
return prepayData, nil
|
||||
}
|
||||
|
||||
// HandleWechatPayNotification 处理微信支付回调
|
||||
func (w *WechatPayService) HandleWechatPayNotification(ctx context.Context, req *http.Request) (*payments.Transaction, error) {
|
||||
transaction := new(payments.Transaction)
|
||||
_, err := w.notifyHandler.ParseNotifyRequest(ctx, req, transaction)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("微信支付通知处理失败: %v", err)
|
||||
}
|
||||
// 返回交易信息
|
||||
return transaction, nil
|
||||
}
|
||||
|
||||
// HandleRefundNotification 处理微信退款回调
|
||||
func (w *WechatPayService) HandleRefundNotification(ctx context.Context, req *http.Request) (*refunddomestic.Refund, error) {
|
||||
refund := new(refunddomestic.Refund)
|
||||
_, err := w.notifyHandler.ParseNotifyRequest(ctx, req, refund)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("微信退款回调通知处理失败: %v", err)
|
||||
}
|
||||
return refund, nil
|
||||
}
|
||||
|
||||
// QueryOrderStatus 主动查询订单状态
|
||||
func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID string) (*payments.Transaction, error) {
|
||||
svc := jsapi.JsapiApiService{Client: w.wechatClient}
|
||||
|
||||
// 调用 QueryOrderById 方法查询订单状态
|
||||
resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{
|
||||
TransactionId: core.String(transactionID),
|
||||
Mchid: core.String(w.config.MchID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||
}
|
||||
return resp, nil
|
||||
|
||||
}
|
||||
|
||||
// WeChatRefund 申请微信退款
|
||||
func (w *WechatPayService) WeChatRefund(ctx context.Context, outTradeNo string, refundAmount float64, totalAmount float64) error {
|
||||
|
||||
// 生成唯一的退款单号
|
||||
outRefundNo := fmt.Sprintf("%s-refund", outTradeNo)
|
||||
|
||||
// 初始化退款服务
|
||||
svc := refunddomestic.RefundsApiService{Client: w.wechatClient}
|
||||
|
||||
// 创建退款请求
|
||||
resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{
|
||||
OutTradeNo: core.String(outTradeNo),
|
||||
OutRefundNo: core.String(outRefundNo),
|
||||
NotifyUrl: core.String(w.config.RefundNotifyUrl),
|
||||
Amount: &refunddomestic.AmountReq{
|
||||
Currency: core.String("CNY"),
|
||||
Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)),
|
||||
Total: core.Int64(lzUtils.ToWechatAmount(totalAmount)),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("微信订单申请退款错误: %v", err)
|
||||
}
|
||||
// 打印退款结果
|
||||
logx.Infof("退款申请成功,状态码=%d,退款单号=%s,微信退款单号=%s", result.Response.StatusCode, *resp.OutRefundNo, *resp.RefundId)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateOutTradeNo 生成唯一订单号
|
||||
func (w *WechatPayService) GenerateOutTradeNo() string {
|
||||
length := 16
|
||||
timestamp := time.Now().UnixNano()
|
||||
timeStr := strconv.FormatInt(timestamp, 10)
|
||||
randomPart := strconv.Itoa(int(timestamp % 1e6))
|
||||
combined := timeStr + randomPart
|
||||
|
||||
if len(combined) >= length {
|
||||
return combined[:length]
|
||||
}
|
||||
|
||||
for len(combined) < length {
|
||||
combined += strconv.Itoa(int(timestamp % 10))
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
199
app/main/api/internal/service/westdexService.go
Normal file
199
app/main/api/internal/service/westdexService.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type WestResp struct {
|
||||
Message string `json:"message"`
|
||||
Code string `json:"code"`
|
||||
Data string `json:"data"`
|
||||
ID string `json:"id"`
|
||||
ErrorCode *int `json:"error_code"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
type G05HZ01WestResp struct {
|
||||
Message string `json:"message"`
|
||||
Code string `json:"code"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
ID string `json:"id"`
|
||||
ErrorCode *int `json:"error_code"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
type WestDexService struct {
|
||||
config config.WestConfig
|
||||
}
|
||||
|
||||
// NewWestDexService 是一个构造函数,用于初始化 WestDexService
|
||||
func NewWestDexService(c config.Config) *WestDexService {
|
||||
return &WestDexService{
|
||||
config: c.WestConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// CallAPI 调用西部数据的 API
|
||||
func (w *WestDexService) CallAPI(code string, reqData map[string]interface{}) (resp []byte, err error) {
|
||||
// 生成当前的13位时间戳
|
||||
timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
|
||||
// 构造请求URL
|
||||
reqUrl := fmt.Sprintf("%s/%s/%s?timestamp=%s", w.config.Url, w.config.SecretId, code, timestamp)
|
||||
|
||||
jsonData, marshalErr := json.Marshal(reqData)
|
||||
if marshalErr != nil {
|
||||
return nil, marshalErr
|
||||
}
|
||||
|
||||
// 创建HTTP POST请求
|
||||
req, newRequestErr := http.NewRequest("POST", reqUrl, bytes.NewBuffer(jsonData))
|
||||
if newRequestErr != nil {
|
||||
return nil, newRequestErr
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{}
|
||||
httpResp, clientDoErr := client.Do(req)
|
||||
if clientDoErr != nil {
|
||||
return nil, clientDoErr
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
closeErr := Body.Close()
|
||||
if closeErr != nil {
|
||||
|
||||
}
|
||||
}(httpResp.Body)
|
||||
|
||||
// 检查请求是否成功
|
||||
if httpResp.StatusCode == 200 {
|
||||
// 读取响应体
|
||||
bodyBytes, ReadErr := io.ReadAll(httpResp.Body)
|
||||
if ReadErr != nil {
|
||||
return nil, ReadErr
|
||||
}
|
||||
|
||||
// 手动调用 json.Unmarshal 触发自定义的 UnmarshalJSON 方法
|
||||
var westDexResp WestResp
|
||||
UnmarshalErr := json.Unmarshal(bodyBytes, &westDexResp)
|
||||
if UnmarshalErr != nil {
|
||||
return nil, UnmarshalErr
|
||||
}
|
||||
if westDexResp.Code != "00000" && westDexResp.Code != "0" {
|
||||
if westDexResp.Data == "" {
|
||||
return nil, errors.New(westDexResp.Message)
|
||||
}
|
||||
decryptedData, DecryptErr := crypto.WestDexDecrypt(westDexResp.Data, w.config.Key)
|
||||
if DecryptErr != nil {
|
||||
return nil, DecryptErr
|
||||
}
|
||||
return decryptedData, errors.New(westDexResp.Message)
|
||||
}
|
||||
if westDexResp.Data == "" {
|
||||
return nil, errors.New(westDexResp.Message)
|
||||
}
|
||||
// 解密响应数据
|
||||
decryptedData, DecryptErr := crypto.WestDexDecrypt(westDexResp.Data, w.config.Key)
|
||||
if DecryptErr != nil {
|
||||
return nil, DecryptErr
|
||||
}
|
||||
// 输出解密后的数据
|
||||
log.Println(string(decryptedData))
|
||||
|
||||
return decryptedData, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("西部请求失败Code: %d", httpResp.StatusCode)
|
||||
}
|
||||
|
||||
// CallAPI 调用西部数据的 API
|
||||
func (w *WestDexService) G05HZ01CallAPI(code string, reqData map[string]interface{}) (resp []byte, err error) {
|
||||
// 生成当前的13位时间戳
|
||||
timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
|
||||
// 构造请求URL
|
||||
reqUrl := fmt.Sprintf("%s/%s/%s?timestamp=%s", w.config.Url, w.config.SecretSecondId, code, timestamp)
|
||||
|
||||
jsonData, marshalErr := json.Marshal(reqData)
|
||||
if marshalErr != nil {
|
||||
return nil, marshalErr
|
||||
}
|
||||
|
||||
// 创建HTTP POST请求
|
||||
req, newRequestErr := http.NewRequest("POST", reqUrl, bytes.NewBuffer(jsonData))
|
||||
if newRequestErr != nil {
|
||||
return nil, newRequestErr
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{}
|
||||
httpResp, clientDoErr := client.Do(req)
|
||||
if clientDoErr != nil {
|
||||
return nil, clientDoErr
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
closeErr := Body.Close()
|
||||
if closeErr != nil {
|
||||
|
||||
}
|
||||
}(httpResp.Body)
|
||||
|
||||
// 检查请求是否成功
|
||||
if httpResp.StatusCode == 200 {
|
||||
// 读取响应体
|
||||
bodyBytes, ReadErr := io.ReadAll(httpResp.Body)
|
||||
if ReadErr != nil {
|
||||
return nil, ReadErr
|
||||
}
|
||||
|
||||
// 手动调用 json.Unmarshal 触发自定义的 UnmarshalJSON 方法
|
||||
var westDexResp G05HZ01WestResp
|
||||
UnmarshalErr := json.Unmarshal(bodyBytes, &westDexResp)
|
||||
if UnmarshalErr != nil {
|
||||
return nil, UnmarshalErr
|
||||
}
|
||||
if westDexResp.Code != "0000" {
|
||||
if westDexResp.Data == nil {
|
||||
return nil, errors.New(westDexResp.Message)
|
||||
} else {
|
||||
return westDexResp.Data, errors.New(string(westDexResp.Data))
|
||||
}
|
||||
}
|
||||
if westDexResp.Data == nil {
|
||||
return nil, errors.New(westDexResp.Message)
|
||||
}
|
||||
return westDexResp.Data, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("西部请求失败Code: %d", httpResp.StatusCode)
|
||||
}
|
||||
|
||||
func (w *WestDexService) Encrypt(data string) string {
|
||||
encryptedValue, err := crypto.WestDexEncrypt(data, w.config.Key)
|
||||
if err != nil {
|
||||
panic("WestDexEncrypt error: " + err.Error())
|
||||
}
|
||||
return encryptedValue
|
||||
}
|
||||
|
||||
// GetDateRange 返回今天到明天的日期范围,格式为 "yyyyMMdd-yyyyMMdd"
|
||||
func (w *WestDexService) GetDateRange() string {
|
||||
today := time.Now().Format("20060102") // 获取今天的日期
|
||||
tomorrow := time.Now().Add(24 * time.Hour).Format("20060102") // 获取明天的日期
|
||||
return fmt.Sprintf("%s-%s", today, tomorrow) // 拼接日期范围并返回
|
||||
}
|
||||
193
app/main/api/internal/service/yushanService.go
Normal file
193
app/main/api/internal/service/yushanService.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type YushanService struct {
|
||||
config config.YushanConfig
|
||||
}
|
||||
|
||||
func NewYushanService(c config.Config) *YushanService {
|
||||
return &YushanService{
|
||||
config: c.YushanConfig,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrEmptyResult 表示查询结果为空
|
||||
ErrEmptyResult = errors.New("查询结果为空")
|
||||
)
|
||||
|
||||
func (y *YushanService) request(prodID string, params map[string]interface{}) ([]byte, error) {
|
||||
// 获取当前时间戳
|
||||
unixMilliseconds := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
// 生成请求序列号
|
||||
requestSN, _ := y.GenerateRandomString()
|
||||
|
||||
// 构建请求数据
|
||||
reqData := map[string]interface{}{
|
||||
"prod_id": prodID,
|
||||
"req_time": unixMilliseconds,
|
||||
"request_sn": requestSN,
|
||||
"req_data": params,
|
||||
}
|
||||
|
||||
// 将请求数据转换为 JSON 字节数组
|
||||
messageBytes, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取 API 密钥
|
||||
key, err := hex.DecodeString(y.config.ApiKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 使用 AES CBC 加密请求数据
|
||||
cipherText := y.AES_CBC_Encrypt(messageBytes, key)
|
||||
|
||||
// 将加密后的数据编码为 Base64 字符串
|
||||
content := base64.StdEncoding.EncodeToString(cipherText)
|
||||
|
||||
// 发起 HTTP 请求
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", y.config.Url, strings.NewReader(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("ACCT_ID", y.config.AcctID)
|
||||
|
||||
// 执行请求
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respData []byte
|
||||
|
||||
if IsJSON(string(body)) {
|
||||
respData = body
|
||||
} else {
|
||||
sDec, err := base64.StdEncoding.DecodeString(string(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
respData = y.AES_CBC_Decrypt(sDec, key)
|
||||
}
|
||||
retCode := gjson.GetBytes(respData, "retcode").String()
|
||||
|
||||
if retCode == "100000" {
|
||||
// retcode 为 100000,表示查询为空
|
||||
return json.RawMessage("{}"), ErrEmptyResult
|
||||
} else if retCode == "000000" || retCode == "000001" || retCode == "000002" || retCode == "000003" {
|
||||
// retcode 为 000000,表示有数据,返回 retdata
|
||||
retData := gjson.GetBytes(respData, "retdata")
|
||||
if !retData.Exists() {
|
||||
return respData, fmt.Errorf("羽山请求retdata为空: %s", string(respData))
|
||||
}
|
||||
return []byte(retData.Raw), nil
|
||||
} else {
|
||||
return respData, fmt.Errorf("羽山请求未知的状态码: %s", string(respData))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 判断字符串是否为 JSON 格式
|
||||
func IsJSON(s string) bool {
|
||||
var js interface{}
|
||||
return json.Unmarshal([]byte(s), &js) == nil
|
||||
}
|
||||
|
||||
// GenerateRandomString 生成一个32位的随机字符串订单号
|
||||
func (y *YushanService) GenerateRandomString() (string, error) {
|
||||
// 创建一个16字节的数组
|
||||
bytes := make([]byte, 16)
|
||||
// 读取随机字节到数组中
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 将字节数组编码为16进制字符串
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
// AEC加密(CBC模式)
|
||||
func (y *YushanService) AES_CBC_Encrypt(plainText []byte, key []byte) []byte {
|
||||
//指定加密算法,返回一个AES算法的Block接口对象
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//进行填充
|
||||
plainText = Padding(plainText, block.BlockSize())
|
||||
//指定初始向量vi,长度和block的块尺寸一致
|
||||
iv := []byte("0000000000000000")
|
||||
//指定分组模式,返回一个BlockMode接口对象
|
||||
blockMode := cipher.NewCBCEncrypter(block, iv)
|
||||
//加密连续数据库
|
||||
cipherText := make([]byte, len(plainText))
|
||||
blockMode.CryptBlocks(cipherText, plainText)
|
||||
//返回base64密文
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// AEC解密(CBC模式)
|
||||
func (y *YushanService) AES_CBC_Decrypt(cipherText []byte, key []byte) []byte {
|
||||
//指定解密算法,返回一个AES算法的Block接口对象
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//指定初始化向量IV,和加密的一致
|
||||
iv := []byte("0000000000000000")
|
||||
//指定分组模式,返回一个BlockMode接口对象
|
||||
blockMode := cipher.NewCBCDecrypter(block, iv)
|
||||
//解密
|
||||
plainText := make([]byte, len(cipherText))
|
||||
blockMode.CryptBlocks(plainText, cipherText)
|
||||
//删除填充
|
||||
plainText = UnPadding(plainText)
|
||||
return plainText
|
||||
} // 对明文进行填充
|
||||
func Padding(plainText []byte, blockSize int) []byte {
|
||||
//计算要填充的长度
|
||||
n := blockSize - len(plainText)%blockSize
|
||||
//对原来的明文填充n个n
|
||||
temp := bytes.Repeat([]byte{byte(n)}, n)
|
||||
plainText = append(plainText, temp...)
|
||||
return plainText
|
||||
}
|
||||
|
||||
// 对密文删除填充
|
||||
func UnPadding(cipherText []byte) []byte {
|
||||
//取出密文最后一个字节end
|
||||
end := cipherText[len(cipherText)-1]
|
||||
//删除填充
|
||||
cipherText = cipherText[:len(cipherText)-int(end)]
|
||||
return cipherText
|
||||
}
|
||||
99
app/main/api/internal/svc/servicecontext.go
Normal file
99
app/main/api/internal/svc/servicecontext.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/app/main/api/internal/middleware"
|
||||
"tyc-server/app/main/api/internal/service"
|
||||
"tyc-server/app/main/model"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
Config config.Config
|
||||
Redis *redis.Redis
|
||||
SourceInterceptor rest.Middleware
|
||||
UserModel model.UserModel
|
||||
UserAuthModel model.UserAuthModel
|
||||
ProductModel model.ProductModel
|
||||
FeatureModel model.FeatureModel
|
||||
ProductFeatureModel model.ProductFeatureModel
|
||||
ProductRenderModel model.ProductRenderModel
|
||||
OrderModel model.OrderModel
|
||||
QueryModel model.QueryModel
|
||||
GlobalNotificationsModel model.GlobalNotificationsModel
|
||||
ExampleModel model.ExampleModel
|
||||
ExampleParamsModel model.ExampleParamsModel
|
||||
AlipayService *service.AliPayService
|
||||
WechatPayService *service.WechatPayService
|
||||
ApplePayService *service.ApplePayService
|
||||
WestDexService *service.WestDexService
|
||||
YushanService *service.YushanService
|
||||
TianjuService *service.TianjuService
|
||||
ApiRequestService *service.ApiRequestService
|
||||
AsynqServer *asynq.Server // 服务端
|
||||
AsynqService *service.AsynqService // 客户端
|
||||
VerificationService *service.VerificationService
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
db := sqlx.NewMysql(c.DataSource)
|
||||
redisConf := redis.RedisConf{
|
||||
Host: c.CacheRedis[0].Host,
|
||||
Pass: c.CacheRedis[0].Pass,
|
||||
Type: c.CacheRedis[0].Type,
|
||||
}
|
||||
asynqServer := asynq.NewServer(
|
||||
asynq.RedisClientOpt{Addr: c.CacheRedis[0].Host, Password: c.CacheRedis[0].Pass},
|
||||
asynq.Config{
|
||||
IsFailure: func(err error) bool {
|
||||
logx.Errorf("异步任务失败: %+v \n", err)
|
||||
return true
|
||||
},
|
||||
Concurrency: 10,
|
||||
},
|
||||
)
|
||||
westDexService := service.NewWestDexService(c)
|
||||
yushanService := service.NewYushanService(c)
|
||||
tianjuService := service.NewTianjuService(c)
|
||||
productFeatureModel := model.NewProductFeatureModel(db, c.CacheRedis)
|
||||
featureModel := model.NewFeatureModel(db, c.CacheRedis)
|
||||
userAuthModel := model.NewUserAuthModel(db, c.CacheRedis)
|
||||
apiRequestService := service.NewApiRequestService(c, westDexService, yushanService, tianjuService, featureModel, productFeatureModel)
|
||||
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Redis: redis.MustNewRedis(redisConf),
|
||||
SourceInterceptor: middleware.NewSourceInterceptorMiddleware().Handle,
|
||||
AlipayService: service.NewAliPayService(c),
|
||||
WechatPayService: service.NewWechatPayService(c, userAuthModel),
|
||||
ApplePayService: service.NewApplePayService(c),
|
||||
WestDexService: westDexService,
|
||||
YushanService: yushanService,
|
||||
TianjuService: tianjuService,
|
||||
VerificationService: service.NewVerificationService(c, westDexService, apiRequestService),
|
||||
AsynqServer: asynqServer,
|
||||
ApiRequestService: apiRequestService,
|
||||
AsynqService: service.NewAsynqService(c),
|
||||
UserModel: model.NewUserModel(db, c.CacheRedis),
|
||||
UserAuthModel: userAuthModel,
|
||||
ProductModel: model.NewProductModel(db, c.CacheRedis),
|
||||
ProductRenderModel: model.NewProductRenderModel(db, c.CacheRedis),
|
||||
OrderModel: model.NewOrderModel(db, c.CacheRedis),
|
||||
QueryModel: model.NewQueryModel(db, c.CacheRedis),
|
||||
ExampleModel: model.NewExampleModel(db, c.CacheRedis),
|
||||
ExampleParamsModel: model.NewExampleParamsModel(db, c.CacheRedis),
|
||||
GlobalNotificationsModel: model.NewGlobalNotificationsModel(db, c.CacheRedis),
|
||||
FeatureModel: featureModel,
|
||||
ProductFeatureModel: productFeatureModel,
|
||||
}
|
||||
}
|
||||
func (s *ServiceContext) Close() {
|
||||
if s.AsynqService != nil {
|
||||
s.AsynqService.Close()
|
||||
}
|
||||
}
|
||||
12
app/main/api/internal/types/cache.go
Normal file
12
app/main/api/internal/types/cache.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package types
|
||||
|
||||
type QueryCache struct {
|
||||
Name string `json:"name"`
|
||||
IDCard string `json:"id_card"`
|
||||
Mobile string `json:"mobile"`
|
||||
Product string `json:"product_id"`
|
||||
}
|
||||
type QueryCacheLoad struct {
|
||||
Product string `json:"product_en"`
|
||||
Params string `json:"params"`
|
||||
}
|
||||
12
app/main/api/internal/types/payload.go
Normal file
12
app/main/api/internal/types/payload.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package types
|
||||
|
||||
type MsgPaySuccessQueryPayload struct {
|
||||
OrderID int64 `json:"order_id"`
|
||||
}
|
||||
|
||||
type MsgCarMaintenanceQueryPayload struct {
|
||||
OrderID string `json:"order_id"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
DelayDuration int64 `json:"delay_duration"` // 延迟时间(纳秒)
|
||||
QueryID int64 `json:"query_id"` // 关联的query表记录ID
|
||||
}
|
||||
209
app/main/api/internal/types/query.go
Normal file
209
app/main/api/internal/types/query.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package types
|
||||
|
||||
type MarriageReq struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
type HomeServiceReq struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
// RiskAssessment 查询请求结构
|
||||
type RiskAssessmentReq struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
// CompanyInfo 查询请求结构
|
||||
type CompanyInfoReq struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
// RentalInfo 查询请求结构
|
||||
type RentalInfoReq struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
// PreLoanBackgroundCheck 查询请求结构
|
||||
type PreLoanBackgroundCheckReq struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
// BackgroundCheck 查询请求结构
|
||||
type BackgroundCheckReq struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
type EntLawsuitReq struct {
|
||||
EntName string `json:"ent_name" validate:"required,name"`
|
||||
EntCode string `json:"ent_code" validate:"required,USCI"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
type TocPhoneThreeElements struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
type TocPhoneTwoElements struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
type TocIDCardTwoElements struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
}
|
||||
type TocDualMarriage struct {
|
||||
NameMan string `json:"name_man" validate:"required,name"`
|
||||
IDCardMan string `json:"id_card_man" validate:"required,idCard"`
|
||||
NameWoman string `json:"name_woman" validate:"required,name"`
|
||||
IDCardWoman string `json:"id_card_woman" validate:"required,idCard"`
|
||||
}
|
||||
type TocPersonVehicleVerification struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
CarType string `json:"car_type" validate:"required"`
|
||||
CarLicense string `json:"car_license" validate:"required"`
|
||||
}
|
||||
type TocCarVin struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
}
|
||||
type TocCarVinDrivingPermit struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
CarDrivingPermit string `json:"car_driving_permit" validate:"required"`
|
||||
}
|
||||
type TocCarVinLicense struct {
|
||||
VinCode string `json:"vin_code" validate:"required"`
|
||||
CarLicense string `json:"car_license" validate:"required"`
|
||||
}
|
||||
|
||||
// 银行卡黑名单
|
||||
type TocBankCardBlacklist struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
BankCard string `json:"bank_card" validate:"required"`
|
||||
}
|
||||
|
||||
// 手机号码风险
|
||||
type TocPhoneNumberRisk struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
|
||||
// 手机二次卡
|
||||
type TocPhoneSecondaryCard struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
StartDate string `json:"start_date" validate:"required"`
|
||||
}
|
||||
|
||||
// 出境限制查询
|
||||
type TocExitRestriction struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
}
|
||||
|
||||
// 手机月消费档次查询
|
||||
type TocMonthlyMobileConsumptionLevel struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
|
||||
// 学历信息验证
|
||||
type TocEducationVerification struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
CertificateNumber string `json:"certificate_number" validate:"required"`
|
||||
}
|
||||
|
||||
// 反诈反赌风险核验
|
||||
type TocFraudGamblingCheck struct {
|
||||
Mobile string `json:"mobile,omitempty" validate:"omitempty,mobile"`
|
||||
BankCard string `json:"bank_card,omitempty" validate:"omitempty,bankCard"`
|
||||
IDCard string `json:"id_card,omitempty" validate:"omitempty,idCard"`
|
||||
}
|
||||
|
||||
// 手机号反赌反诈风险查询
|
||||
type TocMobileDrugFraudRiskCheck struct {
|
||||
Mobile string `json:"mobile,omitempty" validate:"omitempty,mobile"`
|
||||
BankCard string `json:"bank_card,omitempty" validate:"omitempty,bankCard"`
|
||||
IDCard string `json:"id_card,omitempty" validate:"omitempty,idCard"`
|
||||
}
|
||||
|
||||
// 手机号空号检测
|
||||
type TocMobileNumberValidation struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
|
||||
// 银行卡归属地查询
|
||||
type TocBankCardLocation struct {
|
||||
BankCard string `json:"bank_card" validate:"required"`
|
||||
}
|
||||
|
||||
// 银行卡姓名二要素验证
|
||||
type TocBankCardNameElementVerification struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
BankCard string `json:"bank_card" validate:"required"`
|
||||
}
|
||||
|
||||
// 银行卡号码二要素验证
|
||||
type TocBankCardIDElementVerification struct {
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
BankCard string `json:"bank_card" validate:"required"`
|
||||
}
|
||||
|
||||
// 银行卡三要素综合验证
|
||||
type TocBankCardThreeElementsVerification struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
BankCard string `json:"bank_card" validate:"required"`
|
||||
}
|
||||
|
||||
// 高风险特殊手机号
|
||||
type TocMobileRiskAssessment struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
|
||||
// 手机归属地
|
||||
type TocMobileLocation struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
|
||||
// 身份证归属地
|
||||
type TocIDCardLocation struct {
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
}
|
||||
|
||||
// 偿贷压力
|
||||
type TocDebtRepayStress struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
|
||||
// 学历信息查询
|
||||
type TocEducationInfo struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
}
|
||||
|
||||
// 人企关系加强版
|
||||
type TocPersonEnterprisePro struct {
|
||||
Name string `json:"name" validate:"required,name"`
|
||||
IDCard string `json:"id_card" validate:"required,idCard"`
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
}
|
||||
47
app/main/api/internal/types/queryMap.go
Normal file
47
app/main/api/internal/types/queryMap.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package types
|
||||
|
||||
// 特殊名单 G26BJ05
|
||||
var G26BJ05FieldMapping = map[string]string{
|
||||
"IDCard": "id",
|
||||
"Name": "name",
|
||||
"Mobile": "cell",
|
||||
"TimeRange": "time_range",
|
||||
}
|
||||
|
||||
// 个人不良
|
||||
var G34BJ03FieldMapping = map[string]string{
|
||||
"IDCard": "id_card",
|
||||
"Name": "name",
|
||||
}
|
||||
|
||||
// 个人涉诉 G35SC01
|
||||
var G35SC01FieldMapping = map[string]string{
|
||||
"Name": "name",
|
||||
"IDCard": "idcard",
|
||||
"InquiredAuth": "inquired_auth",
|
||||
}
|
||||
|
||||
// 单人婚姻 G09SC02
|
||||
var G09SC02FieldMapping = map[string]string{
|
||||
"IDCard": "certNumMan",
|
||||
"Name": "nameMan",
|
||||
}
|
||||
|
||||
// 借贷意向 G27BJ05
|
||||
var G27BJ05FieldMapping = map[string]string{
|
||||
"IDCard": "id",
|
||||
"Name": "name",
|
||||
"Mobile": "cell",
|
||||
}
|
||||
|
||||
// 借贷行为 G28BJ05
|
||||
var G28BJ05FieldMapping = map[string]string{
|
||||
"IDCard": "id",
|
||||
"Name": "name",
|
||||
"Mobile": "cell",
|
||||
}
|
||||
|
||||
// 股东人企关系精准版 G05HZ01
|
||||
var G05HZ01FieldMapping = map[string]string{
|
||||
"IDCard": "pid",
|
||||
}
|
||||
73
app/main/api/internal/types/queryParams.go
Normal file
73
app/main/api/internal/types/queryParams.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package types
|
||||
|
||||
type WestDexServiceRequestParams struct {
|
||||
FieldMapping map[string]string
|
||||
ApiID string
|
||||
}
|
||||
|
||||
var WestDexParams = map[string][]WestDexServiceRequestParams{
|
||||
"marriage": {
|
||||
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
|
||||
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
|
||||
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
|
||||
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
|
||||
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
|
||||
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
|
||||
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
|
||||
|
||||
},
|
||||
"backgroundcheck": {
|
||||
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
|
||||
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
|
||||
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
|
||||
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
|
||||
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
|
||||
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
|
||||
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
|
||||
},
|
||||
"companyinfo": {
|
||||
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
|
||||
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
|
||||
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
|
||||
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
|
||||
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
|
||||
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
|
||||
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
|
||||
},
|
||||
"homeservice": {
|
||||
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
|
||||
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
|
||||
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
|
||||
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
|
||||
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
|
||||
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
|
||||
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
|
||||
},
|
||||
"preloanbackgroundcheck": {
|
||||
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
|
||||
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
|
||||
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
|
||||
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
|
||||
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
|
||||
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
|
||||
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
|
||||
},
|
||||
"rentalinfo": {
|
||||
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
|
||||
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
|
||||
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
|
||||
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
|
||||
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
|
||||
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
|
||||
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
|
||||
},
|
||||
"riskassessment": {
|
||||
{FieldMapping: G09SC02FieldMapping, ApiID: "G09SC02"}, // 单人婚姻
|
||||
{FieldMapping: G27BJ05FieldMapping, ApiID: "G27BJ05"}, // 借贷意向
|
||||
{FieldMapping: G28BJ05FieldMapping, ApiID: "G28BJ05"}, // 借贷行为
|
||||
{FieldMapping: G26BJ05FieldMapping, ApiID: "G26BJ05"}, // 特殊名单
|
||||
{FieldMapping: G05HZ01FieldMapping, ApiID: "G05HZ01"}, // 股东人企关系
|
||||
{FieldMapping: G34BJ03FieldMapping, ApiID: "G34BJ03"}, // 个人不良
|
||||
{FieldMapping: G35SC01FieldMapping, ApiID: "G35SC01"}, // 个人涉诉
|
||||
},
|
||||
}
|
||||
5
app/main/api/internal/types/taskname.go
Normal file
5
app/main/api/internal/types/taskname.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package types
|
||||
|
||||
const MsgPaySuccessQuery = "msg:pay_success:query"
|
||||
const MsgCleanQueryData = "msg:clean_query_data"
|
||||
const MsgCarMaintenanceQuery = "msg:car:maintenance:query"
|
||||
249
app/main/api/internal/types/types.go
Normal file
249
app/main/api/internal/types/types.go
Normal file
@@ -0,0 +1,249 @@
|
||||
// Code generated by goctl. DO NOT EDIT.
|
||||
package types
|
||||
|
||||
type Feature struct {
|
||||
ID int64 `json:"id"` // 功能ID
|
||||
ApiID string `json:"api_id"` // API标识
|
||||
Name string `json:"name"` // 功能描述
|
||||
}
|
||||
|
||||
type GetNotificationsResp struct {
|
||||
Notifications []Notification `json:"notifications"` // 通知列表
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
}
|
||||
|
||||
type GetProductByEnRequest struct {
|
||||
ProductEn string `path:"product_en"`
|
||||
}
|
||||
|
||||
type GetProductByIDRequest struct {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
|
||||
type GetProductRenderListRequest struct {
|
||||
Module string `path:"module"`
|
||||
}
|
||||
|
||||
type GetProductRenderListResponse struct {
|
||||
Product []Product
|
||||
}
|
||||
|
||||
type IapCallbackReq struct {
|
||||
OrderID int64 `json:"order_id" validate:"required"`
|
||||
TransactionReceipt string `json:"transaction_receipt" validate:"required"`
|
||||
}
|
||||
|
||||
type MobileCodeLoginReq struct {
|
||||
Mobile string `json:"mobile"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
type MobileCodeLoginResp struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
|
||||
type MobileLoginReq struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
type MobileLoginResp struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
Title string `json:"title"` // 通知标题
|
||||
Content string `json:"content"` // 通知内容 (富文本)
|
||||
NotificationPage string `json:"notificationPage"` // 通知页面
|
||||
StartDate string `json:"startDate"` // 通知开始日期,格式 "YYYY-MM-DD"
|
||||
EndDate string `json:"endDate"` // 通知结束日期,格式 "YYYY-MM-DD"
|
||||
StartTime string `json:"startTime"` // 每天通知开始时间,格式 "HH:MM:SS"
|
||||
EndTime string `json:"endTime"` // 每天通知结束时间,格式 "HH:MM:SS"
|
||||
}
|
||||
|
||||
type PaymentReq struct {
|
||||
Id string `json:"id"`
|
||||
PayMethod string `json:"pay_method"`
|
||||
}
|
||||
|
||||
type PaymentResp struct {
|
||||
PrepayData interface{} `json:"prepay_data"`
|
||||
PrepayId string `json:"prepay_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
}
|
||||
|
||||
type Product struct {
|
||||
ProductName string `json:"product_name"`
|
||||
ProductEn string `json:"product_en"`
|
||||
Description string `json:"description"`
|
||||
Notes string `json:"notes,optional"`
|
||||
SellPrice float64 `json:"sell_price"`
|
||||
Features []Feature `json:"features"` // 关联功能列表
|
||||
}
|
||||
|
||||
type ProductResponse struct {
|
||||
Product
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
Id int64 `json:"id"` // 主键ID
|
||||
OrderId int64 `json:"order_id"` // 订单ID
|
||||
UserId int64 `json:"user_id"` // 用户ID
|
||||
ProductName string `json:"product_name"` // 产品ID
|
||||
QueryParams map[string]interface{} `json:"query_params"`
|
||||
QueryData []QueryItem `json:"query_data"`
|
||||
CreateTime string `json:"create_time"` // 创建时间
|
||||
UpdateTime string `json:"update_time"` // 更新时间
|
||||
QueryState string `json:"query_state"` // 查询状态
|
||||
}
|
||||
|
||||
type QueryDetailByOrderIdReq struct {
|
||||
OrderId int64 `path:"order_id"`
|
||||
}
|
||||
|
||||
type QueryDetailByOrderIdResp struct {
|
||||
Query
|
||||
}
|
||||
|
||||
type QueryDetailByOrderNoReq struct {
|
||||
OrderNo string `path:"order_no"`
|
||||
}
|
||||
|
||||
type QueryDetailByOrderNoResp struct {
|
||||
Query
|
||||
}
|
||||
|
||||
type QueryDetailReq struct {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
|
||||
type QueryDetailResp struct {
|
||||
Query
|
||||
}
|
||||
|
||||
type QueryExampleReq struct {
|
||||
Feature string `form:"feature"`
|
||||
}
|
||||
|
||||
type QueryExampleResp struct {
|
||||
Query
|
||||
}
|
||||
|
||||
type QueryItem struct {
|
||||
Feature interface{} `json:"feature"`
|
||||
Data interface{} `json:"data"` // 这里可以是 map 或 具体的 struct
|
||||
}
|
||||
|
||||
type QueryListReq struct {
|
||||
Page int64 `form:"page"` // 页码
|
||||
PageSize int64 `form:"page_size"` // 每页数据量
|
||||
}
|
||||
|
||||
type QueryListResp struct {
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
List []Query `json:"list"` // 查询列表
|
||||
}
|
||||
|
||||
type QueryProvisionalOrderReq struct {
|
||||
Id string `path:"id"`
|
||||
}
|
||||
|
||||
type QueryProvisionalOrderResp struct {
|
||||
QueryParams map[string]interface{} `json:"query_params"`
|
||||
Product Product `json:"product"`
|
||||
}
|
||||
|
||||
type QueryReq struct {
|
||||
Data string `json:"data" validate:"required"`
|
||||
}
|
||||
|
||||
type QueryResp struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
type QueryRetryReq struct {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
|
||||
type QueryRetryResp struct {
|
||||
}
|
||||
|
||||
type QueryServiceReq struct {
|
||||
Product string `path:"product"`
|
||||
Data string `json:"data" validate:"required"`
|
||||
}
|
||||
|
||||
type QueryServiceResp struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
type QuerySingleTestReq struct {
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Api string `json:"api"`
|
||||
}
|
||||
|
||||
type QuerySingleTestResp struct {
|
||||
Data interface{} `json:"data"`
|
||||
Api string `json:"api"`
|
||||
}
|
||||
|
||||
type RegisterReq struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
Password string `json:"password" validate:"required,min=11,max=11,password"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
type RegisterResp struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
|
||||
type UpdateQueryDataReq struct {
|
||||
Id int64 `json:"id"` // 查询ID
|
||||
QueryData string `json:"query_data"` // 查询数据(未加密的JSON)
|
||||
}
|
||||
|
||||
type UpdateQueryDataResp struct {
|
||||
Id int64 `json:"id"`
|
||||
UpdatedAt string `json:"updated_at"` // 更新时间
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id int64 `json:"id"`
|
||||
Mobile string `json:"mobile"`
|
||||
NickName string `json:"nickName"`
|
||||
}
|
||||
|
||||
type UserInfoResp struct {
|
||||
UserInfo User `json:"userInfo"`
|
||||
}
|
||||
|
||||
type WXH5AuthReq struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type WXH5AuthResp struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
|
||||
type WXMiniAuthReq struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type WXMiniAuthResp struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
AccessExpire int64 `json:"accessExpire"`
|
||||
RefreshAfter int64 `json:"refreshAfter"`
|
||||
}
|
||||
|
||||
type SendSmsReq struct {
|
||||
Mobile string `json:"mobile" validate:"required,mobile"`
|
||||
ActionType string `json:"actionType" validate:"required,oneof=login register query"`
|
||||
}
|
||||
66
app/main/api/main.go
Normal file
66
app/main/api/main.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"tyc-server/app/main/api/internal/config"
|
||||
"tyc-server/app/main/api/internal/handler"
|
||||
"tyc-server/app/main/api/internal/queue"
|
||||
"tyc-server/app/main/api/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/conf"
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 读取环境变量 ENV,默认为 "prod"
|
||||
env := os.Getenv("ENV")
|
||||
if env == "" {
|
||||
env = "production"
|
||||
}
|
||||
|
||||
// 根据 ENV 加载不同的配置文件
|
||||
var defaultConfigFile string
|
||||
if env == "development" {
|
||||
defaultConfigFile = "app/main/api/etc/main.dev.yaml"
|
||||
} else {
|
||||
defaultConfigFile = "etc/main.yaml" // 生产环境配置
|
||||
}
|
||||
configFile := flag.String("f", defaultConfigFile, "the config file")
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
|
||||
svcContext := svc.NewServiceContext(c)
|
||||
defer svcContext.Close()
|
||||
|
||||
// 启动 asynq 消费者
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
// 初始化 cron job 或异步任务队列
|
||||
asynq := queue.NewCronJob(ctx, svcContext)
|
||||
mux := asynq.Register()
|
||||
|
||||
// 启动 asynq 消费者
|
||||
if err := svcContext.AsynqServer.Run(mux); err != nil {
|
||||
logx.WithContext(ctx).Errorf("异步任务启动失败: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("异步任务启动!!!")
|
||||
}()
|
||||
// go func() {
|
||||
// script.RunEncryptMobile()
|
||||
// }()
|
||||
server := rest.MustNewServer(c.RestConf)
|
||||
defer server.Stop()
|
||||
|
||||
handler.RegisterHandlers(server, svcContext)
|
||||
|
||||
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
|
||||
server.Start()
|
||||
}
|
||||
27
app/main/model/exampleModel.go
Normal file
27
app/main/model/exampleModel.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
var _ ExampleModel = (*customExampleModel)(nil)
|
||||
|
||||
type (
|
||||
// ExampleModel is an interface to be customized, add more methods here,
|
||||
// and implement the added methods in customExampleModel.
|
||||
ExampleModel interface {
|
||||
exampleModel
|
||||
}
|
||||
|
||||
customExampleModel struct {
|
||||
*defaultExampleModel
|
||||
}
|
||||
)
|
||||
|
||||
// NewExampleModel returns a model for the database table.
|
||||
func NewExampleModel(conn sqlx.SqlConn, c cache.CacheConf) ExampleModel {
|
||||
return &customExampleModel{
|
||||
defaultExampleModel: newExampleModel(conn, c),
|
||||
}
|
||||
}
|
||||
434
app/main/model/exampleModel_gen.go
Normal file
434
app/main/model/exampleModel_gen.go
Normal file
@@ -0,0 +1,434 @@
|
||||
// Code generated by goctl. DO NOT EDIT!
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/builder"
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlc"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
"tyc-server/common/globalkey"
|
||||
)
|
||||
|
||||
var (
|
||||
exampleFieldNames = builder.RawFieldNames(&Example{})
|
||||
exampleRows = strings.Join(exampleFieldNames, ",")
|
||||
exampleRowsExpectAutoSet = strings.Join(stringx.Remove(exampleFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
|
||||
exampleRowsWithPlaceHolder = strings.Join(stringx.Remove(exampleFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"
|
||||
|
||||
cacheTycExampleIdPrefix = "cache:tyc:example:id:"
|
||||
cacheTycExampleApiIdPrefix = "cache:tyc:example:apiId:"
|
||||
cacheTycExampleFeatureIdPrefix = "cache:tyc:example:featureId:"
|
||||
)
|
||||
|
||||
type (
|
||||
exampleModel interface {
|
||||
Insert(ctx context.Context, session sqlx.Session, data *Example) (sql.Result, error)
|
||||
FindOne(ctx context.Context, id int64) (*Example, error)
|
||||
FindOneByApiId(ctx context.Context, apiId string) (*Example, error)
|
||||
FindOneByFeatureId(ctx context.Context, featureId int64) (*Example, error)
|
||||
Update(ctx context.Context, session sqlx.Session, data *Example) (sql.Result, error)
|
||||
UpdateWithVersion(ctx context.Context, session sqlx.Session, data *Example) error
|
||||
Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error
|
||||
SelectBuilder() squirrel.SelectBuilder
|
||||
DeleteSoft(ctx context.Context, session sqlx.Session, data *Example) error
|
||||
FindSum(ctx context.Context, sumBuilder squirrel.SelectBuilder, field string) (float64, error)
|
||||
FindCount(ctx context.Context, countBuilder squirrel.SelectBuilder, field string) (int64, error)
|
||||
FindAll(ctx context.Context, rowBuilder squirrel.SelectBuilder, orderBy string) ([]*Example, error)
|
||||
FindPageListByPage(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Example, error)
|
||||
FindPageListByPageWithTotal(ctx context.Context, rowBuilder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Example, int64, error)
|
||||
FindPageListByIdDESC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Example, error)
|
||||
FindPageListByIdASC(ctx context.Context, rowBuilder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Example, error)
|
||||
Delete(ctx context.Context, session sqlx.Session, id int64) error
|
||||
}
|
||||
|
||||
defaultExampleModel struct {
|
||||
sqlc.CachedConn
|
||||
table string
|
||||
}
|
||||
|
||||
Example struct {
|
||||
Id int64 `db:"id"` // 主键ID
|
||||
CreateTime time.Time `db:"create_time"` // 创建时间
|
||||
UpdateTime time.Time `db:"update_time"` // 更新时间
|
||||
DeleteTime sql.NullTime `db:"delete_time"` // 删除时间
|
||||
DelState int64 `db:"del_state"` // 删除状态
|
||||
Version int64 `db:"version"` // 版本号
|
||||
ApiId string `db:"api_id"` // API标识
|
||||
FeatureId int64 `db:"feature_id"` // 关联feature表的ID
|
||||
Content string `db:"content"` // 内容
|
||||
}
|
||||
)
|
||||
|
||||
func newExampleModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultExampleModel {
|
||||
return &defaultExampleModel{
|
||||
CachedConn: sqlc.NewConn(conn, c),
|
||||
table: "`example`",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) Insert(ctx context.Context, session sqlx.Session, data *Example) (sql.Result, error) {
|
||||
data.DelState = globalkey.DelStateNo
|
||||
tycExampleApiIdKey := fmt.Sprintf("%s%v", cacheTycExampleApiIdPrefix, data.ApiId)
|
||||
tycExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheTycExampleFeatureIdPrefix, data.FeatureId)
|
||||
tycExampleIdKey := fmt.Sprintf("%s%v", cacheTycExampleIdPrefix, data.Id)
|
||||
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?)", m.table, exampleRowsExpectAutoSet)
|
||||
if session != nil {
|
||||
return session.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.FeatureId, data.Content)
|
||||
}
|
||||
return conn.ExecCtx(ctx, query, data.DeleteTime, data.DelState, data.Version, data.ApiId, data.FeatureId, data.Content)
|
||||
}, tycExampleApiIdKey, tycExampleFeatureIdKey, tycExampleIdKey)
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindOne(ctx context.Context, id int64) (*Example, error) {
|
||||
tycExampleIdKey := fmt.Sprintf("%s%v", cacheTycExampleIdPrefix, id)
|
||||
var resp Example
|
||||
err := m.QueryRowCtx(ctx, &resp, tycExampleIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
|
||||
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", exampleRows, m.table)
|
||||
return conn.QueryRowCtx(ctx, v, query, id, globalkey.DelStateNo)
|
||||
})
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindOneByApiId(ctx context.Context, apiId string) (*Example, error) {
|
||||
tycExampleApiIdKey := fmt.Sprintf("%s%v", cacheTycExampleApiIdPrefix, apiId)
|
||||
var resp Example
|
||||
err := m.QueryRowIndexCtx(ctx, &resp, tycExampleApiIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := fmt.Sprintf("select %s from %s where `api_id` = ? and del_state = ? limit 1", exampleRows, m.table)
|
||||
if err := conn.QueryRowCtx(ctx, &resp, query, apiId, globalkey.DelStateNo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindOneByFeatureId(ctx context.Context, featureId int64) (*Example, error) {
|
||||
tycExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheTycExampleFeatureIdPrefix, featureId)
|
||||
var resp Example
|
||||
err := m.QueryRowIndexCtx(ctx, &resp, tycExampleFeatureIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
|
||||
query := fmt.Sprintf("select %s from %s where `feature_id` = ? and del_state = ? limit 1", exampleRows, m.table)
|
||||
if err := conn.QueryRowCtx(ctx, &resp, query, featureId, globalkey.DelStateNo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, m.queryPrimary)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
case sqlc.ErrNotFound:
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) Update(ctx context.Context, session sqlx.Session, newData *Example) (sql.Result, error) {
|
||||
data, err := m.FindOne(ctx, newData.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tycExampleApiIdKey := fmt.Sprintf("%s%v", cacheTycExampleApiIdPrefix, data.ApiId)
|
||||
tycExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheTycExampleFeatureIdPrefix, data.FeatureId)
|
||||
tycExampleIdKey := fmt.Sprintf("%s%v", cacheTycExampleIdPrefix, data.Id)
|
||||
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, exampleRowsWithPlaceHolder)
|
||||
if session != nil {
|
||||
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.FeatureId, newData.Content, newData.Id)
|
||||
}
|
||||
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.FeatureId, newData.Content, newData.Id)
|
||||
}, tycExampleApiIdKey, tycExampleFeatureIdKey, tycExampleIdKey)
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *Example) error {
|
||||
|
||||
oldVersion := newData.Version
|
||||
newData.Version += 1
|
||||
|
||||
var sqlResult sql.Result
|
||||
var err error
|
||||
|
||||
data, err := m.FindOne(ctx, newData.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tycExampleApiIdKey := fmt.Sprintf("%s%v", cacheTycExampleApiIdPrefix, data.ApiId)
|
||||
tycExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheTycExampleFeatureIdPrefix, data.FeatureId)
|
||||
tycExampleIdKey := fmt.Sprintf("%s%v", cacheTycExampleIdPrefix, data.Id)
|
||||
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, exampleRowsWithPlaceHolder)
|
||||
if session != nil {
|
||||
return session.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.FeatureId, newData.Content, newData.Id, oldVersion)
|
||||
}
|
||||
return conn.ExecCtx(ctx, query, newData.DeleteTime, newData.DelState, newData.Version, newData.ApiId, newData.FeatureId, newData.Content, newData.Id, oldVersion)
|
||||
}, tycExampleApiIdKey, tycExampleFeatureIdKey, tycExampleIdKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateCount, err := sqlResult.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if updateCount == 0 {
|
||||
return ErrNoRowsUpdate
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) DeleteSoft(ctx context.Context, session sqlx.Session, data *Example) error {
|
||||
data.DelState = globalkey.DelStateYes
|
||||
data.DeleteTime = sql.NullTime{Time: time.Now(), Valid: true}
|
||||
if err := m.UpdateWithVersion(ctx, session, data); err != nil {
|
||||
return errors.Wrapf(errors.New("delete soft failed "), "ExampleModel delete err : %+v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindSum(ctx context.Context, builder squirrel.SelectBuilder, field string) (float64, error) {
|
||||
|
||||
if len(field) == 0 {
|
||||
return 0, errors.Wrapf(errors.New("FindSum Least One Field"), "FindSum Least One Field")
|
||||
}
|
||||
|
||||
builder = builder.Columns("IFNULL(SUM(" + field + "),0)")
|
||||
|
||||
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var resp float64
|
||||
err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...)
|
||||
switch err {
|
||||
case nil:
|
||||
return resp, nil
|
||||
default:
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindCount(ctx context.Context, builder squirrel.SelectBuilder, field string) (int64, error) {
|
||||
|
||||
if len(field) == 0 {
|
||||
return 0, errors.Wrapf(errors.New("FindCount Least One Field"), "FindCount Least One Field")
|
||||
}
|
||||
|
||||
builder = builder.Columns("COUNT(" + field + ")")
|
||||
|
||||
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var resp int64
|
||||
err = m.QueryRowNoCacheCtx(ctx, &resp, query, values...)
|
||||
switch err {
|
||||
case nil:
|
||||
return resp, nil
|
||||
default:
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder, orderBy string) ([]*Example, error) {
|
||||
|
||||
builder = builder.Columns(exampleRows)
|
||||
|
||||
if orderBy == "" {
|
||||
builder = builder.OrderBy("id DESC")
|
||||
} else {
|
||||
builder = builder.OrderBy(orderBy)
|
||||
}
|
||||
|
||||
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).ToSql()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []*Example
|
||||
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||
switch err {
|
||||
case nil:
|
||||
return resp, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindPageListByPage(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Example, error) {
|
||||
|
||||
builder = builder.Columns(exampleRows)
|
||||
|
||||
if orderBy == "" {
|
||||
builder = builder.OrderBy("id DESC")
|
||||
} else {
|
||||
builder = builder.OrderBy(orderBy)
|
||||
}
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []*Example
|
||||
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||
switch err {
|
||||
case nil:
|
||||
return resp, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindPageListByPageWithTotal(ctx context.Context, builder squirrel.SelectBuilder, page, pageSize int64, orderBy string) ([]*Example, int64, error) {
|
||||
|
||||
total, err := m.FindCount(ctx, builder, "id")
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
builder = builder.Columns(exampleRows)
|
||||
|
||||
if orderBy == "" {
|
||||
builder = builder.OrderBy("id DESC")
|
||||
} else {
|
||||
builder = builder.OrderBy(orderBy)
|
||||
}
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).Offset(uint64(offset)).Limit(uint64(pageSize)).ToSql()
|
||||
if err != nil {
|
||||
return nil, total, err
|
||||
}
|
||||
|
||||
var resp []*Example
|
||||
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||
switch err {
|
||||
case nil:
|
||||
return resp, total, nil
|
||||
default:
|
||||
return nil, total, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindPageListByIdDESC(ctx context.Context, builder squirrel.SelectBuilder, preMinId, pageSize int64) ([]*Example, error) {
|
||||
|
||||
builder = builder.Columns(exampleRows)
|
||||
|
||||
if preMinId > 0 {
|
||||
builder = builder.Where(" id < ? ", preMinId)
|
||||
}
|
||||
|
||||
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id DESC").Limit(uint64(pageSize)).ToSql()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []*Example
|
||||
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||
switch err {
|
||||
case nil:
|
||||
return resp, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) FindPageListByIdASC(ctx context.Context, builder squirrel.SelectBuilder, preMaxId, pageSize int64) ([]*Example, error) {
|
||||
|
||||
builder = builder.Columns(exampleRows)
|
||||
|
||||
if preMaxId > 0 {
|
||||
builder = builder.Where(" id > ? ", preMaxId)
|
||||
}
|
||||
|
||||
query, values, err := builder.Where("del_state = ?", globalkey.DelStateNo).OrderBy("id ASC").Limit(uint64(pageSize)).ToSql()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []*Example
|
||||
err = m.QueryRowsNoCacheCtx(ctx, &resp, query, values...)
|
||||
switch err {
|
||||
case nil:
|
||||
return resp, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) Trans(ctx context.Context, fn func(ctx context.Context, session sqlx.Session) error) error {
|
||||
|
||||
return m.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
return fn(ctx, session)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) SelectBuilder() squirrel.SelectBuilder {
|
||||
return squirrel.Select().From(m.table)
|
||||
}
|
||||
func (m *defaultExampleModel) Delete(ctx context.Context, session sqlx.Session, id int64) error {
|
||||
data, err := m.FindOne(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tycExampleApiIdKey := fmt.Sprintf("%s%v", cacheTycExampleApiIdPrefix, data.ApiId)
|
||||
tycExampleFeatureIdKey := fmt.Sprintf("%s%v", cacheTycExampleFeatureIdPrefix, data.FeatureId)
|
||||
tycExampleIdKey := fmt.Sprintf("%s%v", cacheTycExampleIdPrefix, id)
|
||||
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
|
||||
if session != nil {
|
||||
return session.ExecCtx(ctx, query, id)
|
||||
}
|
||||
return conn.ExecCtx(ctx, query, id)
|
||||
}, tycExampleApiIdKey, tycExampleFeatureIdKey, tycExampleIdKey)
|
||||
return err
|
||||
}
|
||||
func (m *defaultExampleModel) formatPrimary(primary interface{}) string {
|
||||
return fmt.Sprintf("%s%v", cacheTycExampleIdPrefix, primary)
|
||||
}
|
||||
func (m *defaultExampleModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
|
||||
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", exampleRows, m.table)
|
||||
return conn.QueryRowCtx(ctx, v, query, primary, globalkey.DelStateNo)
|
||||
}
|
||||
|
||||
func (m *defaultExampleModel) tableName() string {
|
||||
return m.table
|
||||
}
|
||||
27
app/main/model/exampleParamsModel.go
Normal file
27
app/main/model/exampleParamsModel.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
var _ ExampleParamsModel = (*customExampleParamsModel)(nil)
|
||||
|
||||
type (
|
||||
// ExampleParamsModel is an interface to be customized, add more methods here,
|
||||
// and implement the added methods in customExampleParamsModel.
|
||||
ExampleParamsModel interface {
|
||||
exampleParamsModel
|
||||
}
|
||||
|
||||
customExampleParamsModel struct {
|
||||
*defaultExampleParamsModel
|
||||
}
|
||||
)
|
||||
|
||||
// NewExampleParamsModel returns a model for the database table.
|
||||
func NewExampleParamsModel(conn sqlx.SqlConn, c cache.CacheConf) ExampleParamsModel {
|
||||
return &customExampleParamsModel{
|
||||
defaultExampleParamsModel: newExampleParamsModel(conn, c),
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user