feat: analysis user auth and ws
This commit is contained in:
+1
-2
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
Reference in New Issue
Block a user