调整微信扫码登录相关接口,拆分门店奖励:奖励类型、奖励详情

This commit is contained in:
2025-06-03 11:06:00 +08:00
parent ea87bc829e
commit fdc9cc3463
37 changed files with 698 additions and 189 deletions

View File

@ -7,4 +7,5 @@ type AdminInfoReq struct {
}
type AdminInfoRes struct {
g.Meta `mime:"application/json"`
Username string `json:"username" dc:"用户名"`
}

1
api/user/v1/user.go Normal file
View File

@ -0,0 +1 @@
package user

View File

@ -4,20 +4,20 @@ import "github.com/gogf/gf/v2/frame/g"
type WeChatLoginReq struct {
g.Meta `path:"/wechat/login" method:"post" tags:"WeChat" summary:"获取微信二维码登录"`
UUID string `json:"uuid" v:"required" dc:"UUID"`
SceneId string `json:"sceneId" v:"required" dc:"场景ID,规则:[门店id]_[6位随机字符串]"`
}
type WeChatLoginRes struct {
}
type WeChatEventReq struct {
g.Meta `path:"/wechat" method:"post" tags:"WeChat" summary:"微信订阅事件"`
ToUserName string `xml:"ToUserName" dc:"开发者微信号" json:"ToUserName,omitempty"`
FromUserName string `xml:"FromUserName" dc:"发送方账号OpenID" json:"FromUserName,omitempty"`
CreateTime int64 `xml:"CreateTime" dc:"消息创建时间" json:"CreateTime,omitempty"`
ToUserName string `xml:"ToUserName" dc:"开发者微信号" json:"ToUserName"`
FromUserName string `xml:"FromUserName" dc:"发送方账号OpenID" json:"FromUserName"`
CreateTime int64 `xml:"CreateTime" dc:"消息创建时间" json:"CreateTime"`
MsgType string `xml:"MsgType" dc:"消息类型" json:"MsgType,omitempty"`
Event string `xml:"Event" dc:"事件类型 subscribe | SCAN" json:"Event,omitempty"`
EventKey string `xml:"EventKey" dc:"事件KEY值" json:"EventKey,omitempty"`
Ticket string `xml:"Ticket" dc:"二维码ticket" json:"Ticket,omitempty"`
Event string `xml:"Event" dc:"事件类型 subscribe | SCAN" json:"Event"`
EventKey string `xml:"EventKey" dc:"事件KEY值" json:"EventKey"`
Ticket string `xml:"Ticket" dc:"二维码ticket" json:"Ticket"`
}
type WeChatEventRes struct {
@ -36,7 +36,9 @@ type WeChatVertifyRes struct {
type WeChatPollingReq struct {
g.Meta `path:"/wechat/polling" method:"post" tags:"WeChat" summary:"微信长轮询"`
UUID string `json:"uuid" v:"required" dc:"UUID"`
SceneId string `json:"sceneId" v:"required" dc:"场景ID"`
}
type WeChatPollingRes struct {
Status string `json:"status" dc:"状态"`
Token string `json:"token" dc:"token"`
}

8
go.mod
View File

@ -5,31 +5,37 @@ go 1.24.2
require (
github.com/casbin/casbin/v2 v2.105.0
github.com/go-resty/resty/v2 v2.16.5
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.0
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.0
github.com/gogf/gf/v2 v2.9.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/hailaz/gf-casbin-adapter/v2 v2.8.1
golang.org/x/crypto v0.38.0
golang.org/x/sync v0.14.0
)
require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect

19
go.sum
View File

@ -2,15 +2,22 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/casbin/casbin/v2 v2.105.0 h1:dLj5P6pLApBRat9SADGiLxLZjiDPvA1bsPkyV4PGx6I=
github.com/casbin/casbin/v2 v2.105.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
@ -24,6 +31,12 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.0 h1:1f7EeD0lfPHoXfaJDSL7cxRcSRelbsAKgF3MGXY+Uyo=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.0/go.mod h1:tToO1PjGkLIR+9DbJ0wrKicYma0H/EUHXOpwel6Dw+0=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.0 h1:EEZqu1PNRSmm+7Cqm9A/8+ObgfbMzhE1ps9Z3LD7HgM=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.0/go.mod h1:LHrxY+2IzNTHVTPG/s5yaz1VmXbj+CQ7Hr5SeVkHiTw=
github.com/gogf/gf/v2 v2.9.0 h1:semN5Q5qGjDQEv4620VzxcJzJlSD07gmyJ9Sy9zfbHk=
github.com/gogf/gf/v2 v2.9.0/go.mod h1:sWGQw+pLILtuHmbOxoe0D+0DdaXxbleT57axOLH2vKI=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
@ -58,6 +71,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@ -80,6 +95,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -5,7 +5,9 @@ import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
"server/internal/controller/admin"
"server/internal/controller/auth"
"server/internal/controller/wx"
"server/internal/middleware"
)
@ -21,7 +23,15 @@ var (
group.Middleware(ghttp.MiddlewareCORS)
group.Bind(
auth.NewV1(),
wx.NewV1(),
)
group.Group("/x", func(group *ghttp.RouterGroup) {
group.Middleware(middleware.Auth)
group.Middleware(middleware.Casbin)
group.Bind(
admin.NewV1(),
)
})
})
s.Run()
return nil

