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