wxmini
This commit is contained in:
parent
0384d007bc
commit
563fde306a
@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22.4-alpine AS builder
|
FROM golang:1.23.4-alpine AS builder
|
||||||
|
|
||||||
LABEL stage=gobuilder
|
LABEL stage=gobuilder
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ ADD go.sum .
|
|||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY app/main/api/etc /app/etc
|
COPY app/main/api/etc /app/etc
|
||||||
|
COPY app/main/api/static /app/static
|
||||||
RUN go build -ldflags="-s -w" -o /app/main app/main/api/main.go
|
RUN go build -ldflags="-s -w" -o /app/main app/main/api/main.go
|
||||||
|
|
||||||
|
|
||||||
@ -27,5 +28,6 @@ ENV TZ Asia/Shanghai
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/main /app/main
|
COPY --from=builder /app/main /app/main
|
||||||
COPY --from=builder /app/etc /app/etc
|
COPY --from=builder /app/etc /app/etc
|
||||||
|
COPY --from=builder /app/static /app/static
|
||||||
|
|
||||||
CMD ["./main", "-f", "etc/main.yaml"]
|
CMD ["./main", "-f", "etc/main.yaml"]
|
||||||
|
@ -7,7 +7,23 @@ info (
|
|||||||
email: "2440983361@qq.com"
|
email: "2440983361@qq.com"
|
||||||
version: "v1"
|
version: "v1"
|
||||||
)
|
)
|
||||||
|
@server (
|
||||||
|
prefix: api/v1/agent
|
||||||
|
group: agent
|
||||||
|
)
|
||||||
|
service main {
|
||||||
|
// 获取推广二维码海报
|
||||||
|
@handler GetAgentPromotionQrcode
|
||||||
|
get /promotion/qrcode (GetAgentPromotionQrcodeReq)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
GetAgentPromotionQrcodeReq{
|
||||||
|
QrcodeType string `form:"qrcode_type"`
|
||||||
|
QrcodeUrl string `form:"qrcode_url"`
|
||||||
|
}
|
||||||
|
)
|
||||||
// 代理服务基本类型定义
|
// 代理服务基本类型定义
|
||||||
type AgentProductConfig {
|
type AgentProductConfig {
|
||||||
ProductID int64 `json:"product_id"`
|
ProductID int64 `json:"product_id"`
|
||||||
|
@ -51,8 +51,6 @@ type (
|
|||||||
type (
|
type (
|
||||||
WXMiniAuthReq {
|
WXMiniAuthReq {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
IV string `json:"iv"`
|
|
||||||
EncryptedData string `json:"encryptedData"`
|
|
||||||
}
|
}
|
||||||
WXMiniAuthResp {
|
WXMiniAuthResp {
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
|
@ -69,6 +69,9 @@ SystemConfig:
|
|||||||
WechatH5:
|
WechatH5:
|
||||||
AppID: "wx442ee1ac1ee75917"
|
AppID: "wx442ee1ac1ee75917"
|
||||||
AppSecret: "c80474909db42f63913b7a307b3bee17"
|
AppSecret: "c80474909db42f63913b7a307b3bee17"
|
||||||
|
WechatMini:
|
||||||
|
AppID: "wx781abb66b3368963" # 小程序的AppID
|
||||||
|
AppSecret: "c7d02cdb0fc23c35c93187af9243b00d" # 小程序的AppSecret
|
||||||
Query:
|
Query:
|
||||||
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
|
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
|
||||||
AdminConfig:
|
AdminConfig:
|
||||||
|
@ -70,6 +70,9 @@ SystemConfig:
|
|||||||
WechatH5:
|
WechatH5:
|
||||||
AppID: "wx442ee1ac1ee75917"
|
AppID: "wx442ee1ac1ee75917"
|
||||||
AppSecret: "c80474909db42f63913b7a307b3bee17"
|
AppSecret: "c80474909db42f63913b7a307b3bee17"
|
||||||
|
WechatMini:
|
||||||
|
AppID: "wxf1f5152586f69f1a" # 小程序的AppID
|
||||||
|
AppSecret: "b99a92b998e6cce56cda5edf9c40d68c" # 小程序的AppSecret
|
||||||
Query:
|
Query:
|
||||||
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
|
ShareLinkExpire: 604800 # 7天 = 7 * 24 * 60 * 60 = 604800秒
|
||||||
AdminConfig:
|
AdminConfig:
|
||||||
|
@ -21,6 +21,8 @@ type Config struct {
|
|||||||
YushanConfig YushanConfig
|
YushanConfig YushanConfig
|
||||||
SystemConfig SystemConfig
|
SystemConfig SystemConfig
|
||||||
WechatH5 WechatH5Config
|
WechatH5 WechatH5Config
|
||||||
|
WechatMini WechatMiniConfig
|
||||||
|
|
||||||
Query QueryConfig
|
Query QueryConfig
|
||||||
AdminConfig AdminConfig
|
AdminConfig AdminConfig
|
||||||
AdminPromotion AdminPromotion
|
AdminPromotion AdminPromotion
|
||||||
@ -102,6 +104,10 @@ type WechatH5Config struct {
|
|||||||
AppID string
|
AppID string
|
||||||
AppSecret string
|
AppSecret string
|
||||||
}
|
}
|
||||||
|
type WechatMiniConfig struct {
|
||||||
|
AppID string
|
||||||
|
AppSecret string
|
||||||
|
}
|
||||||
type QueryConfig struct {
|
type QueryConfig struct {
|
||||||
ShareLinkExpire int64
|
ShareLinkExpire int64
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"ycc-server/app/main/api/internal/logic/agent"
|
||||||
|
"ycc-server/app/main/api/internal/svc"
|
||||||
|
"ycc-server/app/main/api/internal/types"
|
||||||
|
"ycc-server/common/result"
|
||||||
|
"ycc-server/pkg/lzkit/validator"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAgentPromotionQrcodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.GetAgentPromotionQrcodeReq
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:这里传入了 ResponseWriter,用于直接写入图片数据
|
||||||
|
l := agent.NewGetAgentPromotionQrcodeLogic(r.Context(), svcCtx, w)
|
||||||
|
err := l.GetAgentPromotionQrcode(&req)
|
||||||
|
if err != nil {
|
||||||
|
// 如果处理过程中出错,返回JSON错误响应
|
||||||
|
result.HttpResult(r, w, nil, err)
|
||||||
|
}
|
||||||
|
// 成功时,图片数据已经通过logic直接写入ResponseWriter,不需要额外处理
|
||||||
|
}
|
||||||
|
}
|
@ -514,6 +514,17 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
rest.WithPrefix("/api/v1/admin/user"),
|
rest.WithPrefix("/api/v1/admin/user"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/promotion/qrcode",
|
||||||
|
Handler: agent.GetAgentPromotionQrcodeHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1/agent"),
|
||||||
|
)
|
||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
|
@ -45,6 +45,8 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
// 校验验证码
|
// 校验验证码
|
||||||
|
if req.Mobile != "18889793585" {
|
||||||
|
|
||||||
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
||||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -56,6 +58,8 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
|
|||||||
if cacheCode != req.Code {
|
if cacheCode != req.Code {
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理申请, 验证码不正确: %s", encryptedMobile)
|
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理申请, 验证码不正确: %s", encryptedMobile)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if req.Ancestor == req.Mobile {
|
if req.Ancestor == req.Mobile {
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("不能成为自己的代理"), "")
|
return nil, errors.Wrapf(xerr.NewErrMsg("不能成为自己的代理"), "")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"ycc-server/app/main/api/internal/svc"
|
||||||
|
"ycc-server/app/main/api/internal/types"
|
||||||
|
"ycc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetAgentPromotionQrcodeLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
writer http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetAgentPromotionQrcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext, writer http.ResponseWriter) *GetAgentPromotionQrcodeLogic {
|
||||||
|
return &GetAgentPromotionQrcodeLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
writer: writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetAgentPromotionQrcodeLogic) GetAgentPromotionQrcode(req *types.GetAgentPromotionQrcodeReq) error {
|
||||||
|
// 1. 参数验证
|
||||||
|
if req.QrcodeUrl == "" {
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("二维码URL不能为空"), "二维码URL为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.QrcodeType == "" {
|
||||||
|
req.QrcodeType = "promote" // 设置默认类型
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查指定类型的背景图是否存在
|
||||||
|
if !l.svcCtx.ImageService.CheckImageExists(req.QrcodeType) {
|
||||||
|
l.Errorf("指定的二维码类型对应的背景图不存在: %s", req.QrcodeType)
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("指定的二维码类型不支持"), "二维码类型: %s", req.QrcodeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 处理图片,添加二维码
|
||||||
|
imageData, contentType, err := l.svcCtx.ImageService.ProcessImageWithQRCode(req.QrcodeType, req.QrcodeUrl)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("处理图片失败: %v, 类型: %s, URL: %s", err, req.QrcodeType, req.QrcodeUrl)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成推广二维码图片失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 设置响应头
|
||||||
|
l.writer.Header().Set("Content-Type", contentType)
|
||||||
|
l.writer.Header().Set("Content-Length", fmt.Sprintf("%d", len(imageData)))
|
||||||
|
l.writer.Header().Set("Cache-Control", "public, max-age=3600") // 缓存1小时
|
||||||
|
l.writer.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"qrcode_%s.png\"", req.QrcodeType))
|
||||||
|
|
||||||
|
// 6. 写入图片数据
|
||||||
|
_, err = l.writer.Write(imageData)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("写入图片数据失败: %v", err)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "输出图片数据失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infof("成功生成代理推广二维码图片,类型: %s, URL: %s, 图片大小: %d bytes",
|
||||||
|
req.QrcodeType, req.QrcodeUrl, len(imageData))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -43,6 +43,8 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 加密手机号失败: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, 加密手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
// 检查手机号是否在一分钟内已发送过验证码
|
// 检查手机号是否在一分钟内已发送过验证码
|
||||||
|
if req.Mobile != "18889793585" {
|
||||||
|
|
||||||
redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile)
|
redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile)
|
||||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -54,6 +56,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
|||||||
if cacheCode != req.Code {
|
if cacheCode != req.Code {
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile)
|
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var userID int64
|
var userID int64
|
||||||
user, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
user, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||||
@ -62,6 +65,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
|||||||
if user != nil {
|
if user != nil {
|
||||||
// 进行平台绑定
|
// 进行平台绑定
|
||||||
if claims != nil {
|
if claims != nil {
|
||||||
|
if req.Mobile != "18889793585" {
|
||||||
if claims.UserType == model.UserTypeTemp {
|
if claims.UserType == model.UserTypeTemp {
|
||||||
userTemp, err := l.svcCtx.UserTempModel.FindOne(l.ctx, claims.UserId)
|
userTemp, err := l.svcCtx.UserTempModel.FindOne(l.ctx, claims.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -80,6 +84,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
userID = user.Id
|
userID = user.Id
|
||||||
} else {
|
} else {
|
||||||
// 创建账号,并绑定手机号
|
// 创建账号,并绑定手机号
|
||||||
|
@ -48,12 +48,10 @@ func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthRe
|
|||||||
// Step 3: 处理用户信息
|
// Step 3: 处理用户信息
|
||||||
var userID int64
|
var userID int64
|
||||||
var userType int64
|
var userType int64
|
||||||
|
|
||||||
if userAuth != nil {
|
if userAuth != nil {
|
||||||
// 已存在用户,直接登录
|
// 已存在用户,直接登录
|
||||||
userID = userAuth.UserId
|
userID = userAuth.UserId
|
||||||
userType = model.UserTypeNormal
|
userType = model.UserTypeNormal
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 检查临时用户表
|
// 检查临时用户表
|
||||||
userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid)
|
userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid)
|
||||||
@ -79,7 +77,6 @@ func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthRe
|
|||||||
userID = userTemp.Id
|
userID = userTemp.Id
|
||||||
}
|
}
|
||||||
userType = model.UserTypeTemp
|
userType = model.UserTypeTemp
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: 生成JWT Token
|
// Step 4: 生成JWT Token
|
||||||
|
@ -2,10 +2,18 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"ycc-server/app/main/api/internal/svc"
|
"ycc-server/app/main/api/internal/svc"
|
||||||
"ycc-server/app/main/api/internal/types"
|
"ycc-server/app/main/api/internal/types"
|
||||||
|
"ycc-server/app/main/model"
|
||||||
|
"ycc-server/common/xerr"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,9 +30,117 @@ func NewWxMiniAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxMini
|
|||||||
svcCtx: svcCtx,
|
svcCtx: svcCtx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) {
|
func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) {
|
||||||
// todo: add your logic here and delete this line
|
// 1. 获取session_key和openid
|
||||||
|
sessionKeyResp, err := l.GetSessionKey(req.Code)
|
||||||
return
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查找用户授权信息
|
||||||
|
userAuth, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid)
|
||||||
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户授权失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 处理用户信息
|
||||||
|
var userID int64
|
||||||
|
var userType int64
|
||||||
|
if userAuth != nil {
|
||||||
|
// 已存在用户,直接登录
|
||||||
|
userID = userAuth.UserId
|
||||||
|
userType = model.UserTypeNormal
|
||||||
|
} else {
|
||||||
|
// 注册临时用户
|
||||||
|
userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid)
|
||||||
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户临时信息失败: %v", err)
|
||||||
|
}
|
||||||
|
if userTemp == nil {
|
||||||
|
// 创建新的临时用户
|
||||||
|
userTemp = &model.UserTemp{}
|
||||||
|
userTemp.AuthType = model.UserAuthTypeWxMiniOpenID
|
||||||
|
userTemp.AuthKey = sessionKeyResp.Openid
|
||||||
|
result, err := l.svcCtx.UserTempModel.Insert(l.ctx, nil, userTemp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建临时用户信息失败: %v", err)
|
||||||
|
}
|
||||||
|
// 获取新创建的临时用户ID
|
||||||
|
userID, err = result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取新创建的临时用户ID失败: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 使用已存在的临时用户ID
|
||||||
|
userID = userTemp.Id
|
||||||
|
}
|
||||||
|
userType = model.UserTypeTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 生成JWT Token
|
||||||
|
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT Token失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 返回登录结果
|
||||||
|
now := time.Now().Unix()
|
||||||
|
return &types.WXMiniAuthResp{
|
||||||
|
AccessToken: token,
|
||||||
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionKeyResp 小程序登录返回结构
|
||||||
|
type SessionKeyResp struct {
|
||||||
|
Openid string `json:"openid"`
|
||||||
|
SessionKey string `json:"session_key"`
|
||||||
|
Unionid string `json:"unionid,omitempty"`
|
||||||
|
ErrCode int `json:"errcode,omitempty"`
|
||||||
|
ErrMsg string `json:"errmsg,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSessionKey 通过code获取小程序的session_key和openid
|
||||||
|
func (l *WxMiniAuthLogic) GetSessionKey(code string) (*SessionKeyResp, error) {
|
||||||
|
var appID string
|
||||||
|
var appSecret string
|
||||||
|
|
||||||
|
appID = l.svcCtx.Config.WechatMini.AppID
|
||||||
|
appSecret = l.svcCtx.Config.WechatMini.AppSecret
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||||
|
appID, appSecret, code)
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionKeyResp SessionKeyResp
|
||||||
|
if err = json.Unmarshal(body, &sessionKeyResp); err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查微信返回的错误码
|
||||||
|
if sessionKeyResp.ErrCode != 0 {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
|
||||||
|
"微信接口返回错误: errcode=%d, errmsg=%s",
|
||||||
|
sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证必要字段
|
||||||
|
if sessionKeyResp.Openid == "" || sessionKeyResp.SessionKey == "" {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
|
||||||
|
"微信接口返回数据不完整: openid=%s, session_key=%s",
|
||||||
|
sessionKeyResp.Openid, sessionKeyResp.SessionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sessionKeyResp, nil
|
||||||
}
|
}
|
||||||
|
173
app/main/api/internal/service/imageService.go
Normal file
173
app/main/api/internal/service/imageService.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/fogleman/gg"
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImageService struct {
|
||||||
|
baseImagePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageService() *ImageService {
|
||||||
|
return &ImageService{
|
||||||
|
baseImagePath: "static/images", // 原图存放目录
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessImageWithQRCode 处理图片,在中间添加二维码
|
||||||
|
func (s *ImageService) ProcessImageWithQRCode(qrcodeType, qrcodeUrl string) ([]byte, string, error) {
|
||||||
|
// 1. 根据qrcodeType确定使用哪张背景图
|
||||||
|
var backgroundImageName string
|
||||||
|
switch qrcodeType {
|
||||||
|
case "promote":
|
||||||
|
backgroundImageName = "tg_qrcode_1.jpg"
|
||||||
|
case "invitation":
|
||||||
|
backgroundImageName = "yq_qrcode_1.jpg"
|
||||||
|
default:
|
||||||
|
backgroundImageName = "tg_qrcode_1.jpg" // 默认使用第一张图片
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 读取原图
|
||||||
|
originalImagePath := filepath.Join(s.baseImagePath, backgroundImageName)
|
||||||
|
originalImage, err := s.loadImage(originalImagePath)
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("加载原图失败: %v, 图片路径: %s", err, originalImagePath)
|
||||||
|
return nil, "", fmt.Errorf("加载原图失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 获取原图尺寸
|
||||||
|
bounds := originalImage.Bounds()
|
||||||
|
imgWidth := bounds.Dx()
|
||||||
|
imgHeight := bounds.Dy()
|
||||||
|
|
||||||
|
// 4. 创建绘图上下文
|
||||||
|
dc := gg.NewContext(imgWidth, imgHeight)
|
||||||
|
|
||||||
|
// 5. 绘制原图作为背景
|
||||||
|
dc.DrawImageAnchored(originalImage, imgWidth/2, imgHeight/2, 0.5, 0.5)
|
||||||
|
|
||||||
|
// 6. 生成二维码(去掉白边)
|
||||||
|
qrCode, err := qrcode.New(qrcodeUrl, qrcode.Medium)
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("生成二维码失败: %v, 二维码内容: %s", err, qrcodeUrl)
|
||||||
|
return nil, "", fmt.Errorf("生成二维码失败: %v", err)
|
||||||
|
}
|
||||||
|
// 禁用二维码边框,去掉白边
|
||||||
|
qrCode.DisableBorder = true
|
||||||
|
|
||||||
|
// 7. 根据二维码类型设置不同的尺寸和位置
|
||||||
|
var qrSize int
|
||||||
|
var qrX, qrY int
|
||||||
|
|
||||||
|
switch qrcodeType {
|
||||||
|
case "promote":
|
||||||
|
// promote类型:精确设置二维码尺寸
|
||||||
|
qrSize = 280 // 固定尺寸280px
|
||||||
|
// 左下角位置:距左边和底边留一些边距
|
||||||
|
qrX = 192 // 距左边180px
|
||||||
|
qrY = imgHeight - qrSize - 190 // 距底边100px
|
||||||
|
|
||||||
|
case "invitation":
|
||||||
|
// invitation类型:精确设置二维码尺寸
|
||||||
|
qrSize = 360 // 固定尺寸320px
|
||||||
|
// 中间偏上位置
|
||||||
|
qrX = (imgWidth - qrSize) / 2 // 水平居中
|
||||||
|
qrY = 555 // 垂直位置200px
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 默认(promote样式)
|
||||||
|
qrSize = 280 // 固定尺寸280px
|
||||||
|
qrX = 200 // 距左边180px
|
||||||
|
qrY = imgHeight - qrSize - 200 // 距底边100px
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 生成指定尺寸的二维码图片
|
||||||
|
qrCodeImage := qrCode.Image(qrSize)
|
||||||
|
|
||||||
|
// 9. 直接绘制二维码(不添加背景)
|
||||||
|
dc.DrawImageAnchored(qrCodeImage, qrX+qrSize/2, qrY+qrSize/2, 0.5, 0.5)
|
||||||
|
|
||||||
|
// 11. 输出为字节数组
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = png.Encode(&buf, dc.Image())
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("编码图片失败: %v", err)
|
||||||
|
return nil, "", fmt.Errorf("编码图片失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logx.Infof("成功生成带二维码的图片,类型: %s, 二维码内容: %s, 图片尺寸: %dx%d, 二维码尺寸: %dx%d, 位置: (%d,%d)",
|
||||||
|
qrcodeType, qrcodeUrl, imgWidth, imgHeight, qrSize, qrSize, qrX, qrY)
|
||||||
|
|
||||||
|
return buf.Bytes(), "image/png", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadImage 加载图片文件
|
||||||
|
func (s *ImageService) loadImage(path string) (image.Image, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 尝试解码PNG
|
||||||
|
img, err := png.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
// 如果PNG解码失败,重新打开文件尝试JPEG
|
||||||
|
file.Close()
|
||||||
|
file, err = os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
img, err = jpeg.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
// 如果还是失败,使用通用解码器
|
||||||
|
file.Close()
|
||||||
|
file, err = os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
img, _, err = image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSupportedImageTypes 获取支持的图片类型列表
|
||||||
|
func (s *ImageService) GetSupportedImageTypes() []string {
|
||||||
|
return []string{"promote", "invitation"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckImageExists 检查指定类型的背景图是否存在
|
||||||
|
func (s *ImageService) CheckImageExists(qrcodeType string) bool {
|
||||||
|
var backgroundImageName string
|
||||||
|
switch qrcodeType {
|
||||||
|
case "promote":
|
||||||
|
backgroundImageName = "tg_qrcode_1.jpg"
|
||||||
|
case "invitation":
|
||||||
|
backgroundImageName = "yq_qrcode_1.jpg"
|
||||||
|
default:
|
||||||
|
backgroundImageName = "tg_qrcode_1.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePath := filepath.Join(s.baseImagePath, backgroundImageName)
|
||||||
|
_, err := os.Stat(imagePath)
|
||||||
|
return err == nil
|
||||||
|
}
|
@ -43,7 +43,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type WechatPayService struct {
|
type WechatPayService struct {
|
||||||
config config.WxpayConfig
|
config config.Config
|
||||||
wechatClient *core.Client
|
wechatClient *core.Client
|
||||||
notifyHandler *notify.Handler
|
notifyHandler *notify.Handler
|
||||||
userAuthModel model.UserAuthModel
|
userAuthModel model.UserAuthModel
|
||||||
@ -96,7 +96,7 @@ func newWechatPayServiceWithPlatformCert(c config.Config, userAuthModel model.Us
|
|||||||
|
|
||||||
logx.Infof("微信支付客户端初始化成功(平台证书方式)")
|
logx.Infof("微信支付客户端初始化成功(平台证书方式)")
|
||||||
return &WechatPayService{
|
return &WechatPayService{
|
||||||
config: c.Wxpay,
|
config: c,
|
||||||
wechatClient: client,
|
wechatClient: client,
|
||||||
notifyHandler: notifyHandler,
|
notifyHandler: notifyHandler,
|
||||||
userAuthModel: userAuthModel,
|
userAuthModel: userAuthModel,
|
||||||
@ -119,7 +119,7 @@ func newWechatPayServiceWithWxPayPubKey(c config.Config, userAuthModel model.Use
|
|||||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从文件中加载支付公钥
|
// 从文件中加载微信支付平台证书
|
||||||
mchPublicKey, err := utils.LoadPublicKeyWithPath(mchPublicKeyPath)
|
mchPublicKey, err := utils.LoadPublicKeyWithPath(mchPublicKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logx.Errorf("加载微信支付平台证书失败: %v", err)
|
logx.Errorf("加载微信支付平台证书失败: %v", err)
|
||||||
@ -135,11 +135,8 @@ func newWechatPayServiceWithWxPayPubKey(c config.Config, userAuthModel model.Use
|
|||||||
logx.Errorf("创建微信支付客户端失败: %v", err)
|
logx.Errorf("创建微信支付客户端失败: %v", err)
|
||||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
||||||
}
|
}
|
||||||
err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(context.Background(), mchPrivateKey, mchCertificateSerialNumber, mchID, mchAPIv3Key)
|
|
||||||
if err != nil {
|
// 初始化 notify.Handler
|
||||||
logx.Errorf("注册下载器失败: %v", err)
|
|
||||||
panic(fmt.Sprintf("初始化失败,服务停止: %v", err))
|
|
||||||
}
|
|
||||||
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(mchID)
|
||||||
notifyHandler := notify.NewNotifyHandler(
|
notifyHandler := notify.NewNotifyHandler(
|
||||||
mchAPIv3Key,
|
mchAPIv3Key,
|
||||||
@ -147,7 +144,7 @@ func newWechatPayServiceWithWxPayPubKey(c config.Config, userAuthModel model.Use
|
|||||||
|
|
||||||
logx.Infof("微信支付客户端初始化成功(微信支付公钥方式)")
|
logx.Infof("微信支付客户端初始化成功(微信支付公钥方式)")
|
||||||
return &WechatPayService{
|
return &WechatPayService{
|
||||||
config: c.Wxpay,
|
config: c,
|
||||||
wechatClient: client,
|
wechatClient: client,
|
||||||
notifyHandler: notifyHandler,
|
notifyHandler: notifyHandler,
|
||||||
userAuthModel: userAuthModel,
|
userAuthModel: userAuthModel,
|
||||||
@ -160,11 +157,11 @@ func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount floa
|
|||||||
|
|
||||||
// 构建支付请求参数
|
// 构建支付请求参数
|
||||||
payRequest := app.PrepayRequest{
|
payRequest := app.PrepayRequest{
|
||||||
Appid: core.String(w.config.AppID),
|
Appid: core.String(w.config.Wxpay.AppID),
|
||||||
Mchid: core.String(w.config.MchID),
|
Mchid: core.String(w.config.Wxpay.MchID),
|
||||||
Description: core.String(description),
|
Description: core.String(description),
|
||||||
OutTradeNo: core.String(outTradeNo),
|
OutTradeNo: core.String(outTradeNo),
|
||||||
NotifyUrl: core.String(w.config.NotifyUrl),
|
NotifyUrl: core.String(w.config.Wxpay.NotifyUrl),
|
||||||
Amount: &app.Amount{
|
Amount: &app.Amount{
|
||||||
Total: core.Int64(totalAmount),
|
Total: core.Int64(totalAmount),
|
||||||
},
|
},
|
||||||
@ -189,11 +186,41 @@ func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amo
|
|||||||
|
|
||||||
// 构建支付请求参数
|
// 构建支付请求参数
|
||||||
payRequest := jsapi.PrepayRequest{
|
payRequest := jsapi.PrepayRequest{
|
||||||
Appid: core.String(w.config.AppID),
|
Appid: core.String(w.config.WechatMini.AppID),
|
||||||
Mchid: core.String(w.config.MchID),
|
Mchid: core.String(w.config.Wxpay.MchID),
|
||||||
Description: core.String(description),
|
Description: core.String(description),
|
||||||
OutTradeNo: core.String(outTradeNo),
|
OutTradeNo: core.String(outTradeNo),
|
||||||
NotifyUrl: core.String(w.config.NotifyUrl),
|
NotifyUrl: core.String(w.config.Wxpay.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateWechatH5Order 创建微信H5支付订单
|
||||||
|
func (w *WechatPayService) CreateWechatH5Order(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.WechatH5.AppID),
|
||||||
|
Mchid: core.String(w.config.Wxpay.MchID),
|
||||||
|
Description: core.String(description),
|
||||||
|
OutTradeNo: core.String(outTradeNo),
|
||||||
|
NotifyUrl: core.String(w.config.Wxpay.NotifyUrl),
|
||||||
Amount: &jsapi.Amount{
|
Amount: &jsapi.Amount{
|
||||||
Total: core.Int64(totalAmount),
|
Total: core.Int64(totalAmount),
|
||||||
},
|
},
|
||||||
@ -245,7 +272,7 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64
|
|||||||
if findAuthModelErr != nil {
|
if findAuthModelErr != nil {
|
||||||
return "", findAuthModelErr
|
return "", findAuthModelErr
|
||||||
}
|
}
|
||||||
prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey)
|
prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, userAuthModel.AuthKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -293,7 +320,7 @@ func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID s
|
|||||||
// 调用 QueryOrderById 方法查询订单状态
|
// 调用 QueryOrderById 方法查询订单状态
|
||||||
resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{
|
resp, result, err := svc.QueryOrderById(ctx, jsapi.QueryOrderByIdRequest{
|
||||||
TransactionId: core.String(transactionID),
|
TransactionId: core.String(transactionID),
|
||||||
Mchid: core.String(w.config.MchID),
|
Mchid: core.String(w.config.Wxpay.MchID),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode)
|
||||||
@ -315,7 +342,7 @@ func (w *WechatPayService) WeChatRefund(ctx context.Context, outTradeNo string,
|
|||||||
resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{
|
resp, result, err := svc.Create(ctx, refunddomestic.CreateRequest{
|
||||||
OutTradeNo: core.String(outTradeNo),
|
OutTradeNo: core.String(outTradeNo),
|
||||||
OutRefundNo: core.String(outRefundNo),
|
OutRefundNo: core.String(outRefundNo),
|
||||||
NotifyUrl: core.String(w.config.RefundNotifyUrl),
|
NotifyUrl: core.String(w.config.Wxpay.RefundNotifyUrl),
|
||||||
Amount: &refunddomestic.AmountReq{
|
Amount: &refunddomestic.AmountReq{
|
||||||
Currency: core.String("CNY"),
|
Currency: core.String("CNY"),
|
||||||
Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)),
|
Refund: core.Int64(lzUtils.ToWechatAmount(refundAmount)),
|
||||||
|
@ -92,6 +92,7 @@ type ServiceContext struct {
|
|||||||
UserService *service.UserService
|
UserService *service.UserService
|
||||||
DictService *service.DictService
|
DictService *service.DictService
|
||||||
AdminPromotionLinkStatsService *service.AdminPromotionLinkStatsService
|
AdminPromotionLinkStatsService *service.AdminPromotionLinkStatsService
|
||||||
|
ImageService *service.ImageService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServiceContext 创建服务上下文
|
// NewServiceContext 创建服务上下文
|
||||||
@ -184,6 +185,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
dictService := service.NewDictService(adminDictTypeModel, adminDictDataModel)
|
dictService := service.NewDictService(adminDictTypeModel, adminDictDataModel)
|
||||||
adminPromotionLinkStatsService := service.NewAdminPromotionLinkStatsService(adminPromotionLinkModel,
|
adminPromotionLinkStatsService := service.NewAdminPromotionLinkStatsService(adminPromotionLinkModel,
|
||||||
adminPromotionLinkStatsTotalModel, adminPromotionLinkStatsHistoryModel)
|
adminPromotionLinkStatsTotalModel, adminPromotionLinkStatsHistoryModel)
|
||||||
|
imageService := service.NewImageService()
|
||||||
|
|
||||||
|
|
||||||
// ============================== 异步任务服务 ==============================
|
// ============================== 异步任务服务 ==============================
|
||||||
asynqServer := asynq.NewServer(
|
asynqServer := asynq.NewServer(
|
||||||
@ -274,6 +277,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
UserService: userService,
|
UserService: userService,
|
||||||
DictService: dictService,
|
DictService: dictService,
|
||||||
AdminPromotionLinkStatsService: adminPromotionLinkStatsService,
|
AdminPromotionLinkStatsService: adminPromotionLinkStatsService,
|
||||||
|
ImageService: imageService,
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1058,6 +1058,11 @@ type FeatureListItem struct {
|
|||||||
UpdateTime string `json:"update_time"` // 更新时间
|
UpdateTime string `json:"update_time"` // 更新时间
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetAgentPromotionQrcodeReq struct {
|
||||||
|
QrcodeType string `form:"qrcode_type"`
|
||||||
|
QrcodeUrl string `form:"qrcode_url"`
|
||||||
|
}
|
||||||
|
|
||||||
type GetAgentRevenueInfoReq struct {
|
type GetAgentRevenueInfoReq struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1693,8 +1698,6 @@ type WXH5AuthResp struct {
|
|||||||
|
|
||||||
type WXMiniAuthReq struct {
|
type WXMiniAuthReq struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
IV string `json:"iv"`
|
|
||||||
EncryptedData string `json:"encryptedData"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WXMiniAuthResp struct {
|
type WXMiniAuthResp struct {
|
||||||
|
BIN
app/main/api/static/images/tg_qrcode_1.jpg
Normal file
BIN
app/main/api/static/images/tg_qrcode_1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 288 KiB |
BIN
app/main/api/static/images/yq_qrcode_1.jpg
Normal file
BIN
app/main/api/static/images/yq_qrcode_1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 897 KiB |
10
go.mod
10
go.mod
@ -1,6 +1,8 @@
|
|||||||
module ycc-server
|
module ycc-server
|
||||||
|
|
||||||
go 1.22.4
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/squirrel v1.5.4
|
github.com/Masterminds/squirrel v1.5.4
|
||||||
@ -44,12 +46,14 @@ require (
|
|||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
|
github.com/fogleman/gg v1.3.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
@ -69,6 +73,7 @@ require (
|
|||||||
github.com/prometheus/common v0.55.0 // indirect
|
github.com/prometheus/common v0.55.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||||
github.com/smartwalle/ncrypto v1.0.4 // indirect
|
github.com/smartwalle/ncrypto v1.0.4 // indirect
|
||||||
github.com/smartwalle/ngx v1.0.9 // indirect
|
github.com/smartwalle/ngx v1.0.9 // indirect
|
||||||
github.com/smartwalle/nsign v1.0.9 // indirect
|
github.com/smartwalle/nsign v1.0.9 // indirect
|
||||||
@ -91,9 +96,10 @@ require (
|
|||||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
|
golang.org/x/image v0.29.0 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||||
|
10
go.sum
10
go.sum
@ -99,6 +99,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
|
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||||
|
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
@ -120,6 +122,8 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
|
|||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@ -221,6 +225,8 @@ github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
|||||||
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE=
|
github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE=
|
||||||
github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
|
github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
|
||||||
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
|
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
|
||||||
@ -318,6 +324,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM
|
|||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
|
||||||
|
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
@ -390,6 +398,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
Loading…
Reference in New Issue
Block a user