调整微信扫码登录相关接口,拆分门店奖励:奖励类型、奖励详情
This commit is contained in:
@ -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
1
api/user/v1/user.go
Normal file
@ -0,0 +1 @@
|
||||
package user
|
||||
@ -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
8
go.mod
@ -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
19
go.sum
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1 +1,13 @@
|
||||
package consts
|
||||
|
||||
const (
|
||||
GuestPermission = "guest"
|
||||
UserPermission = "user"
|
||||
AdminPermission = "admin"
|
||||
MerchantPermission = "merchant"
|
||||
StorePermission = "store"
|
||||
)
|
||||
const (
|
||||
QRCodeExpireTime = 60
|
||||
QRCodeLimitTime = 10
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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, "未知登录状态 %d,SceneID: %s", loginCache.Status, req.SceneId)
|
||||
return nil, ecode.InvalidOperation.Sub("未知登录状态")
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
87
internal/dao/internal/reward_types.go
Normal file
87
internal/dao/internal/reward_types.go
Normal 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)
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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=Web,2=iOS,3=Android,4=微信小程序,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",
|
||||
|
||||
27
internal/dao/reward_types.go
Normal file
27
internal/dao/reward_types.go
Normal 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.
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -10,6 +10,6 @@ type (
|
||||
Id int
|
||||
}
|
||||
AdminInfoOut struct {
|
||||
Token string
|
||||
Username string
|
||||
}
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 // 软删除时间戳
|
||||
|
||||
23
internal/model/do/reward_types.go
Normal file
23
internal/model/do/reward_types.go
Normal 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 // 软删除时间
|
||||
}
|
||||
@ -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 表示无限)
|
||||
|
||||
@ -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=Web,2=iOS,3=Android,4=微信小程序,5=支付宝小程序,6=其他
|
||||
LoginPlatform interface{} // 登录平台:1=PC
|
||||
LoginType interface{} // 登录方式:1=微信,2=手机号,3=账号密码,4=其他
|
||||
LoginStatus interface{} // 登录状态:1=成功,2=失败
|
||||
FailReason interface{} // 失败原因
|
||||
|
||||
@ -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:"软删除时间戳"` // 软删除时间戳
|
||||
|
||||
21
internal/model/entity/reward_types.go
Normal file
21
internal/model/entity/reward_types.go
Normal 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:"软删除时间"` // 软删除时间
|
||||
}
|
||||
@ -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 表示无限)
|
||||
|
||||
@ -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=Web,2=iOS,3=Android,4=微信小程序,5=支付宝小程序,6=其他"` // 登录平台:1=Web,2=iOS,3=Android,4=微信小程序,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
29
internal/model/user.go
Normal 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 {
|
||||
}
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user