Files
digital-psychology/apps/api/internal/javabridge/client.go
T
jackyu66gitandCursor 7155b8b53a feat(api): 接入微信登录并原生实现咨询域(ECR-049/050)
小程序可在 Go 上完成微信手机号登录、测评、预约和下单,不再反代 Java。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 00:26:29 +08:00

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)
}