View File

@ -1 +1,13 @@
package consts
const (
GuestPermission = "guest"
UserPermission = "user"
AdminPermission = "admin"
MerchantPermission = "merchant"
StorePermission = "store"
)
const (
QRCodeExpireTime = 60
QRCodeLimitTime = 10
)

View File

@ -2,13 +2,19 @@ package admin
import (
"context"
"server/internal/model"
"server/internal/service"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"server/api/admin/v1"
v1 "server/api/admin/v1"
)
func (c *ControllerV1) AdminInfo(ctx context.Context, req *v1.AdminInfoReq) (res *v1.AdminInfoRes, err error) {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
userId := g.RequestFromCtx(ctx).GetCtxVar("userId").Int()
out, err := service.Admin().Info(ctx, &model.AdminInfoIn{Id: userId})
if err != nil {
return nil, err
}
return &v1.AdminInfoRes{Username: out.Username}, nil
}

View File

@ -2,13 +2,19 @@ package auth
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"server/api/auth/v1"
v1 "server/api/auth/v1"
"server/internal/model"
"server/internal/service"
)
func (c *ControllerV1) AdminLogin(ctx context.Context, req *v1.AdminLoginReq) (res *v1.AdminLoginRes, err error) {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
out, err := service.Admin().Login(ctx, &model.AdminLoginIn{
Username: req.Username,
Password: req.Password,
})
if err != nil {
return nil, err
}
return &v1.AdminLoginRes{Token: out.Token}, nil
}

View File

