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,16 +1,16 @@
|
||||
package gateway
|
||||
|
||||
// Admin channel-configuration writes (owner-only, audited). The panel saves a
|
||||
// platform's account config (token/address/rate limit/black-or-white lists) to
|
||||
// channels.json and hot-reloads that platform's adapters — no restart required
|
||||
// (design/04 §3.1). The GET /admin/channels read path masks every token so a
|
||||
// secret is never returned to the client; this write path persists the real
|
||||
// token to the gitignored file and triggers the bridge reload.
|
||||
// 管理渠道配置写操作(仅 owner,带审计)。面板把某平台的
|
||||
// 账号配置(token/地址/速率限制/黑或白名单)保存到
|
||||
// channels.json 并热重载该平台的适配器 —— 无需重启
|
||||
// (design/04 §3.1)。GET /admin/channels 读路径会对每个 token
|
||||
// 脱敏,因此秘密绝不返回给客户端;这条写路径把真实
|
||||
// token 持久化进被 gitignore 的文件并触发 bridge 重载。
|
||||
//
|
||||
// The request carries a generic config map (platform-specific keys such as
|
||||
// bot_token / base_url / remote_url / endpointPath) plus convenience top-level
|
||||
// fields for the common token/address fields, so the panel can send either the
|
||||
// config object or the shortcut fields.
|
||||
// 请求携带一个通用 config map(平台特有键,如
|
||||
// bot_token / base_url / remote_url / endpointPath),外加针对常见
|
||||
// token/地址字段的顶层便捷字段,因此面板既可发送
|
||||
// config 对象,也可发送快捷字段。
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
apperr "git.yeij.top/AskaEth/Cyrene_v2/internal/errors"
|
||||
)
|
||||
|
||||
// AdminChannelAccount is the wire shape of one configured channel account in the
|
||||
// admin list. Config values are masked (no raw token) so nothing sensitive is
|
||||
// returned to the client.
|
||||
// AdminChannelAccount 是管理列表中一个已配置渠道账号的线上
|
||||
// 结构。Config 的值已脱敏(不含原始 token),因此没有敏感
|
||||
// 内容返回给客户端。
|
||||
type AdminChannelAccount struct {
|
||||
ID string `json:"id"`
|
||||
Config map[string]any `json:"config,omitempty"`
|
||||
@@ -35,29 +35,29 @@ type AdminChannelAccount struct {
|
||||
GroupMsgPerMin int `json:"groupMsgPerMin,omitempty"`
|
||||
MsgPerMin int `json:"msgPerMin,omitempty"`
|
||||
MaxMessages int `json:"maxMessages,omitempty"`
|
||||
// PersonaID is the role this account answers as (design/22 §2 方案 b), omitted
|
||||
// when the account binds none. It is a persona id, not a secret.
|
||||
// PersonaID 是该账号应答所用的角色(design/22 §2 方案 b),账号
|
||||
// 未绑定任何角色时省略。它是人格 id,不是秘密。
|
||||
PersonaID string `json:"personaId,omitempty"`
|
||||
// BotID/BotUsername/BotNickname are the connected bot's resolved identity
|
||||
// (kook fetches it from /api/v3/user/me on connect). They come from the live
|
||||
// adapter, not the persisted channels.json config, and are non-sensitive (the
|
||||
// bot's public handle/display name), so the panel may show them.
|
||||
// BotID/BotUsername/BotNickname 是已连接机器人的实际解析身份
|
||||
// (kook 在 connect 时从 /api/v3/user/me 获取)。它们来自活的
|
||||
// 适配器,而不是持久化的 channels.json 配置,且非敏感(机器人
|
||||
// 的公开 handle/显示名),因此面板可以展示它们。
|
||||
BotID string `json:"botId,omitempty"`
|
||||
BotUsername string `json:"botUsername,omitempty"`
|
||||
BotNickname string `json:"botNickname,omitempty"`
|
||||
// Capabilities lists the account-level admin operations the platform's
|
||||
// adapter exposes (friends/channels/user-chats/message-test) so the panel
|
||||
// renders the right per-account cards. It is omitted for platforms without a
|
||||
// specialized adapter surface.
|
||||
// Capabilities 列出该平台适配器暴露的账号级管理操作
|
||||
// (friends/channels/user-chats/message-test),使面板
|
||||
// 渲染正确的每账号卡片。没有专用适配器表面的平台
|
||||
// 会省略它。
|
||||
Capabilities []string `json:"capabilities,omitempty"`
|
||||
}
|
||||
|
||||
// channelConfigRequest is the PUT /api/v1/admin/channels/{platform}/config body.
|
||||
// Config holds platform-specific keys (bot_token, base_url, remote_url,
|
||||
// endpointPath, ...); the top-level convenience fields are merged into it for the
|
||||
// common token/address fields. AllowGroups/DenyGroups/AllowUsers/DenyUsers are the
|
||||
// black-or-white lists (design/16 §4.6 §6); the *_PerMin/MaxMessages fields are
|
||||
// the per-account flow limits (design/12 §6 策略三/四).
|
||||
// channelConfigRequest 是 PUT /api/v1/admin/channels/{platform}/config 的请求体。
|
||||
// Config 保存平台特有键(bot_token、base_url、remote_url、
|
||||
// endpointPath……);顶层便捷字段会为常见 token/地址字段
|
||||
// 合并进去。AllowGroups/DenyGroups/AllowUsers/DenyUsers 是
|
||||
// 黑或白名单(design/16 §4.6 §6);*_PerMin/MaxMessages 字段是
|
||||
// 每账号的流量限制(design/12 §6 策略三/四)。
|
||||
type channelConfigRequest struct {
|
||||
ID string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
@@ -75,22 +75,22 @@ type channelConfigRequest struct {
|
||||
GroupMsgPerMin int `json:"groupMsgPerMin"`
|
||||
MsgPerMin int `json:"msgPerMin"`
|
||||
MaxMessages int `json:"maxMessages"`
|
||||
// PersonaID is the role this account answers as (design/22 §2 方案 b). It is a
|
||||
// pointer because the wire contract is three-state and a plain string cannot
|
||||
// tell an absent key from an explicit "": absent keeps the stored binding,
|
||||
// "" clears it, a non-empty id binds it. An unknown id is refused with 400 so a
|
||||
// save can never bind a role that cannot answer.
|
||||
// PersonaID 是该账号应答所用的角色(design/22 §2 方案 b)。它是
|
||||
// 指针,因为线上契约是三态的,而普通 string 无法
|
||||
// 区分键缺失与显式 "":缺失保留已存绑定,
|
||||
// "" 清除它,非空 id 绑定它。未知 id 以 400 拒绝,因此
|
||||
// 保存绝不可能绑定一个无法应答的角色。
|
||||
PersonaID *string `json:"personaId"`
|
||||
}
|
||||
|
||||
// adminSaveChannelConfig persists one account's config for a platform into
|
||||
// channels.json (Upsert by id) and hot-reloads that platform's adapters. The
|
||||
// response carries the channels.json path and the account id so the panel can
|
||||
// address the saved account (for a later edit/delete).
|
||||
// adminSaveChannelConfig 把某平台一个账号的配置持久化进
|
||||
// channels.json(按 id Upsert)并热重载该平台的适配器。
|
||||
// 响应携带 channels.json 路径与账号 id,使面板能
|
||||
// 定位已保存的账号(供之后编辑/删除)。
|
||||
//
|
||||
// personaId is three-state (see channelPersonaBinding): the panel only sends the
|
||||
// key when the owner touched the role select, so an absent key must keep the
|
||||
// stored binding rather than silently unbind the account.
|
||||
// personaId 是三态的(见 channelPersonaBinding):面板仅在主人
|
||||
// 触碰过角色选择时才发送该键,因此键缺失必须保留
|
||||
// 已存绑定,而不是静默解绑该账号。
|
||||
func (s *Server) adminSaveChannelConfig(w http.ResponseWriter, r *http.Request) {
|
||||
platform := strings.TrimSpace(r.PathValue("platform"))
|
||||
if platform == "" {
|
||||
@@ -108,11 +108,11 @@ func (s *Server) adminSaveChannelConfig(w http.ResponseWriter, r *http.Request)
|
||||
writeError(w, apperr.Wrap(apperr.ErrInternal, "channels_unavailable", "渠道配置服务未注入"))
|
||||
return
|
||||
}
|
||||
// A channel-level role must exist in the persona library, or the account would
|
||||
// be bound to a role that cannot answer (design/22 §2 方案 b). Only a value the
|
||||
// panel actually sent is validated; an absent key (nil) inherits the stored
|
||||
// binding, which was validated when it was written. An explicit "" is always
|
||||
// accepted: it means "clear the channel binding".
|
||||
// 渠道级角色必须存在于人格库中,否则账号会被绑定到
|
||||
// 一个无法应答的角色(design/22 §2 方案 b)。只校验面板确实
|
||||
// 发送的值;键缺失(nil)继承已存绑定,该绑定在
|
||||
// 写入时就已校验。显式的 "" 一律接受:
|
||||
// 它表示「清除渠道绑定」。
|
||||
if req.PersonaID != nil {
|
||||
if !s.requireKnownPersona(w, strings.TrimSpace(*req.PersonaID)) {
|
||||
s.auditClaims(r, "channel.save", "error")
|
||||
@@ -120,10 +120,10 @@ func (s *Server) adminSaveChannelConfig(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
cfg := channelConfigFromRequest(req)
|
||||
// weixin-ilink may be saved with an empty config: the panel creates the account
|
||||
// first, then the user scans the QR (get_bot_qrcode) which fills bot_token/bot_id/
|
||||
// owner_id via login-status (design/08 §4). Requiring a token here would block that
|
||||
// flow, so exempt it from the "at least one field" gate.
|
||||
// weixin-ilink 可以带空配置保存:面板先创建账号,
|
||||
// 随后用户扫码(get_bot_qrcode),它经 login-status 填入
|
||||
// bot_token/bot_id/owner_id(design/08 §4)。此处要求 token 会阻塞该
|
||||
// 流程,因此把它从「至少一个字段」的门禁中豁免。
|
||||
if len(cfg) == 0 && platform != "weixin-ilink" {
|
||||
s.auditClaims(r, "channel.save", "error")
|
||||
writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "至少提供 token/baseUrl/address 等配置字段"))
|
||||
@@ -163,8 +163,8 @@ func (s *Server) adminSaveChannelConfig(w http.ResponseWriter, r *http.Request)
|
||||
})
|
||||
}
|
||||
|
||||
// adminDeleteChannelAccount removes one account (by the id query param) from a
|
||||
// platform in channels.json and hot-reloads that platform so the adapter stops.
|
||||
// adminDeleteChannelAccount 按 id 查询参数从某平台在 channels.json
|
||||
// 中移除一个账号,并热重载该平台使适配器停止。
|
||||
func (s *Server) adminDeleteChannelAccount(w http.ResponseWriter, r *http.Request) {
|
||||
platform := strings.TrimSpace(r.PathValue("platform"))
|
||||
id := strings.TrimSpace(r.URL.Query().Get("id"))
|
||||
@@ -206,10 +206,10 @@ func (s *Server) adminDeleteChannelAccount(w http.ResponseWriter, r *http.Reques
|
||||
})
|
||||
}
|
||||
|
||||
// channelConfigFromRequest merges the request's convenience fields into the
|
||||
// platform-specific config map, preferring keys the panel already set explicitly.
|
||||
// It never injects a key the panel left out (an empty token is left absent so the
|
||||
// adapter's zero value, not an empty-string token, is used).
|
||||
// channelConfigFromRequest 把请求的便捷字段合并进平台特有 config
|
||||
// map,优先采用面板已显式设置的键。它绝不注入面板
|
||||
// 未给出的键(空 token 保持缺位,这样用的是
|
||||
// 适配器的零值,而不是空字符串 token)。
|
||||
func channelConfigFromRequest(req channelConfigRequest) map[string]any {
|
||||
cfg := make(map[string]any, len(req.Config)+8)
|
||||
for k, v := range req.Config {
|
||||
@@ -253,17 +253,17 @@ func channelConfigFromRequest(req channelConfigRequest) map[string]any {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// channelPersonaBinding resolves the three-state personaId contract of
|
||||
// PUT /admin/channels/{platform}/config:
|
||||
// channelPersonaBinding 解析 PUT /admin/channels/{platform}/config 的
|
||||
// 三态 personaId 契约:
|
||||
//
|
||||
// - the key is absent (nil) → keep the account's stored binding;
|
||||
// - the key is an explicit "" → clear the binding (an explicit unbind);
|
||||
// - the key is a non-empty id → bind that role.
|
||||
// - 键缺失(nil) → 保留该账号已存的绑定;
|
||||
// - 键是显式 "" → 清除绑定(显式解绑);
|
||||
// - 键是非空 id → 绑定该角色。
|
||||
//
|
||||
// The panel omits the key unless the owner touched the role select, so resolving
|
||||
// an absent key from the stored account is what keeps a plain config edit from
|
||||
// silently dropping a binding. A save that creates a new account (no stored
|
||||
// account to inherit from) resolves to "no binding".
|
||||
// 除非主人触碰过角色选择,面板会省略该键,因此从已存
|
||||
// 账号解析缺失键,正是让一次普通的配置编辑不会
|
||||
// 静默丢弃绑定的原因。创建新账号的保存(没有已存
|
||||
// 账号可继承)解析为「无绑定」。
|
||||
func channelPersonaBinding(sent *string, stored config.ChannelAccount) string {
|
||||
if sent == nil {
|
||||
return strings.TrimSpace(stored.PersonaID)
|
||||
@@ -271,10 +271,10 @@ func channelPersonaBinding(sent *string, stored config.ChannelAccount) string {
|
||||
return strings.TrimSpace(*sent)
|
||||
}
|
||||
|
||||
// existingChannelAccount returns the stored account a save would replace (same
|
||||
// platform, same id) so the write can inherit the fields the panel did not send.
|
||||
// An empty id means the save creates a new account, so there is nothing to
|
||||
// inherit.
|
||||
// existingChannelAccount 返回一次保存将要替换的已存账号(同
|
||||
// 平台、同 id),使写操作能继承面板未发送的字段。
|
||||
// 空 id 意味着该保存创建新账号,因此没有
|
||||
// 可继承的内容。
|
||||
func existingChannelAccount(list []config.ChannelAccount, id string) config.ChannelAccount {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
@@ -288,9 +288,9 @@ func existingChannelAccount(list []config.ChannelAccount, id string) config.Chan
|
||||
return config.ChannelAccount{}
|
||||
}
|
||||
|
||||
// savedAccountID returns the account id that a save produced — the caller-supplied
|
||||
// one when present, otherwise the id runtimeconfig.Upsert generated (which it
|
||||
// appends last for a brand-new account).
|
||||
// savedAccountID 返回一次保存产出的账号 id —— 调用方提供时
|
||||
// 用它的,否则用 runtimeconfig.Upsert 生成的 id(对全新账号
|
||||
// 它会追加在末尾)。
|
||||
func savedAccountID(list []config.ChannelAccount, supplied string) string {
|
||||
if supplied != "" {
|
||||
return supplied
|
||||
@@ -301,7 +301,7 @@ func savedAccountID(list []config.ChannelAccount, supplied string) string {
|
||||
return list[len(list)-1].ID
|
||||
}
|
||||
|
||||
// adminChannelAccountOf maps a (masked) account to its admin wire shape.
|
||||
// adminChannelAccountOf 把一个(已脱敏的)账号映射为其管理侧线上结构。
|
||||
func adminChannelAccountOf(acct config.ChannelAccount) AdminChannelAccount {
|
||||
return AdminChannelAccount{
|
||||
ID: acct.ID,
|
||||
@@ -317,9 +317,9 @@ func adminChannelAccountOf(acct config.ChannelAccount) AdminChannelAccount {
|
||||
}
|
||||
}
|
||||
|
||||
// channelBasePlatform returns the bare platform of a (possibly multi-account)
|
||||
// adapter name like "telegram:conf0" → "telegram", so config lookup groups every
|
||||
// account of a platform together.
|
||||
// channelBasePlatform 返回形如 "telegram:conf0" → "telegram" 的
|
||||
// (可能多账号的)适配器名的裸平台,使配置查找把某平台的
|
||||
// 所有账号归到一组。
|
||||
func channelBasePlatform(name string) string {
|
||||
if base, _, ok := strings.Cut(name, ":"); ok {
|
||||
return base
|
||||
@@ -327,9 +327,9 @@ func channelBasePlatform(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// configuredAccount reports whether an account carries a meaningful config (a
|
||||
// panel-created id or a real token/address), so the admin list skips the blank
|
||||
// placeholder accounts the env fallback synthesizes for unconfigured platforms.
|
||||
// configuredAccount 报告某账号是否携带有效配置(面板创建的
|
||||
// id 或真实 token/地址),使管理列表跳过环境变量回退
|
||||
// 为未配置平台合成的空白占位账号。
|
||||
func configuredAccount(a config.ChannelAccount) bool {
|
||||
if strings.TrimSpace(a.ID) != "" {
|
||||
return true
|
||||
|
||||
Reference in new issue
Block a user