小程序可在 Go 上完成微信手机号登录、测评、预约和下单,不再反代 Java。 Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
3.2 KiB
Go
120 lines
3.2 KiB
Go
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)
|
|
}
|