@ -2,32 +2,90 @@ package wx
import (
"context"
"github.com/gogf/gf/v2/os/glog"
"fmt"
v1 "server/api/wx/v1"
"server/internal/model"
"server/internal/service"
"strings"
"server/api/auth/v1"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
)
func (c *ControllerV1) WeChatEvent(ctx context.Context, req *v1.WeChatEventReq) (res *v1.WeChatEventRes, err error) {
// 收到微信订阅事件
glog.Infof(ctx,
"微信消息推送:时间=%d, 消息类型=%s, 事件=%s, 事件Key=%s",
req.CreateTime,
req.MsgType,
req.Event,
req.EventKey,
glog.Infof(ctx, "【微信事件】收到事件 | event=%s, msgType=%s, eventKey=%s, fromUserName=%s, toUserName=%s, createTime=%d",
req.Event, req.MsgType, req.EventKey, req.FromUserName, req.ToUserName, req.CreateTime,
)
// 根据事件类型进行不同的处理:
switch req.MsgType {
case "event":
switch req.Event {
case "subscribe":
// 未关注,扫描关注后, 注册账号,关联微信的 open_id
case "SCAN":
// 已关注,扫描后,根据 open_id 查找用户生成 token
default:
// 处理其他事件
key := strings.TrimPrefix(req.EventKey, "qrscene_")
out, err := service.User().Login(ctx, &model.UserLoginIn{OpenId: req.FromUserName})
if err != nil {
return nil, err
}
if err = updateLoginCache(ctx, key, out.Token); err != nil {
glog.Errorf(ctx, "【微信事件】更新登录缓存失败 | error=%s", err.Error())
}
return nil, nil
case "SCAN":
out, err := service.User().Login(ctx, &model.UserLoginIn{OpenId: req.FromUserName})
if err != nil {
return nil, err
}
if err = updateLoginCache(ctx, req.EventKey, out.Token); err != nil {
glog.Errorf(ctx, "【微信事件】更新登录缓存失败 | error=%s", err.Error())
}
return nil, nil
default:
glog.Infof(ctx, "【微信事件】不支持的事件 | event=%s", req.Event)
return nil, nil
}
default:
glog.Infof(ctx, "【微信事件】不支持的消息类型 | msgType=%s", req.MsgType)
return nil, nil
}
}
func updateLoginCache(ctx context.Context, key string, token string) error {
loginCacheKey := fmt.Sprintf("wx:login:cache:%s", key)
glog.Infof(ctx, "【微信事件】准备更新登录缓存 | redisKey=%s, token=%s", loginCacheKey, token)
// 获取原缓存
data, err := g.Redis().Get(ctx, loginCacheKey)
if err != nil {
glog.Errorf(ctx, "【微信事件】获取缓存失败 | key=%s, error=%v", loginCacheKey, err)
return err
}
if data.IsEmpty() {
glog.Warningf(ctx, "【微信事件】缓存不存在 | key=%s", loginCacheKey)
return nil // 不是错误,只是二维码超时或错误
}
// 反序列化
var loginCache model.LoginCache
if err := gjson.Unmarshal(data.Bytes(), &loginCache); err != nil {
glog.Errorf(ctx, "【微信事件】反序列化缓存失败 | key=%s, error=%v", loginCacheKey, err)
return err
}
// 更新状态与Token
loginCache.Status = 1
loginCache.Token = token
// 序列化并写回 Redis
newData, err := gjson.Marshal(loginCache)
if err != nil {
glog.Errorf(ctx, "【微信事件】序列化缓存失败 | key=%s, error=%v", loginCacheKey, err)
return err
}
if err := g.Redis().SetEX(ctx, loginCacheKey, newData, 60); err != nil {
glog.Errorf(ctx, "【微信事件】写入缓存失败 | key=%s, error=%v", loginCacheKey, err)
return err
}
glog.Infof(ctx, "【微信事件】缓存更新成功 | key=%s, token=%s", loginCacheKey, token)
return nil
}

View File

@ -2,14 +2,54 @@ package wx
import (
"context"
"encoding/json"
"fmt"
v1 "server/api/wx/v1"
"server/internal/model"
"server/utility/ecode"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"server/api/auth/v1"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
)
func (c *ControllerV1) WeChatPolling(ctx context.Context, req *v1.WeChatPollingReq) (res *v1.WeChatPollingRes, err error) {
// 收到请求根据 uuid 查询缓存中的数据,看看是否生成了 token
return nil, gerror.NewCode(gcode.CodeNotImplemented)
loginCacheKey := fmt.Sprintf("wx:login:cache:%s", req.SceneId)
glog.Infof(ctx, "开始处理微信长轮询请求SceneID: %s", req.SceneId)
var loginCache model.LoginCache
data, err := g.Redis().Get(ctx, loginCacheKey)
if err != nil {
glog.Errorf(ctx, "从 Redis 获取登录缓存失败SceneID: %s错误: %v", req.SceneId, err)
return nil, ecode.Fail.Sub("获取登录状态失败")
}
if data.IsEmpty() {
glog.Warningf(ctx, "用户尚未扫码登录SceneID: %s", req.SceneId)
return nil, ecode.InvalidOperation.Sub("请先调用获取二维码登录")
}
if err = json.Unmarshal(data.Bytes(), &loginCache); err != nil {
glog.Errorf(ctx, "解析登录状态失败SceneID: %s错误: %v", req.SceneId, err)
return nil, ecode.Fail.Sub("解析登录状态失败")
}
switch loginCache.Status {
case 0:
glog.Infof(ctx, "用户尚未扫码登录SceneID: %s", req.SceneId)
return &v1.WeChatPollingRes{
Status: "waiting",
Token: "",
}, nil
case 1:
// 直接返回缓存的token
glog.Infof(ctx, "用户扫码登录成功SceneID: %s返回缓存Token", req.SceneId)
return &v1.WeChatPollingRes{
Status: "success",
Token: loginCache.Token,
}, nil
default:
glog.Warningf(ctx, "未知登录状态 %dSceneID: %s", loginCache.Status, req.SceneId)
return nil, ecode.InvalidOperation.Sub("未知登录状态")
}
}

View File

@ -2,31 +2,34 @@ package wx
import (
"context"
"github.com/gogf/gf/v2/crypto/gsha1"
"crypto/sha1"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"server/utility/ecode"
"server/utility/wechat"
"sort"
"strings"
"server/api/auth/v1"
"server/api/wx/v1"
)
func (c *ControllerV1) WeChatVertify(ctx context.Context, req *v1.WeChatVertifyReq) (res *v1.WeChatVertifyRes, err error) {
weChatToken := g.Config().MustGet(ctx, "wechat.token").String()
// 1. 将 token、timestamp、nonce 组成 slice
params := []string{weChatToken, req.Timestamp, req.Nonce}
// 2. 字典序排序
// 1. 排序
params := []string{wechat.GetWeChatClient().GetToken(), req.Timestamp, req.Nonce}
sort.Strings(params)
// 3. 拼接字符串
joined := strings.Join(params, "")
// 4. SHA1 加密
encrypt := gsha1.Encrypt(joined)
// 5. 与 signature 对比
if encrypt != req.Signature {
return nil, ecode.InvalidOperation.Sub("微信服务器验证失败")
// 2. 拼接成字符串
str := strings.Join(params, "")
// 3. SHA1 加密
h := sha1.New()
h.Write([]byte(str))
sha1Str := fmt.Sprintf("%x", h.Sum(nil))
// 4. 比较签名
if sha1Str != req.Signature {
return nil, fmt.Errorf("签名错误")
}
g.RequestFromCtx(ctx).Response.WriteJson(req.EchoStr)
return
g.RequestFromCtx(ctx).Response.Write(req.EchoStr)
return nil, nil
}

View File

@ -26,10 +26,7 @@ type AdminsColumns struct {
RealName string // 真实姓名
Phone string // 手机号
Email string // 邮箱
Role string // 角色1=超级管理员2=运营管理员3=客服管理员4=财务管理员
Status string // 状态1=正常2=禁用
LastLoginAt string // 最后登录时间
LastLoginIp string // 最后登录IP
CreatedAt string // 创建时间
UpdatedAt string // 更新时间
DeletedAt string // 软删除时间戳
@ -43,10 +40,7 @@ var adminsColumns = AdminsColumns{
RealName: "real_name",
Phone: "phone",
Email: "email",
Role: "role",
Status: "status",
LastLoginAt: "last_login_at",
LastLoginIp: "last_login_ip",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",

View File

@ -0,0 +1,87 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// RewardTypesDao is the data access object for the table reward_types.
type RewardTypesDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of the current DAO.
columns RewardTypesColumns // columns contains all the column names of Table for convenient usage.
}
// RewardTypesColumns defines and stores column names for the table reward_types.
type RewardTypesColumns struct {
Id string // 类型ID
Name string // 类型名称
Code string // 类型编码
Description string // 类型描述
Status string // 状态1=启用2=禁用
CreatedAt string // 创建时间
UpdatedAt string // 更新时间
DeletedAt string // 软删除时间
}
// rewardTypesColumns holds the columns for the table reward_types.
var rewardTypesColumns = RewardTypesColumns{
Id: "id",
Name: "name",
Code: "code",
Description: "description",
Status: "status",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",
}
// NewRewardTypesDao creates and returns a new DAO object for table data access.
func NewRewardTypesDao() *RewardTypesDao {
return &RewardTypesDao{
group: "default",
table: "reward_types",
columns: rewardTypesColumns,
}
}
// DB retrieves and returns the underlying raw database management object of the current DAO.
func (dao *RewardTypesDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of the current DAO.
func (dao *RewardTypesDao) Table() string {
return dao.table
}
// Columns returns all column names of the current DAO.
func (dao *RewardTypesDao) Columns() RewardTypesColumns {
return dao.columns
}
// Group returns the database configuration group name of the current DAO.
func (dao *RewardTypesDao) Group() string {
return dao.group
}
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
func (dao *RewardTypesDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rolls back the transaction and returns the error if function f returns a non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note: Do not commit or roll back the transaction in function f,
// as it is automatically handled by this function.
func (dao *RewardTypesDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}

View File

@ -22,7 +22,7 @@ type StoreRewardsDao struct {
type StoreRewardsColumns struct {
Id string // 门店奖励ID
StoreId string // 所属门店ID
RewardType string // 奖励类型1=积分2=优惠券3=商品4=抽奖券
RewardTypeId string // 奖励类型ID
RewardName string // 奖励名称
Amount string // 奖励数量
Total string // 该奖励总库存NULL 表示无限)
@ -36,7 +36,7 @@ type StoreRewardsColumns struct {
var storeRewardsColumns = StoreRewardsColumns{
Id: "id",
StoreId: "store_id",
RewardType: "reward_type",
RewardTypeId: "reward_type_id",
RewardName: "reward_name",
Amount: "amount",
Total: "total",

View File

@ -23,10 +23,8 @@ type UserLoginRecordsColumns struct {
Id string // 记录ID
UserId string // 用户ID
StoreId string // 登录门店ID
MerchantId string // 所属商户ID
LoginIp string // 登录IP地址
LoginDevice string // 登录设备信息
LoginPlatform string // 登录平台1=Web2=iOS3=Android4=微信小程序5=支付宝小程序6=其他
LoginPlatform string // 登录平台1=PC
LoginType string // 登录方式1=微信2=手机号3=账号密码4=其他
LoginStatus string // 登录状态1=成功2=失败
FailReason string // 失败原因
@ -40,9 +38,7 @@ var userLoginRecordsColumns = UserLoginRecordsColumns{
Id: "id",
UserId: "user_id",
StoreId: "store_id",
MerchantId: "merchant_id",
LoginIp: "login_ip",
LoginDevice: "login_device",
LoginPlatform: "login_platform",
LoginType: "login_type",
LoginStatus: "login_status",

View File

@ -0,0 +1,27 @@
// =================================================================================
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
// =================================================================================
package dao
import (
"server/internal/dao/internal"
)
// internalRewardTypesDao is an internal type for wrapping the internal DAO implementation.
type internalRewardTypesDao = *internal.RewardTypesDao
// rewardTypesDao is the data access object for the table reward_types.
// You can define custom methods on it to extend its functionality as needed.
type rewardTypesDao struct {
internalRewardTypesDao
}
var (
// RewardTypes is a globally accessible object for table reward_types operations.
RewardTypes = rewardTypesDao{
internal.NewRewardTypesDao(),
}
)
// Add your custom methods and functionality below.

View File

@ -2,6 +2,8 @@ package admin
import (
"context"
"github.com/gogf/gf/v2/os/glog"
"server/internal/consts"
"server/internal/dao"
"server/internal/model"
"server/internal/model/do"
@ -19,8 +21,29 @@ func New() service.IAdmin {
return &sAdmin{}
}
func checkAdmin() {
ctx := context.Background()
exist, err := dao.Admins.Ctx(ctx).Where(do.Admins{Username: "admin"}).Exist()
if err != nil {
panic("初始化管理员失败")
}
if !exist {
passwordHash, _ := utility.EncryptPassword("Aa123456")
_, err = dao.Admins.Ctx(ctx).Insert(do.Admins{
Username: "admin",
PasswordHash: passwordHash,
Status: 1,
})
if err != nil {
panic("初始化管理员失败")
}
}
glog.Infof(ctx, "初始化管理员成功")
}
func init() {
service.RegisterAdmin(New())
go checkAdmin()
}
func (s *sAdmin) Login(ctx context.Context, in *model.AdminLoginIn) (out *model.LoginOut, err error) {
exist, err := dao.Admins.Ctx(ctx).Exist(do.Admins{Username: in.Username})
@ -35,12 +58,13 @@ func (s *sAdmin) Login(ctx context.Context, in *model.AdminLoginIn) (out *model.
return nil, ecode.Fail.Sub("查询管理员失败")
}
if !utility.ComparePassword(admin.PasswordHash, in.Password) {
return nil, ecode.Auth.Sub("密码错误")
return nil, ecode.Auth
}
token, err := jwt.GenerateToken(&jwt.TokenIn{UserId: admin.Id, Permission: "admin"})
token, err := jwt.GenerateToken(&jwt.TokenIn{UserId: admin.Id, Permission: consts.AdminPermission})
if err != nil {
return nil, ecode.Fail.Sub("生成token失败")
}
out = &model.LoginOut{
Token: token,
}
@ -55,5 +79,12 @@ func (s *sAdmin) Info(ctx context.Context, in *model.AdminInfoIn) (out *model.Ad
if !exist {
return nil, ecode.Params.Sub("该用户不存在")
}
var admin entity.Admins
if err := dao.Admins.Ctx(ctx).WherePri(in.Id).Scan(&admin); err != nil {
return nil, ecode.Fail.Sub("查询管理员失败")
}
out = &model.AdminInfoOut{
Username: admin.Username,
}
return
}

View File

@ -1 +1,102 @@
package user
package internal
import (
"context"
"server/internal/consts"
"server/internal/dao"
"server/internal/model"
"server/internal/model/do"
"server/internal/model/entity"
"server/internal/service"
"server/utility/ecode"
"server/utility/jwt"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/grand"
)
type sUser struct{}
func init() {
service.RegisterUser(New())
}
func New() service.IUser {
return &sUser{}
}
func (s *sUser) Login(ctx context.Context, in *model.UserLoginIn) (out *model.UserLoginOut, err error) {
// 根据 OpenId 查找用户
exist, err := dao.Users.Ctx(ctx).Where(do.Users{WxOpenId: in.OpenId}).Exist()
if err != nil {
return nil, ecode.Fail.Sub("查找用户失败")
}
var userId int64
if !exist {
// 用户不存在,创建新用户
user := &entity.Users{
WxOpenId: in.OpenId,
Username: grand.Digits(10),
FirstVisitAt: gtime.Now(),
}
result, err := dao.Users.Ctx(ctx).Insert(user)
if err != nil {
return nil, ecode.Fail.Sub("创建用户失败")
}
userId, err = result.LastInsertId()
if err != nil {
return nil, ecode.Fail.Sub("获取用户ID失败")
}
} else {
// 用户存在,更新最后登录时间
var user entity.Users
if err := dao.Users.Ctx(ctx).Where(do.Users{WxOpenId: in.OpenId}).Scan(&user); err != nil {
return nil, ecode.Fail.Sub("查找用户失败")
}
userId = user.Id
if _, err := dao.Users.Ctx(ctx).Where(do.Users{Id: userId}).Update(do.Users{LastLoginAt: gtime.Now()}); err != nil {
return nil, ecode.Fail.Sub("更新登录时间失败")
}
}
// 生成 token
token, err := jwt.GenerateToken(&jwt.TokenIn{
UserId: userId,
Permission: consts.UserPermission,
})
if err != nil {
return nil, ecode.Fail.Sub("生成token失败")
}
out = &model.UserLoginOut{
Token: token,
}
return
}
func (s *sUser) Info(ctx context.Context, in *model.UserInfoIn) (out *model.UserInfoOut, err error) {
exist, err := dao.Users.Ctx(ctx).Where(do.Users{WxOpenId: in.OpenId, Id: in.Id}).OmitEmptyWhere().Exist()
if err != nil {
return nil, ecode.Fail.Sub("查找用户失败")
}
if !exist {
return nil, ecode.Params.Sub("用户不存在")
}
var user entity.Users
if err := dao.Users.Ctx(ctx).Where(do.Users{WxOpenId: in.OpenId, Id: in.Id}).OmitEmptyWhere().Scan(&user); err != nil {
return nil, ecode.Fail.Sub("查找用户失败")
}
out = &model.UserInfoOut{
Id: user.Id,
}
return
}
func (s *sUser) Update(ctx context.Context, in *model.UserUpdateIn) (out *model.UpdateOut, err error) {
return
}
func (s *sUser) BindPhone(ctx context.Context, in *model.UserBindPhoneIn) (out *model.UpdateOut, err error) {
return
}

View File

@ -27,7 +27,7 @@ import (
// - 若 Token 格式非法或解析失败:终止请求并返回错误。
// - 若 Token 合法:将用户信息写入上下文,继续执行下一个中间件或处理函数。
func Auth(r *ghttp.Request) {
token := r.Header.Get("Authorization")
token := r.GetHeader("Authorization")
ctx := r.GetCtx()
if token == "" {
glog.Infof(ctx, "未登录用户访问: %s %s", r.URL.Path, r.Method)

View File

@ -1,28 +1,27 @@
package middleware
import (
"github.com/gogf/gf/v2/net/ghttp"
"server/utility/ecode"
"server/utility/myCasbin"
)
// Casbin 是用于访问权限控制的中间件。
//
//import (
// "github.com/gogf/gf/v2/net/ghttp"
// "server/utility/ecode"
// "server/utility/myCasbin"
//)
// 该中间件基于 Casbin 权限控制框架,校验当前用户是否有权访问指定的 URL 和请求方法。
// 用户权限从请求上下文中的 "permission" 字段获取,该字段通常由前置中间件(如 Auth注入。
//
//// Casbin 是用于访问权限控制的中间件。
////
//// 该中间件基于 Casbin 权限控制框架,校验当前用户是否有权访问指定的 URL 和请求方法
//// 用户权限从请求上下文中的 "permission" 字段获取,该字段通常由前置中间件(如 Auth注入。
////
//// 参数:
////
//// r *ghttp.Request - 当前的 HTTP 请求对象,由框架自动传入。
////
//// 行为:
//// - 如果权限验证未通过终止请求返回权限不足的错误ecode.Denied)。
//// - 如果权限验证通过:继续执行后续中间件或处理逻辑。
//func Casbin(r *ghttp.Request) {
// permission := r.GetCtxVar("permission").String()
// if !myCasbin.GetMyCasbin().HasPermission(permission, r.URL.Path, r.Method) {
// Exit(r, ecode.Denied)
// }
// r.Middleware.Next()
//}
// 参数:
//
// r *ghttp.Request - 当前的 HTTP 请求对象,由框架自动传入
//
// 行为:
// - 如果权限验证未通过终止请求返回权限不足的错误ecode.Denied
// - 如果权限验证通过:继续执行后续中间件或处理逻辑。
func Casbin(r *ghttp.Request) {
permission := r.GetCtxVar("permission").String()
if !myCasbin.GetMyCasbin().HasPermission(permission, r.URL.Path, r.Method) {
Exit(r, ecode.Denied)
}
r.Middleware.Next()
}

View File

@ -10,6 +10,6 @@ type (
Id int
}
AdminInfoOut struct {
Token string
Username string
}
)

View File

@ -3,3 +3,14 @@ package model
type LoginOut struct {
Token string
}
type UpdateOut struct {
Success bool
}
type DeleteOut struct {
Success bool
}
type CreateOut struct {
Id int64
}

View File

@ -18,10 +18,7 @@ type Admins struct {
RealName interface{} // 真实姓名
Phone interface{} // 手机号
Email interface{} // 邮箱
Role interface{} // 角色1=超级管理员2=运营管理员3=客服管理员4=财务管理员
Status interface{} // 状态1=正常2=禁用
LastLoginAt *gtime.Time // 最后登录时间
LastLoginIp interface{} // 最后登录IP
CreatedAt *gtime.Time // 创建时间
UpdatedAt *gtime.Time // 更新时间
DeletedAt *gtime.Time // 软删除时间戳

View File

@ -0,0 +1,23 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// RewardTypes is the golang structure of table reward_types for DAO operations like Where/Data.
type RewardTypes struct {
g.Meta `orm:"table:reward_types, do:true"`
Id interface{} // 类型ID
Name interface{} // 类型名称
Code interface{} // 类型编码
Description interface{} // 类型描述
Status interface{} // 状态1=启用2=禁用
CreatedAt *gtime.Time // 创建时间
UpdatedAt *gtime.Time // 更新时间
DeletedAt *gtime.Time // 软删除时间
}

View File

@ -14,7 +14,7 @@ type StoreRewards struct {
g.Meta `orm:"table:store_rewards, do:true"`
Id interface{} // 门店奖励ID
StoreId interface{} // 所属门店ID
RewardType interface{} // 奖励类型1=积分2=优惠券3=商品4=抽奖券
RewardTypeId interface{} // 奖励类型ID
RewardName interface{} // 奖励名称
Amount interface{} // 奖励数量
Total interface{} // 该奖励总库存NULL 表示无限)

View File

@ -15,10 +15,8 @@ type UserLoginRecords struct {
Id interface{} // 记录ID
UserId interface{} // 用户ID
StoreId interface{} // 登录门店ID
MerchantId interface{} // 所属商户ID
LoginIp interface{} // 登录IP地址
LoginDevice interface{} // 登录设备信息
LoginPlatform interface{} // 登录平台1=Web2=iOS3=Android4=微信小程序5=支付宝小程序6=其他
LoginPlatform interface{} // 登录平台1=PC
LoginType interface{} // 登录方式1=微信2=手机号3=账号密码4=其他
LoginStatus interface{} // 登录状态1=成功2=失败
FailReason interface{} // 失败原因

View File

@ -16,10 +16,7 @@ type Admins struct {
RealName string `json:"realName" orm:"real_name" description:"真实姓名"` // 真实姓名
Phone string `json:"phone" orm:"phone" description:"手机号"` // 手机号
Email string `json:"email" orm:"email" description:"邮箱"` // 邮箱
Role int `json:"role" orm:"role" description:"角色1=超级管理员2=运营管理员3=客服管理员4=财务管理员"` // 角色1=超级管理员2=运营管理员3=客服管理员4=财务管理员
Status int `json:"status" orm:"status" description:"状态1=正常2=禁用"` // 状态1=正常2=禁用
LastLoginAt *gtime.Time `json:"lastLoginAt" orm:"last_login_at" description:"最后登录时间"` // 最后登录时间
LastLoginIp string `json:"lastLoginIp" orm:"last_login_ip" description:"最后登录IP"` // 最后登录IP
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间
DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳

View File

@ -0,0 +1,21 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// RewardTypes is the golang structure for table reward_types.
type RewardTypes struct {
Id int64 `json:"id" orm:"id" description:"类型ID"` // 类型ID
Name string `json:"name" orm:"name" description:"类型名称"` // 类型名称
Code string `json:"code" orm:"code" description:"类型编码"` // 类型编码
Description string `json:"description" orm:"description" description:"类型描述"` // 类型描述
Status int `json:"status" orm:"status" description:"状态1=启用2=禁用"` // 状态1=启用2=禁用
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间
DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间"` // 软删除时间
}

View File

@ -12,7 +12,7 @@ import (
type StoreRewards struct {
Id int64 `json:"id" orm:"id" description:"门店奖励ID"` // 门店奖励ID
StoreId int64 `json:"storeId" orm:"store_id" description:"所属门店ID"` // 所属门店ID
RewardType int `json:"rewardType" orm:"reward_type" description:"奖励类型1=积分2=优惠券3=商品4=抽奖券"` // 奖励类型1=积分2=优惠券3=商品4=抽奖券
RewardTypeId int64 `json:"rewardTypeId" orm:"reward_type_id" description:"奖励类型ID"` // 奖励类型ID
RewardName string `json:"rewardName" orm:"reward_name" description:"奖励名称"` // 奖励名称
Amount int `json:"amount" orm:"amount" description:"奖励数量"` // 奖励数量
Total int `json:"total" orm:"total" description:"该奖励总库存NULL 表示无限)"` // 该奖励总库存NULL 表示无限)

View File

@ -13,10 +13,8 @@ type UserLoginRecords struct {
Id int64 `json:"id" orm:"id" description:"记录ID"` // 记录ID
UserId int64 `json:"userId" orm:"user_id" description:"用户ID"` // 用户ID
StoreId int64 `json:"storeId" orm:"store_id" description:"登录门店ID"` // 登录门店ID
MerchantId int64 `json:"merchantId" orm:"merchant_id" description:"所属商户ID"` // 所属商户ID
LoginIp string `json:"loginIp" orm:"login_ip" description:"登录IP地址"` // 登录IP地址
LoginDevice string `json:"loginDevice" orm:"login_device" description:"登录设备信息"` // 登录设备信息
LoginPlatform int `json:"loginPlatform" orm:"login_platform" description:"登录平台1=Web2=iOS3=Android4=微信小程序5=支付宝小程序6=其他"` // 登录平台1=Web2=iOS3=Android4=微信小程序5=支付宝小程序6=其他
LoginPlatform int `json:"loginPlatform" orm:"login_platform" description:"登录平台1=PC"` // 登录平台1=PC
LoginType int `json:"loginType" orm:"login_type" description:"登录方式1=微信2=手机号3=账号密码4=其他"` // 登录方式1=微信2=手机号3=账号密码4=其他
LoginStatus int `json:"loginStatus" orm:"login_status" description:"登录状态1=成功2=失败"` // 登录状态1=成功2=失败
FailReason string `json:"failReason" orm:"fail_reason" description:"失败原因"` // 失败原因

29
internal/model/user.go Normal file
View File

@ -0,0 +1,29 @@
package model
import "time"
type LoginCache struct {
Token string `json:"token"`
Status int `json:"status" dc:"0-准备扫码1-已扫码"`
CreatedAt time.Time `json:"created_at"`
}
type UserLoginIn struct {
OpenId string
}
type UserLoginOut struct {
Token string
}
type UserInfoIn struct {
Id int
OpenId string
}
type UserInfoOut struct {
Id int64
}
type UserUpdateIn struct {
}
type UserBindPhoneIn struct {
}

View File

@ -1,7 +1,8 @@
package packed
import (
//_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
//_ "github.com/gogf/gf/contrib/nosql/redis/v2"
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
_ "server/utility/myCasbin"
_ "server/utility/wechat"
)

View File

@ -5,4 +5,31 @@
package service
type ()
import (
"context"
"server/internal/model"
)
type (
IUser interface {
Login(ctx context.Context, in *model.UserLoginIn) (out *model.UserLoginOut, err error)
Info(ctx context.Context, in *model.UserInfoIn) (out *model.UserInfoOut, err error)
Update(ctx context.Context, in *model.UserUpdateIn) (out *model.UpdateOut, err error)
BindPhone(ctx context.Context, in *model.UserBindPhoneIn) (out *model.UpdateOut, err error)
}
)
var (
localUser IUser
)
func User() IUser {
if localUser == nil {
panic("implement not found for interface IUser, forgot register?")
}
return localUser
}
func RegisterUser(i IUser) {
localUser = i
}

View File

@ -1,6 +1,6 @@
# Server configuration.
server:
address: ":8000"
address: ":9000"
openapiPath: "/api.json"
swaggerPath: "/swagger"
@ -15,7 +15,7 @@ database:
level: "all"
stdout: true
default:
link: "mysql:root:MSms0427@tcp(127.0.0.1:3306)/dealxuetest?loc=Local&charset=utf8mb4"
link: "mysql:root:MSms0427@tcp(127.0.0.1:3306)/arenax?loc=Local&charset=utf8mb4"
debug: true
# Redis configuration.
@ -32,4 +32,4 @@ wechat:
appId: "wx056fad20f1bd9110"
appSecret: "4269b5a2bb0274e805b43efb3fbd232a"
ticketExpire: 60
token: "arenax.com"
token: "arenax"

View File

@ -32,7 +32,12 @@ func init() {
glog.Errorf(ctx, "init casbin error: %v", err)
}
enforcer.LoadPolicy()
{
enforcer.AddPolicy("admin", "/x/admin/info", "GET", "获取管理员用户信息")
}
instance = &myCasbin{Enforcer: enforcer}
})
glog.Infof(ctx, "init casbin success")
}

View File

@ -2,14 +2,16 @@ package wechat
import (
"context"
"github.com/go-resty/resty/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"io"
"os"
"server/utility/ecode"
"sync"
"time"
"github.com/go-resty/resty/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/google/uuid"
)
type weChatClient struct {
@ -94,13 +96,13 @@ func (c *weChatClient) autoRefreshToken(ctx context.Context) {
time.Sleep(refreshAfter)
}
}
func (c *weChatClient) GetTicket(sceneID string) (string, error) {
func (c *weChatClient) GetTicket(sceneId string) (string, error) {
body := map[string]interface{}{
"expire_seconds": c.TicketExpire,
"action_name": "QR_SCENE",
"action_name": "QR_STR_SCENE",
"action_info": map[string]interface{}{
"scene": map[string]interface{}{
"scene_str": sceneID,
"scene": map[string]string{
"scene_str": sceneId,
},
},
}
@ -129,7 +131,7 @@ func (c *weChatClient) GetTicket(sceneID string) (string, error) {
return result.Ticket, nil
}
func (c *weChatClient) GetQrCode(ticket string, filename string) (imagePath string, err error) {
func (c *weChatClient) GetQrCode(ticket string) (imagePath string, err error) {
qrCodeUrl := "https://mp.weixin.qq.com/cgi-bin/showqrcode"
resp, err := resty.New().R().
SetDoNotParseResponse(true).
@ -144,8 +146,7 @@ func (c *weChatClient) GetQrCode(ticket string, filename string) (imagePath stri
glog.Errorf(context.Background(), "获取二维码图片响应异常: %+v", resp.Status())
return "", ecode.Fail.Sub("获取二维码图片失败")
}
imagePath = filename
imagePath = uuid.New().String() + ".jpg"
data, readErr := io.ReadAll(resp.RawBody())
if readErr != nil {
@ -163,3 +164,7 @@ func (c *weChatClient) GetQrCode(ticket string, filename string) (imagePath stri
glog.Infof(context.Background(), "二维码图片保存成功: %s", imagePath)
return imagePath, nil
}
func (c *weChatClient) GetToken() string {
return c.Token
}