docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
This commit is contained in:
1 parent
91e8d7a029
commit
074ffc4ebf
73 files changed
+2529
-2529
No files matched your search
@@ -1,14 +1,14 @@
|
||||
package gateway
|
||||
|
||||
// This file adds the weixin-iLink scan-login admin endpoints (design/08 §4),
|
||||
// owner only and audited. The panel triggers a QR bind (login-qr), then polls the
|
||||
// bind status (login-status); on confirmation the handler persists the newly
|
||||
// obtained bot_token / bot_id into channels.json and hot-reloads the platform so
|
||||
// the bridge connects the now-configured adapter — no restart required.
|
||||
// 本文件新增 weixin-iLink 扫码登录管理端点(design/08 §4),
|
||||
// 仅 owner 且带审计。面板先触发二维码绑定(login-qr),再轮询
|
||||
// 绑定状态(login-status);确认后 handler 把新获得的
|
||||
// bot_token / bot_id 持久化进 channels.json 并热重载该平台,
|
||||
// 使 bridge 连接到这个刚配置好的适配器 —— 无需重启。
|
||||
//
|
||||
// The adapter owns the token: it is never logged, and it is returned to the panel
|
||||
// only masked (**** + trailing 4). Identity stays on the adapter's actor metadata,
|
||||
// never a prompt.
|
||||
// token 归适配器所有:绝不记录日志,且返回给面板时
|
||||
// 只做脱敏(**** + 末 4 位)。身份始终落在适配器的 actor 元数据上,
|
||||
// 绝不是提示词。
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -28,26 +28,26 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/plugins/channels/weixin_ilink"
|
||||
)
|
||||
|
||||
// weixinLoginAdmin is the subset of the weixin-ilink adapter's scan-login surface
|
||||
// the gateway panel endpoints invoke. It is implemented by *weixin_ilink.Adapter
|
||||
// and reached by unwrapping the registration wrapper (see asWeixinLoginAdmin).
|
||||
// weixinLoginAdmin 是网关面板端点所调用的 weixin-ilink 适配器扫码
|
||||
// 登录表面的子集。它由 *weixin_ilink.Adapter 实现,
|
||||
// 通过拆开注册包装器抵达(见 asWeixinLoginAdmin)。
|
||||
type weixinLoginAdmin interface {
|
||||
StartLogin(ctx context.Context) (weixin_ilink.LoginQR, error)
|
||||
LoginStatus(ctx context.Context) (status, detail string)
|
||||
Config() map[string]any
|
||||
}
|
||||
|
||||
// weixinLoginQRResponse is the wire shape of GET /admin/channels/{platform}/login-qr.
|
||||
// qrUrl is the WeChat QR image URL (qrcode_img_content) and qrCode is the raw scan
|
||||
// identifier (qrcode); the frontend renders a scannable QR from qrUrl, since the
|
||||
// liteapp URL is an HTML page that cannot be fetched as an image.
|
||||
// weixinLoginQRResponse 是 GET /admin/channels/{platform}/login-qr 的线上
|
||||
// 结构。qrUrl 是微信二维码图片 URL(qrcode_img_content),qrCode 是原始扫码
|
||||
// 标识(qrcode);前端从 qrUrl 渲染出可扫的二维码,因为
|
||||
// liteapp URL 是一个 HTML 页面,无法当作图片拉取。
|
||||
type weixinLoginQRResponse struct {
|
||||
QrUrl string `json:"qrUrl,omitempty"`
|
||||
QrCode string `json:"qrCode,omitempty"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// weixinLoginStatusResponse is the wire shape of GET /admin/channels/{platform}/login-status.
|
||||
// weixinLoginStatusResponse 是 GET /admin/channels/{platform}/login-status 的线上结构。
|
||||
type weixinLoginStatusResponse struct {
|
||||
Status string `json:"status"`
|
||||
BotID string `json:"botId,omitempty"`
|
||||
@@ -55,9 +55,9 @@ type weixinLoginStatusResponse struct {
|
||||
Err string `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
// adminWeixinLoginQR triggers (or returns the in-progress) QR code for a
|
||||
// weixin-ilink account. It is the panel's "扫码登录" entry point; it never blocks
|
||||
// on the user's scan — LoginStatus is polled separately.
|
||||
// adminWeixinLoginQR 为 weixin-ilink 账号触发(或返回进行中的)二维码。
|
||||
// 它是面板的「扫码登录」入口;绝不会阻塞于用户的
|
||||
// 扫码 —— LoginStatus 由另行轮询获得。
|
||||
func (s *Server) adminWeixinLoginQR(w http.ResponseWriter, r *http.Request) {
|
||||
platform := r.PathValue("platform")
|
||||
accountID := r.URL.Query().Get("accountId")
|
||||
@@ -86,9 +86,9 @@ func (s *Server) adminWeixinLoginQR(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// adminWeixinLoginStatus reports the scan-login state. On confirmation it also
|
||||
// persists the freshly obtained token to channels.json and hot-reloads the
|
||||
// platform, so the bridge connects the now-configured adapter.
|
||||
// adminWeixinLoginStatus 报告扫码登录状态。确认时它还会
|
||||
// 把新获得的 token 持久化进 channels.json 并热重载该平台,
|
||||
// 使 bridge 连接到这个刚配置好的适配器。
|
||||
func (s *Server) adminWeixinLoginStatus(w http.ResponseWriter, r *http.Request) {
|
||||
platform := r.PathValue("platform")
|
||||
accountID := r.URL.Query().Get("accountId")
|
||||
@@ -119,15 +119,15 @@ func (s *Server) adminWeixinLoginStatus(w http.ResponseWriter, r *http.Request)
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// persistWeixinConfirmed writes the login-obtained credentials into the account's
|
||||
// channels.json config (merging onto the user's curated base_url/cdn fields and
|
||||
// preserving its allow/deny/limit lists) and hot-reloads the platform so the
|
||||
// bridge connects the now-configured adapter. When ownerID is non-empty it also
|
||||
// registers the scanner's bound ilink_user_id into the owner whitelist
|
||||
// (owner.yaml) so the bridge recognizes it as the owner without a manual edit.
|
||||
// The whitelist registration is best-effort: a failure is logged but does not
|
||||
// fail the already-persisted channels.json, since the identity red line never
|
||||
// blocks a successful bind.
|
||||
// persistWeixinConfirmed 把登录获得的凭据写入该账号的
|
||||
// channels.json 配置(合并到用户精心维护的 base_url/cdn 字段之上,
|
||||
// 并保留其 allow/deny/limit 列表),再热重载该平台,
|
||||
// 使 bridge 连接到这个刚配置好的适配器。ownerID 非空时它还会
|
||||
// 把扫码者绑定的 ilink_user_id 登记进主人白名单
|
||||
// (owner.yaml),使 bridge 无需手工编辑就认出它是主人。
|
||||
// 白名单登记是尽力而为:失败会记日志,但不会让
|
||||
// 已持久化的 channels.json 失败,因为身份红线绝不
|
||||
// 阻塞一次成功的绑定。
|
||||
func (s *Server) persistWeixinConfirmed(ctx context.Context, platform, accountID, token, botID, ownerID string) error {
|
||||
if s.channels == nil {
|
||||
return fmt.Errorf("weixin login: channel config store not injected")
|
||||
@@ -154,9 +154,9 @@ func (s *Server) persistWeixinConfirmed(ctx context.Context, platform, accountID
|
||||
if ownerID != "" {
|
||||
cfg["owner_id"] = ownerID
|
||||
}
|
||||
// Ensure the binding user (the scanner) lands in the channel's allow list, so
|
||||
// the bridge recognises it as the owner and replies instead of silencing it as
|
||||
// a stranger (actor_role=unknown / env=stranger).
|
||||
// 确保绑定用户(扫码者)落入该渠道的允许列表,
|
||||
// 使 bridge 认出它是主人并回复,而不是把它当作
|
||||
// 陌生人(actor_role=unknown / env=stranger)静默掉。
|
||||
allowUsers := append([]string(nil), base.AllowUsers...)
|
||||
if ownerID != "" {
|
||||
found := false
|
||||
@@ -191,10 +191,10 @@ func (s *Server) persistWeixinConfirmed(ctx context.Context, platform, accountID
|
||||
if err := s.channels.Reload(platform); err != nil {
|
||||
return err
|
||||
}
|
||||
// Stamp the scanner (the bound owner) into the owner whitelist so the bridge
|
||||
// recognizes it structurally. Best-effort: never fail an already-successful
|
||||
// bind because a whitelist write hiccupped — the operator is alerted by a log
|
||||
// line instead.
|
||||
// 把扫码者(被绑定的主人)盖进主人白名单,使 bridge
|
||||
// 从结构上认出它。尽力而为:绝不因为白名单写入打嗝
|
||||
// 就让已经成功的绑定失败 —— 改为用一行日志
|
||||
// 提醒运维。
|
||||
if ownerID != "" && s.ownerRegistrar != nil {
|
||||
if err := s.ownerRegistrar.RegisterChannelOwner(ctx, platform, ownerID); err != nil {
|
||||
s.log.Error("weixin login owner register", "platform", platform, "err", err)
|
||||
@@ -203,17 +203,17 @@ func (s *Server) persistWeixinConfirmed(ctx context.Context, platform, accountID
|
||||
return nil
|
||||
}
|
||||
|
||||
// weixinAccountFor resolves the *weixin_ilink.Adapter registered for the
|
||||
// platform/account. It is nil for an unknown account or a wrapped adapter that
|
||||
// does not expose the login surface.
|
||||
// weixinAccountFor 解析为某平台/账号注册的 *weixin_ilink.Adapter。
|
||||
// 对未知账号,或未暴露登录表面的包装适配器,
|
||||
// 它返回 nil。
|
||||
func weixinAccountFor(caps CapabilityStore, platform, accountID string) (weixinLoginAdmin, bool) {
|
||||
if caps == nil {
|
||||
return nil, false
|
||||
}
|
||||
// A weixin adapter may be registered with the bare "weixin-ilink" name (single
|
||||
// account) rather than "weixin-ilink:<acct>", so first try a composite match, and
|
||||
// fall back to a bare weixin adapter when the caller names a specific account but
|
||||
// no composite name carries it (the panel passes the config account id).
|
||||
// weixin 适配器可能以裸 "weixin-ilink" 名注册(单账号),
|
||||
// 而非 "weixin-ilink:<acct>",因此先尝试复合匹配,
|
||||
// 当调用方指定了具体账号、但没有复合名承载它时,回退到
|
||||
// 裸 weixin 适配器(面板传的就是配置里的账号 id)。
|
||||
var bare weixinLoginAdmin
|
||||
var bareFound bool
|
||||
for _, cap := range caps.List(capreg.KindChannel) {
|
||||
@@ -242,11 +242,11 @@ func weixinAccountFor(caps CapabilityStore, platform, accountID string) (weixinL
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// asWeixinLoginAdmin unwraps a registered channel value to its inner weixin-ilink
|
||||
// adapter, mirroring asKookAdmin: the registration wraps the adapter in cmd/cyrene's
|
||||
// namedAdapter (an unexported struct embedding adapter.PlatformAdapter); reflection
|
||||
// reads the embedded field and type-asserts to the login surface. A direct
|
||||
// *weixin_ilink.Adapter value (tests) is also accepted.
|
||||
// asWeixinLoginAdmin 把已注册的渠道值拆到其内层 weixin-ilink
|
||||
// 适配器,仿照 asKookAdmin:注册时会把适配器包进 cmd/cyrene 的
|
||||
// namedAdapter(一个内嵌 adapter.PlatformAdapter 的未导出结构体);
|
||||
// 反射读取被内嵌字段并类型断言到登录表面。直接的
|
||||
// *weixin_ilink.Adapter 值(测试)也被接受。
|
||||
func asWeixinLoginAdmin(v any) (weixinLoginAdmin, bool) {
|
||||
if w, ok := v.(weixinLoginAdmin); ok {
|
||||
return w, true
|
||||
@@ -271,8 +271,8 @@ func asWeixinLoginAdmin(v any) (weixinLoginAdmin, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// weixinTarget resolves the weixin-ilink adapter for a platform/account, writing
|
||||
// an honest error and reporting found=false when it cannot.
|
||||
// weixinTarget 解析某平台/账号的 weixin-ilink 适配器;无法解析时
|
||||
// 写出诚实的错误并报告 found=false。
|
||||
func (s *Server) weixinTarget(r *http.Request, w http.ResponseWriter, what, platform, accountID string) (weixinLoginAdmin, bool) {
|
||||
platform = strings.TrimSpace(platform)
|
||||
if channelBasePlatform(platform) != "weixin-ilink" {
|
||||
@@ -290,8 +290,8 @@ func (s *Server) weixinTarget(r *http.Request, w http.ResponseWriter, what, plat
|
||||
return wx, true
|
||||
}
|
||||
|
||||
// maskToken redacts a secret to "****" + the trailing 4 characters, matching the
|
||||
// project's maskToken so a token never lands verbatim in a wire response.
|
||||
// maskToken 把秘密脱敏成 "****" + 末 4 个字符,与项目的
|
||||
// maskToken 一致,使 token 绝不原样落在线上响应里。
|
||||
func maskToken(s string) string {
|
||||
if len(s) <= 4 {
|
||||
return "****"
|
||||
@@ -299,10 +299,10 @@ func maskToken(s string) string {
|
||||
return "****" + s[len(s)-4:]
|
||||
}
|
||||
|
||||
// qrImageDataURL turns a QR image's raw content into an embeddable data URL. When
|
||||
// the content is an http(s) URL it fetches and base64-encodes it (best effort); on
|
||||
// any failure it returns "" so the caller keeps qrUrl. A content already carrying a
|
||||
// data: prefix is passed through; anything else is treated as raw base64.
|
||||
// qrImageDataURL 把二维码图片的原始内容转成可内嵌的 data URL。
|
||||
// 内容是 http(s) URL 时,它拉取并 base64 编码(尽力而为);
|
||||
// 任何失败都返回 "",使调用方保留 qrUrl。已带
|
||||
// data: 前缀的内容原样透传;其他一律当作原始 base64。
|
||||
func qrImageDataURL(ctx context.Context, raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
|
||||
Reference in new issue
Block a user