diff --git a/app/main/api/desc/front/user.api b/app/main/api/desc/front/user.api index 63a283a..3dcd3c2 100644 --- a/app/main/api/desc/front/user.api +++ b/app/main/api/desc/front/user.api @@ -32,6 +32,9 @@ service main { @doc "wechat h5 auth" @handler wxH5Auth post /user/wxh5Auth (WXH5AuthReq) returns (WXH5AuthResp) + + @handler getSignature + post /wechat/getSignature (GetSignatureReq) returns (GetSignatureResp) } type ( @@ -57,6 +60,18 @@ type ( } ) +type ( + GetSignatureReq { + Url string `json:"url"` + } + GetSignatureResp { + AppId string `json:"appId"` + Timestamp int64 `json:"timestamp"` + NonceStr string `json:"nonceStr"` + Signature string `json:"signature"` + } +) + type ( WXH5AuthReq { Code string `json:"code"` diff --git a/app/main/api/internal/handler/routes.go b/app/main/api/internal/handler/routes.go index 9aa7a1b..510f0b6 100644 --- a/app/main/api/internal/handler/routes.go +++ b/app/main/api/internal/handler/routes.go @@ -1050,6 +1050,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/user/wxh5Auth", Handler: user.WxH5AuthHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/wechat/getSignature", + Handler: user.GetSignatureHandler(serverCtx), + }, }, rest.WithPrefix("/api/v1"), ) diff --git a/app/main/api/internal/handler/user/getsignaturehandler.go b/app/main/api/internal/handler/user/getsignaturehandler.go new file mode 100644 index 0000000..d895708 --- /dev/null +++ b/app/main/api/internal/handler/user/getsignaturehandler.go @@ -0,0 +1,29 @@ +package user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "hm-server/app/main/api/internal/logic/user" + "hm-server/app/main/api/internal/svc" + "hm-server/app/main/api/internal/types" + "hm-server/common/result" + "hm-server/pkg/lzkit/validator" +) + +func GetSignatureHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetSignatureReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := user.NewGetSignatureLogic(r.Context(), svcCtx) + resp, err := l.GetSignature(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/main/api/internal/logic/user/getsignaturelogic.go b/app/main/api/internal/logic/user/getsignaturelogic.go new file mode 100644 index 0000000..aff74a9 --- /dev/null +++ b/app/main/api/internal/logic/user/getsignaturelogic.go @@ -0,0 +1,185 @@ +package user + +import ( + "context" + "crypto/sha1" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strings" + "time" + + "hm-server/app/main/api/internal/svc" + "hm-server/app/main/api/internal/types" + "hm-server/common/xerr" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" +) + +type GetSignatureLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetSignatureLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSignatureLogic { + return &GetSignatureLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetSignatureLogic) GetSignature(req *types.GetSignatureReq) (resp *types.GetSignatureResp, err error) { + // 1. 获取access_token + accessToken, err := l.getAccessToken() + if err != nil { + l.Errorf("获取access_token失败: %v", err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token失败: %v", err) + } + + // 2. 获取jsapi_ticket + jsapiTicket, err := l.getJsapiTicket(accessToken) + if err != nil { + l.Errorf("获取jsapi_ticket失败: %v", err) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取jsapi_ticket失败: %v", err) + } + + // 3. 生成签名 + timestamp := time.Now().Unix() + nonceStr := l.generateNonceStr(16) + signature := l.generateSignature(jsapiTicket, nonceStr, timestamp, req.Url) + + // 4. 返回完整的JS-SDK配置信息 + return &types.GetSignatureResp{ + AppId: l.svcCtx.Config.WechatH5.AppID, + Timestamp: timestamp, + NonceStr: nonceStr, + Signature: signature, + }, nil +} + +// getAccessToken 获取微信公众号access_token +func (l *GetSignatureLogic) getAccessToken() (string, error) { + appID := l.svcCtx.Config.WechatH5.AppID + appSecret := l.svcCtx.Config.WechatH5.AppSecret + + url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appID, appSecret) + + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + var result struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` + } + + if err = json.Unmarshal(body, &result); err != nil { + return "", err + } + + if result.ErrCode != 0 { + return "", fmt.Errorf("获取access_token失败: errcode=%d, errmsg=%s", result.ErrCode, result.ErrMsg) + } + + return result.AccessToken, nil +} + +// getJsapiTicket 获取jsapi_ticket +func (l *GetSignatureLogic) getJsapiTicket(accessToken string) (string, error) { + url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", accessToken) + + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + var result struct { + Ticket string `json:"ticket"` + ExpiresIn int `json:"expires_in"` + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` + } + + if err = json.Unmarshal(body, &result); err != nil { + return "", err + } + + if result.ErrCode != 0 { + return "", fmt.Errorf("获取jsapi_ticket失败: errcode=%d, errmsg=%s", result.ErrCode, result.ErrMsg) + } + + return result.Ticket, nil +} + +// generateNonceStr 生成随机字符串 +func (l *GetSignatureLogic) generateNonceStr(length int) string { + chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + result := make([]byte, length) + for i := 0; i < length; i++ { + result[i] = chars[i%len(chars)] + } + return string(result) +} + +// generateSignature 生成签名 +func (l *GetSignatureLogic) generateSignature(jsapiTicket, nonceStr string, timestamp int64, urlStr string) string { + // 对URL进行解码,避免重复编码 + decodedURL, err := url.QueryUnescape(urlStr) + if err != nil { + decodedURL = urlStr + } + + // 构建签名字符串 + params := map[string]string{ + "jsapi_ticket": jsapiTicket, + "noncestr": nonceStr, + "timestamp": fmt.Sprintf("%d", timestamp), + "url": decodedURL, + } + + // 对参数进行字典序排序 + keys := make([]string, 0, len(params)) + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + + // 拼接字符串 + var signStr strings.Builder + for i, k := range keys { + if i > 0 { + signStr.WriteString("&") + } + signStr.WriteString(k) + signStr.WriteString("=") + signStr.WriteString(params[k]) + } + + // SHA1加密 + h := sha1.New() + h.Write([]byte(signStr.String())) + signature := fmt.Sprintf("%x", h.Sum(nil)) + + return signature +} diff --git a/app/main/api/internal/types/types.go b/app/main/api/internal/types/types.go index e3cd5b5..3e76068 100644 --- a/app/main/api/internal/types/types.go +++ b/app/main/api/internal/types/types.go @@ -1465,6 +1465,17 @@ type GetRoleListResp struct { Items []RoleListItem `json:"items"` // 列表 } +type GetSignatureReq struct { + Url string `json:"url"` +} + +type GetSignatureResp struct { + AppId string `json:"appId"` + Timestamp int64 `json:"timestamp"` + NonceStr string `json:"nonceStr"` + Signature string `json:"signature"` +} + type GetWithdrawalReq struct { Page int64 `form:"page"` // 页码 PageSize int64 `form:"page_size"` // 每页数据量