feat(api): 接入微信登录并原生实现咨询域(ECR-049/050)
小程序可在 Go 上完成微信手机号登录、测评、预约和下单,不再反代 Java。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package javabridge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client talks to the Java consult stack (internal issue + logout).
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
InternalKey string
|
||||
HTTP *http.Client
|
||||
}
|
||||
|
||||
// Token is a Java Redis access token.
|
||||
type Token struct {
|
||||
AccessToken string
|
||||
MemberID int64
|
||||
}
|
||||
|
||||
type issueBody struct {
|
||||
OpenID string `json:"openid"`
|
||||
UnionID string `json:"unionId"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
type javaEnvelope struct {
|
||||
Success bool `json:"success"`
|
||||
Code any `json:"code"`
|
||||
Info string `json:"info"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
MemberID int64 `json:"memberId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// Enabled reports whether Java bridging is configured.
|
||||
func (c *Client) Enabled() bool {
|
||||
return c != nil && strings.TrimSpace(c.BaseURL) != "" && strings.TrimSpace(c.InternalKey) != ""
|
||||
}
|
||||
|
||||
// IssueMiniApp asks Java to register-or-find mini_app account and mint a Redis token.
|
||||
func (c *Client) IssueMiniApp(ctx context.Context, openid, unionID, phone string) (*Token, error) {
|
||||
if !c.Enabled() {
|
||||
return nil, errors.New("咨询后台未配置")
|
||||
}
|
||||
raw, _ := json.Marshal(issueBody{OpenID: openid, UnionID: unionID, Phone: phone})
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, strings.TrimRight(c.BaseURL, "/")+"/app-api/oauth/internal/issue-mini-app", bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Internal-Key", c.InternalKey)
|
||||
cli := c.http()
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.New("咨询账号同步失败")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
var env javaEnvelope
|
||||
if err := json.Unmarshal(body, &env); err != nil {
|
||||
return nil, errors.New("咨询账号同步失败")
|
||||
}
|
||||
codeOK := env.Success || fmt.Sprint(env.Code) == "0" || fmt.Sprint(env.Code) == "200"
|
||||
if resp.StatusCode >= 300 || (!codeOK && env.Data.AccessToken == "") {
|
||||
msg := env.Info
|
||||
if msg == "" {
|
||||
msg = env.Msg
|
||||
}
|
||||
if msg == "" {
|
||||
msg = "咨询账号同步失败"
|
||||
}
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
if env.Data.AccessToken == "" {
|
||||
return nil, errors.New("咨询账号同步失败")
|
||||
}
|
||||
return &Token{AccessToken: env.Data.AccessToken, MemberID: env.Data.MemberID}, nil
|
||||
}
|
||||
|
||||
// Logout revokes a Java access token (best effort).
|
||||
func (c *Client) Logout(ctx context.Context, accessToken string) {
|
||||
if !c.Enabled() || strings.TrimSpace(accessToken) == "" {
|
||||
return
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, strings.TrimRight(c.BaseURL, "/")+"/app-api/oauth/app/logout", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
resp, err := c.http().Do(req)
|
||||
if err == nil && resp != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) http() *http.Client {
|
||||
if c.HTTP != nil {
|
||||
return c.HTTP
|
||||
}
|
||||
return &http.Client{Timeout: 20 * time.Second}
|
||||
}
|
||||
|
||||
// HTTPDo relays an already-built request (consult proxy).
|
||||
func (c *Client) HTTPDo(req *http.Request) (*http.Response, error) {
|
||||
if c == nil {
|
||||
return nil, errors.New("咨询服务未配置")
|
||||
}
|
||||
return c.http().Do(req)
|
||||
}
|
||||
Reference in New Issue
Block a user