From fdc9cc3463834ea12f116b39c19dd35662bc8ac1 Mon Sep 17 00:00:00 2001 From: denghui <1016848185@qq.com> Date: Tue, 3 Jun 2025 11:06:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=BE=AE=E4=BF=A1=E6=89=AB?= =?UTF-8?q?=E7=A0=81=E7=99=BB=E5=BD=95=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E6=8B=86=E5=88=86=E9=97=A8=E5=BA=97=E5=A5=96=E5=8A=B1?= =?UTF-8?q?=EF=BC=9A=E5=A5=96=E5=8A=B1=E7=B1=BB=E5=9E=8B=E3=80=81=E5=A5=96?= =?UTF-8?q?=E5=8A=B1=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/admin/v1/admin.go | 3 +- api/user/v1/user.go | 1 + api/wx/v1/wx.go | 22 ++-- go.mod | 8 +- go.sum | 19 +++- internal/cmd/cmd.go | 10 ++ internal/consts/consts.go | 12 ++ .../controller/admin/admin_v1_admin_info.go | 14 ++- .../controller/auth/auth_v1_admin_login.go | 18 ++- internal/controller/wx/wx_v1_we_chat_event.go | 88 ++++++++++++--- .../controller/wx/wx_v1_we_chat_polling.go | 52 ++++++++- .../controller/wx/wx_v1_we_chat_vertify.go | 35 +++--- internal/dao/internal/admins.go | 6 - internal/dao/internal/reward_types.go | 87 +++++++++++++++ internal/dao/internal/store_rewards.go | 40 +++---- internal/dao/internal/user_login_records.go | 6 +- internal/dao/reward_types.go | 27 +++++ internal/logic/admin/admin.go | 35 +++++- internal/logic/user/user.go | 103 +++++++++++++++++- internal/middleware/auth.go | 2 +- internal/middleware/casbin.go | 47 ++++---- internal/model/admin.go | 2 +- internal/model/common.go | 11 ++ internal/model/do/admins.go | 3 - internal/model/do/reward_types.go | 23 ++++ internal/model/do/store_rewards.go | 22 ++-- internal/model/do/user_login_records.go | 4 +- internal/model/entity/admins.go | 23 ++-- internal/model/entity/reward_types.go | 21 ++++ internal/model/entity/store_rewards.go | 20 ++-- internal/model/entity/user_login_records.go | 24 ++-- internal/model/user.go | 29 +++++ internal/packed/packed.go | 5 +- internal/service/user.go | 29 ++++- manifest/config/config.yaml | 6 +- utility/myCasbin/casbin.go | 5 + utility/wechat/wechat.go | 25 +++-- 37 files changed, 698 insertions(+), 189 deletions(-) create mode 100644 api/user/v1/user.go create mode 100644 internal/dao/internal/reward_types.go create mode 100644 internal/dao/reward_types.go create mode 100644 internal/model/do/reward_types.go create mode 100644 internal/model/entity/reward_types.go create mode 100644 internal/model/user.go diff --git a/api/admin/v1/admin.go b/api/admin/v1/admin.go index d54677b..cb340ee 100644 --- a/api/admin/v1/admin.go +++ b/api/admin/v1/admin.go @@ -6,5 +6,6 @@ type AdminInfoReq struct { g.Meta `path:"/admin/info" method:"get" tags:"Admin" summary:"获取管理员信息"` } type AdminInfoRes struct { - g.Meta `mime:"application/json"` + g.Meta `mime:"application/json"` + Username string `json:"username" dc:"用户名"` } diff --git a/api/user/v1/user.go b/api/user/v1/user.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/api/user/v1/user.go @@ -0,0 +1 @@ +package user diff --git a/api/wx/v1/wx.go b/api/wx/v1/wx.go index 3fd086b..4c0557a 100644 --- a/api/wx/v1/wx.go +++ b/api/wx/v1/wx.go @@ -3,21 +3,21 @@ package v1 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"` + g.Meta `path:"/wechat/login" method:"post" tags:"WeChat" summary:"获取微信二维码登录"` + 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 { @@ -35,8 +35,10 @@ type WeChatVertifyRes struct { } type WeChatPollingReq struct { - g.Meta `path:"/wechat/polling" method:"post" tags:"WeChat" summary:"微信长轮询"` - UUID string `json:"uuid" v:"required" dc:"UUID"` + g.Meta `path:"/wechat/polling" method:"post" tags:"WeChat" summary:"微信长轮询"` + SceneId string `json:"sceneId" v:"required" dc:"场景ID"` } type WeChatPollingRes struct { + Status string `json:"status" dc:"状态"` + Token string `json:"token" dc:"token"` } diff --git a/go.mod b/go.mod index 6ed0316..ab5d5ca 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 6f8629c..11d8cac 100644 --- a/go.sum +++ b/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= diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index c78fe23..3b660ea 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -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 diff --git a/internal/consts/consts.go b/internal/consts/consts.go index d709a2b..ab86fed 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -1 +1,13 @@ package consts + +const ( + GuestPermission = "guest" + UserPermission = "user" + AdminPermission = "admin" + MerchantPermission = "merchant" + StorePermission = "store" +) +const ( + QRCodeExpireTime = 60 + QRCodeLimitTime = 10 +) diff --git a/internal/controller/admin/admin_v1_admin_info.go b/internal/controller/admin/admin_v1_admin_info.go index ce89e44..8ca6ff5 100644 --- a/internal/controller/admin/admin_v1_admin_info.go +++ b/internal/controller/admin/admin_v1_admin_info.go @@ -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 } diff --git a/internal/controller/auth/auth_v1_admin_login.go b/internal/controller/auth/auth_v1_admin_login.go index 63bcf13..757cf51 100644 --- a/internal/controller/auth/auth_v1_admin_login.go +++ b/internal/controller/auth/auth_v1_admin_login.go @@ -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 } diff --git a/internal/controller/wx/wx_v1_we_chat_event.go b/internal/controller/wx/wx_v1_we_chat_event.go index ddf70c1..a2b2fa5 100644 --- a/internal/controller/wx/wx_v1_we_chat_event.go +++ b/internal/controller/wx/wx_v1_we_chat_event.go @@ -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 + 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": - // 已关注,扫描后,根据 open_id 查找用户生成 token + 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 } - 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 } diff --git a/internal/controller/wx/wx_v1_we_chat_polling.go b/internal/controller/wx/wx_v1_we_chat_polling.go index c031f96..fdc87df 100644 --- a/internal/controller/wx/wx_v1_we_chat_polling.go +++ b/internal/controller/wx/wx_v1_we_chat_polling.go @@ -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("未知登录状态") + } } diff --git a/internal/controller/wx/wx_v1_we_chat_vertify.go b/internal/controller/wx/wx_v1_we_chat_vertify.go index 067f99c..b75b138 100644 --- a/internal/controller/wx/wx_v1_we_chat_vertify.go +++ b/internal/controller/wx/wx_v1_we_chat_vertify.go @@ -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 } diff --git a/internal/dao/internal/admins.go b/internal/dao/internal/admins.go index 3214897..290e504 100644 --- a/internal/dao/internal/admins.go +++ b/internal/dao/internal/admins.go @@ -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", diff --git a/internal/dao/internal/reward_types.go b/internal/dao/internal/reward_types.go new file mode 100644 index 0000000..ee07106 --- /dev/null +++ b/internal/dao/internal/reward_types.go @@ -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) +} diff --git a/internal/dao/internal/store_rewards.go b/internal/dao/internal/store_rewards.go index 9e0590a..d73c126 100644 --- a/internal/dao/internal/store_rewards.go +++ b/internal/dao/internal/store_rewards.go @@ -20,30 +20,30 @@ type StoreRewardsDao struct { // StoreRewardsColumns defines and stores column names for the table store_rewards. type StoreRewardsColumns struct { - Id string // 门店奖励ID - StoreId string // 所属门店ID - RewardType string // 奖励类型:1=积分,2=优惠券,3=商品,4=抽奖券 - RewardName string // 奖励名称 - Amount string // 奖励数量 - Total string // 该奖励总库存(NULL 表示无限) - MerchantId string // 所属商户ID - CreatedAt string // 创建时间 - UpdatedAt string // 更新时间 - DeletedAt string // 软删除时间戳 + Id string // 门店奖励ID + StoreId string // 所属门店ID + RewardTypeId string // 奖励类型ID + RewardName string // 奖励名称 + Amount string // 奖励数量 + Total string // 该奖励总库存(NULL 表示无限) + MerchantId string // 所属商户ID + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 软删除时间戳 } // storeRewardsColumns holds the columns for the table store_rewards. var storeRewardsColumns = StoreRewardsColumns{ - Id: "id", - StoreId: "store_id", - RewardType: "reward_type", - RewardName: "reward_name", - Amount: "amount", - Total: "total", - MerchantId: "merchant_id", - CreatedAt: "created_at", - UpdatedAt: "updated_at", - DeletedAt: "deleted_at", + Id: "id", + StoreId: "store_id", + RewardTypeId: "reward_type_id", + RewardName: "reward_name", + Amount: "amount", + Total: "total", + MerchantId: "merchant_id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", } // NewStoreRewardsDao creates and returns a new DAO object for table data access. diff --git a/internal/dao/internal/user_login_records.go b/internal/dao/internal/user_login_records.go index 37d4703..9fc2e0b 100644 --- a/internal/dao/internal/user_login_records.go +++ b/internal/dao/internal/user_login_records.go @@ -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", diff --git a/internal/dao/reward_types.go b/internal/dao/reward_types.go new file mode 100644 index 0000000..02a0bab --- /dev/null +++ b/internal/dao/reward_types.go @@ -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. diff --git a/internal/logic/admin/admin.go b/internal/logic/admin/admin.go index 893d1a1..3e2be3f 100644 --- a/internal/logic/admin/admin.go +++ b/internal/logic/admin/admin.go @@ -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 } diff --git a/internal/logic/user/user.go b/internal/logic/user/user.go index a00006b..371985d 100644 --- a/internal/logic/user/user.go +++ b/internal/logic/user/user.go @@ -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 +} diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 0b721a0..2c30da6 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -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) diff --git a/internal/middleware/casbin.go b/internal/middleware/casbin.go index 4e610f9..6cd3f56 100644 --- a/internal/middleware/casbin.go +++ b/internal/middleware/casbin.go @@ -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() +} diff --git a/internal/model/admin.go b/internal/model/admin.go index f9a2252..2e1b7a1 100644 --- a/internal/model/admin.go +++ b/internal/model/admin.go @@ -10,6 +10,6 @@ type ( Id int } AdminInfoOut struct { - Token string + Username string } ) diff --git a/internal/model/common.go b/internal/model/common.go index ee98759..417a80a 100644 --- a/internal/model/common.go +++ b/internal/model/common.go @@ -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 +} diff --git a/internal/model/do/admins.go b/internal/model/do/admins.go index 611c3f9..339245d 100644 --- a/internal/model/do/admins.go +++ b/internal/model/do/admins.go @@ -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 // 软删除时间戳 diff --git a/internal/model/do/reward_types.go b/internal/model/do/reward_types.go new file mode 100644 index 0000000..fde8748 --- /dev/null +++ b/internal/model/do/reward_types.go @@ -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 // 软删除时间 +} diff --git a/internal/model/do/store_rewards.go b/internal/model/do/store_rewards.go index 5ac440a..54fb7f3 100644 --- a/internal/model/do/store_rewards.go +++ b/internal/model/do/store_rewards.go @@ -11,15 +11,15 @@ import ( // StoreRewards is the golang structure of table store_rewards for DAO operations like Where/Data. type StoreRewards struct { - g.Meta `orm:"table:store_rewards, do:true"` - Id interface{} // 门店奖励ID - StoreId interface{} // 所属门店ID - RewardType interface{} // 奖励类型:1=积分,2=优惠券,3=商品,4=抽奖券 - RewardName interface{} // 奖励名称 - Amount interface{} // 奖励数量 - Total interface{} // 该奖励总库存(NULL 表示无限) - MerchantId interface{} // 所属商户ID - CreatedAt *gtime.Time // 创建时间 - UpdatedAt *gtime.Time // 更新时间 - DeletedAt *gtime.Time // 软删除时间戳 + g.Meta `orm:"table:store_rewards, do:true"` + Id interface{} // 门店奖励ID + StoreId interface{} // 所属门店ID + RewardTypeId interface{} // 奖励类型ID + RewardName interface{} // 奖励名称 + Amount interface{} // 奖励数量 + Total interface{} // 该奖励总库存(NULL 表示无限) + MerchantId interface{} // 所属商户ID + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + DeletedAt *gtime.Time // 软删除时间戳 } diff --git a/internal/model/do/user_login_records.go b/internal/model/do/user_login_records.go index 42cae49..aa84fae 100644 --- a/internal/model/do/user_login_records.go +++ b/internal/model/do/user_login_records.go @@ -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{} // 失败原因 diff --git a/internal/model/entity/admins.go b/internal/model/entity/admins.go index 9097126..6b638b1 100644 --- a/internal/model/entity/admins.go +++ b/internal/model/entity/admins.go @@ -10,17 +10,14 @@ import ( // Admins is the golang structure for table admins. type Admins struct { - Id int64 `json:"id" orm:"id" description:"管理员ID"` // 管理员ID - Username string `json:"username" orm:"username" description:"管理员用户名"` // 管理员用户名 - PasswordHash string `json:"passwordHash" orm:"password_hash" description:"密码哈希"` // 密码哈希 - 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:"软删除时间戳"` // 软删除时间戳 + Id int64 `json:"id" orm:"id" description:"管理员ID"` // 管理员ID + Username string `json:"username" orm:"username" description:"管理员用户名"` // 管理员用户名 + PasswordHash string `json:"passwordHash" orm:"password_hash" description:"密码哈希"` // 密码哈希 + RealName string `json:"realName" orm:"real_name" description:"真实姓名"` // 真实姓名 + Phone string `json:"phone" orm:"phone" description:"手机号"` // 手机号 + Email string `json:"email" orm:"email" 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:"软删除时间戳"` // 软删除时间戳 } diff --git a/internal/model/entity/reward_types.go b/internal/model/entity/reward_types.go new file mode 100644 index 0000000..62465e8 --- /dev/null +++ b/internal/model/entity/reward_types.go @@ -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:"软删除时间"` // 软删除时间 +} diff --git a/internal/model/entity/store_rewards.go b/internal/model/entity/store_rewards.go index 4ebef8c..24bb49d 100644 --- a/internal/model/entity/store_rewards.go +++ b/internal/model/entity/store_rewards.go @@ -10,14 +10,14 @@ import ( // StoreRewards is the golang structure for table store_rewards. 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=抽奖券 - RewardName string `json:"rewardName" orm:"reward_name" description:"奖励名称"` // 奖励名称 - Amount int `json:"amount" orm:"amount" description:"奖励数量"` // 奖励数量 - Total int `json:"total" orm:"total" description:"该奖励总库存(NULL 表示无限)"` // 该奖励总库存(NULL 表示无限) - MerchantId int64 `json:"merchantId" orm:"merchant_id" description:"所属商户ID"` // 所属商户ID - 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:"软删除时间戳"` // 软删除时间戳 + Id int64 `json:"id" orm:"id" description:"门店奖励ID"` // 门店奖励ID + StoreId int64 `json:"storeId" orm:"store_id" description:"所属门店ID"` // 所属门店ID + 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 表示无限) + MerchantId int64 `json:"merchantId" orm:"merchant_id" description:"所属商户ID"` // 所属商户ID + 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:"软删除时间戳"` // 软删除时间戳 } diff --git a/internal/model/entity/user_login_records.go b/internal/model/entity/user_login_records.go index 4c619fa..54da8d7 100644 --- a/internal/model/entity/user_login_records.go +++ b/internal/model/entity/user_login_records.go @@ -10,17 +10,15 @@ import ( // UserLoginRecords is the golang structure for table user_login_records. 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=其他 - 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:"失败原因"` // 失败原因 - 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:"软删除时间戳"` // 软删除时间戳 + 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 + LoginIp string `json:"loginIp" orm:"login_ip" description:"登录IP地址"` // 登录IP地址 + 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:"失败原因"` // 失败原因 + 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:"软删除时间戳"` // 软删除时间戳 } diff --git a/internal/model/user.go b/internal/model/user.go new file mode 100644 index 0000000..1f43a43 --- /dev/null +++ b/internal/model/user.go @@ -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 { +} diff --git a/internal/packed/packed.go b/internal/packed/packed.go index 20ec0b9..a3e727f 100644 --- a/internal/packed/packed.go +++ b/internal/packed/packed.go @@ -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" ) diff --git a/internal/service/user.go b/internal/service/user.go index 3d70438..2433601 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -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 +} diff --git a/manifest/config/config.yaml b/manifest/config/config.yaml index 768cc3f..572fa37 100644 --- a/manifest/config/config.yaml +++ b/manifest/config/config.yaml @@ -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" \ No newline at end of file + token: "arenax" \ No newline at end of file diff --git a/utility/myCasbin/casbin.go b/utility/myCasbin/casbin.go index 4e47ef2..a365eaf 100644 --- a/utility/myCasbin/casbin.go +++ b/utility/myCasbin/casbin.go @@ -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") } diff --git a/utility/wechat/wechat.go b/utility/wechat/wechat.go index 4c703e8..28e1509 100644 --- a/utility/wechat/wechat.go +++ b/utility/wechat/wechat.go @@ -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 +}