feat: analysis user auth and ws

This commit is contained in:
zzw
2026-10-15 09:23:07 +08:00
parent 6fa185003d
commit a65b6d2803
102 changed files with 4966 additions and 786 deletions
+1 -2
View File
@@ -43,8 +43,7 @@ type Claims struct {
Birthday string `json:"birthday"`
Type uint8 `json:"type"` // 1销售 2门店 3普通用户
ClientCode string `json:"client_code"`
SaleId int64 `json:"sale_id,omitempty"`
StoreId int64 `json:"store_id,omitempty"`
BizId int64 `json:"biz_id,omitempty"`
TokenType string `json:"token_type"` // access | refresh
jwt.RegisteredClaims
}
+2
View File
@@ -29,6 +29,7 @@ type UserInfo struct {
GroupName string // 分组名称
Type uint8 // 用户类型
ClientCode string
BizId int64
Valid bool
}
@@ -72,6 +73,7 @@ func GetUserFromCtx(ctx context.Context) UserInfo {
GroupId: parseInt64MD(md, "x-group-id"),
GroupName: firstMD(md, "x-group-name"),
ClientCode: firstMD(md, "x-client-code"),
BizId: parseInt64MD(md, "x-biz-id"),
}
if typeRaw := firstMD(md, "x-user-type"); typeRaw != StringEmpty {
+3
View File
@@ -129,6 +129,9 @@ var (
// 参数类
ErrorReasonParams = Status{Code: 10012, Msg: "原因不能为空"}
ErrorMobileError = Status{Code: 10013, Msg: "手机号格式错误"}
ErrorQRcodeExp = Status{Code: 10015, Msg: "二维码已过期"}
ErrorNotFace = Status{Code: 10016, Msg: "未检测到人脸"}
ErrorFileUpFailed = Status{Code: 10017, Msg: "文件上传失败"}
// ErrorNotFund 数据类
ErrorFormatDataError = Status{Code: 11014, Msg: "格式化数据出错"}
+17 -7
View File
@@ -73,15 +73,12 @@ func (ct CustomTime) MarshalJSON() ([]byte, error) {
// UnmarshalJSON 实现 JSON 反序列化
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
str := string(data)
if str == "null" || str == `""` {
str := strings.Trim(string(data), `"`)
if str == "" || str == "null" || isZeroDate(str) {
ct.Time = time.Time{}
return nil
}
// 去除引号
str = strings.Trim(str, `"`)
// 尝试解析多种格式
formats := []string{
"2006-01-02",
@@ -122,14 +119,27 @@ func (ct *CustomTime) Scan(value interface{}) error {
ct.Time = v
return nil
case []byte:
return ct.UnmarshalJSON(v)
return ct.scanString(string(v))
case string:
return ct.UnmarshalJSON([]byte(v))
return ct.scanString(v)
default:
return fmt.Errorf("unsupported type: %T", value)
}
}
func (ct *CustomTime) scanString(s string) error {
s = strings.TrimSpace(s)
if s == "" || isZeroDate(s) {
ct.Time = time.Time{}
return nil
}
return ct.UnmarshalJSON([]byte(`"` + s + `"`))
}
func isZeroDate(s string) bool {
return s == "0000-00-00" || s == "0000-00-00 00:00:00"
}
// GormDataType 定义 GORM 数据库类型
func (CustomTime) GormDataType() string {
return "datetime"
+5 -5
View File
@@ -1,13 +1,13 @@
package qrcode
import "lone-services/pkg/wechat/auth"
type Client struct {
AppID string
Secret string
Auth *auth.Client
}
func NewClient(appID, secret string) *Client {
func NewClient(authCli *auth.Client) *Client {
return &Client{
AppID: appID,
Secret: secret,
Auth: authCli,
}
}
+1 -2
View File
@@ -17,10 +17,9 @@ type UnlimitedRequest struct {
IsHyaline bool `json:"is_hyaline,omitempty"`
}
// WxaCodeRequest 有数量限制的小程序码(getwxacode),path 可带 query。
type WxaCodeRequest struct {
Path string `json:"path"`
EnvVersion string `json:"env_version,omitempty"`
EnvVersion string `json:"env_version,omitempty"` // release / trial / develop
Width int `json:"width,omitempty"`
AutoColor bool `json:"auto_color,omitempty"`
LineColor *LineColor `json:"line_color,omitempty"`
+4 -2
View File
@@ -13,7 +13,6 @@ import (
"time"
"lone-services/pkg/utils"
"lone-services/pkg/wechat/auth"
)
const unlimitedURL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit"
@@ -54,7 +53,10 @@ func (c *Client) GetUnlimited(accessToken string, req *UnlimitedRequest) ([]byte
}
func (c *Client) CreateUnlimited(ctx context.Context, req *UnlimitedRequest) ([]byte, error) {
token, err := auth.NewClient(c.AppID, c.Secret).AccessToken(ctx)
if c == nil || c.Auth == nil {
return nil, fmt.Errorf("wechat qrcode auth client nil")
}
token, err := c.Auth.AccessToken(ctx)
if err != nil {
return nil, err
}
+4 -6
View File
@@ -11,13 +11,10 @@ import (
"time"
"lone-services/pkg/utils"
"lone-services/pkg/wechat/auth"
)
// 小程序码(有数量限制):https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code/getQRCode.html
const wxaCodeURL = "https://api.weixin.qq.com/wxa/getwxacode"
// BuildPath 拼小程序 path,如 pagesB/userLogin/index?session_id=xxx
func BuildPath(page string, params map[string]string) string {
if len(params) == 0 {
return page
@@ -29,7 +26,6 @@ func BuildPath(page string, params map[string]string) string {
return page + "?" + query.Encode()
}
// GetWxaCode 使用已有 access_token 生成有限小程序码,成功返回图片二进制。
func (c *Client) GetWxaCode(accessToken string, req *WxaCodeRequest) ([]byte, error) {
if accessToken == utils.StringEmpty {
return nil, fmt.Errorf("wechat access_token empty")
@@ -65,9 +61,11 @@ func (c *Client) GetWxaCode(accessToken string, req *WxaCodeRequest) ([]byte, er
return body, nil
}
// CreateWxaCode 通过 auth 拉取 access_token 后生成有限小程序码。
func (c *Client) CreateWxaCode(ctx context.Context, req *WxaCodeRequest) ([]byte, error) {
token, err := auth.NewClient(c.AppID, c.Secret).AccessToken(ctx)
if c == nil || c.Auth == nil {
return nil, fmt.Errorf("wechat qrcode auth client nil")
}
token, err := c.Auth.AccessToken(ctx)
if err != nil {
return nil, err
}
+90
View File
@@ -0,0 +1,90 @@
package ws
import (
"context"
"fmt"
"lone-services/pkg/redis"
jsoniter "github.com/json-iterator/go"
)
func Publish(ctx context.Context, sessionID string, resp Response) error {
if redis.Client == nil {
return fmt.Errorf("ws: redis not initialized")
}
if sessionID == "" {
return fmt.Errorf("ws: session_id empty")
}
var dataRaw []byte
if resp.Data != nil {
b, err := jsoniter.Marshal(resp.Data)
if err != nil {
return err
}
dataRaw = b
}
evt := Event{
SessionID: sessionID,
Code: resp.Code,
Message: resp.Message,
Data: dataRaw,
}
payload, err := jsoniter.Marshal(evt)
if err != nil {
return err
}
return redis.Client.Publish(ctx, RedisChannelQRPush, payload).Err()
}
func UpdateSessionAndPublish(ctx context.Context, sessionID string, status int, data interface{}) error {
if redis.Client == nil {
return fmt.Errorf("ws: redis not initialized")
}
if sessionID == "" {
return fmt.Errorf("ws: session_id empty")
}
sess := Session{Status: status}
if old, err := GetSession(ctx, sessionID); err == nil && old != nil {
sess.ClientCode = old.ClientCode
}
if data != nil {
b, err := jsoniter.Marshal(data)
if err != nil {
return err
}
sess.Data = b
}
raw, err := jsoniter.Marshal(sess)
if err != nil {
return err
}
key := RedisKeyQRSession + sessionID
if err := redis.Client.Set(ctx, key, raw, SessionTTL).Err(); err != nil {
return err
}
return Publish(ctx, sessionID, Success(data))
}
func GetSession(ctx context.Context, sessionID string) (*Session, error) {
if redis.Client == nil {
return nil, fmt.Errorf("ws: redis not initialized")
}
if sessionID == "" {
return nil, fmt.Errorf("ws: session_id empty")
}
val, err := redis.Client.Get(ctx, RedisKeyQRSession+sessionID).Result()
if err != nil {
return nil, err
}
var sess Session
if err := jsoniter.UnmarshalFromString(val, &sess); err != nil {
return nil, err
}
return &sess, nil
}
+52
View File
@@ -0,0 +1,52 @@
package ws
import (
"encoding/json"
"time"
)
const (
RedisKeyQRSession = "qr:session:"
RedisChannelQRPush = "ws:qr:push"
StatusPending = 1 // 待扫码
StatusScanned = 2 // 已扫码未登录
StatusLogined = 3 // 已扫码已登录
WsTypeLogin = 1 // 同意登录
WsTypeScanned = 2 // 拒绝授权 / 拒绝登录
WsTypeScan = 3 // 进入扫码页通知
SessionTTL = 5 * time.Minute
CodeOK = 200
MsgOK = "success"
CodeErr = 500
)
type Session struct {
Status int `json:"status"`
ClientCode string `json:"client_code,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
type Event struct {
SessionID string `json:"session_id"`
Code int `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data"`
}
func Success(data interface{}) Response {
return Response{Code: CodeOK, Message: MsgOK, Data: data}
}
func Fail(msg string) Response {
return Response{Code: CodeErr, Message: msg, Data: nil}
}