# Channel PublicName 实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 为 Channel 添加 `public_name` 字段,让管理员可以为每个渠道设置用户可见的友好名称,替代前端硬编码的"通道一/二/三"。 **Architecture:** 后端 Channel 模型新增字段 → AutoMigrate 自动建列 → 迁移函数回填数据 → 定价查询和 API 返回公共名称 → 前端表单支持编辑 → 展示层使用公共名称。 **Tech Stack:** Go (GORM) / React (Semi Design) / SQLite+MySQL+PostgreSQL --- ### Task 1: Channel 模型添加 PublicName 字段 **Files:** - Modify: `model/channel.go:28` - [ ] **Step 1: 在 Channel 结构体 Name 字段后添加 PublicName** 当前代码(`model/channel.go:28`): ```go Name string `json:"name" gorm:"index"` Weight *uint `json:"weight" gorm:"default:0"` ``` 改为: ```go Name string `json:"name" gorm:"index"` PublicName string `json:"public_name" gorm:"size:255;default:''"` Weight *uint `json:"weight" gorm:"default:0"` ``` - [ ] **Step 2: 验证编译通过** Run: `cd D:/code/new-api && go build ./model/...` Expected: 编译成功,无错误 - [ ] **Step 3: Commit** ```bash git add model/channel.go git commit -m "feat: add PublicName field to Channel model" ``` --- ### Task 2: 修改 GetAllChannelsForBinding 查询包含 public_name **Files:** - Modify: `model/channel.go:282` - [ ] **Step 1: 修改 Select 列** 当前代码(`model/channel.go:282`): ```go err := DB.Select("id, name, type, remark"). ``` 改为: ```go err := DB.Select("id, name, public_name, type, remark"). ``` - [ ] **Step 2: 验证编译通过** Run: `cd D:/code/new-api && go build ./model/...` Expected: 编译成功 - [ ] **Step 3: Commit** ```bash git add model/channel.go git commit -m "feat: include public_name in GetAllChannelsForBinding query" ``` --- ### Task 3: ChannelPricingWithChannel 扩展 + SQL 查询修改 **Files:** - Modify: `model/channel_pricing.go:254` (结构体) - Modify: `model/channel_pricing.go:299` (SELECT) - Modify: `model/channel_pricing.go:319` (GROUP BY) - [ ] **Step 1: 结构体添加 ChannelPublicName 字段** 当前代码(`model/channel_pricing.go:254`): ```go ChannelName string `json:"channel_name"` ChannelType int `json:"channel_type"` ``` 改为: ```go ChannelName string `json:"channel_name"` ChannelPublicName string `json:"channel_public_name"` ChannelType int `json:"channel_type"` ``` - [ ] **Step 2: SELECT 子句添加 channels.public_name** 当前代码(`model/channel_pricing.go:299`): ```go Select(`abilities.channel_id, channels.name as channel_name, channels.type as channel_type, ``` 改为: ```go Select(`abilities.channel_id, channels.name as channel_name, channels.public_name as channel_public_name, channels.type as channel_type, ``` - [ ] **Step 3: GROUP BY 子句添加 channels.public_name** 当前代码(`model/channel_pricing.go:319`): ```go Group("abilities.channel_id, channels.name, channels.type, channel_pricings.quota_type, ``` 改为: ```go Group("abilities.channel_id, channels.name, channels.public_name, channels.type, channel_pricings.quota_type, ``` 注意:`channels.public_name` 插入在 `channels.name` 后面。 - [ ] **Step 4: 验证编译通过** Run: `cd D:/code/new-api && go build ./model/...` Expected: 编译成功 - [ ] **Step 5: Commit** ```bash git add model/channel_pricing.go git commit -m "feat: add public_name to channel pricing query" ``` --- ### Task 4: pricing.go 默认通道名称使用 public_name **Files:** - Modify: `model/pricing.go:290-303` - [ ] **Step 1: 扩展匿名结构体** 当前代码(`model/pricing.go:290-293`): ```go var allCPs []struct { ChannelPricing ChannelName string } ``` 改为: ```go var allCPs []struct { ChannelPricing ChannelName string ChannelPublicName string } ``` - [ ] **Step 2: 扩展 Select 子句** 当前代码(`model/pricing.go:295`): ```go Select("channel_pricings.*, channels.name as channel_name"). ``` 改为: ```go Select("channel_pricings.*, channels.name as channel_name, channels.public_name as channel_public_name"). ``` - [ ] **Step 3: 修改 channelNameMap 构建逻辑** 当前代码(`model/pricing.go:303`): ```go channelNameMap[allCPs[i].ChannelId] = allCPs[i].ChannelName ``` 改为: ```go name := allCPs[i].ChannelPublicName if name == "" { name = allCPs[i].ChannelName } channelNameMap[allCPs[i].ChannelId] = name ``` - [ ] **Step 4: 验证编译通过** Run: `cd D:/code/new-api && go build ./model/...` Expected: 编译成功 - [ ] **Step 5: Commit** ```bash git add model/pricing.go git commit -m "feat: use public_name as default channel display name" ``` --- ### Task 5: 数据回填迁移函数 **Files:** - Modify: `model/main.go:306-307` (调用位置) - Modify: `model/main.go:695` (函数定义位置) - [ ] **Step 1: 在 migrateDB 的 return nil 之前插入调用** 当前代码(`model/main.go:304-308`): ```go // 将现有 sort_order=0 的模型和供应商更新为默认大数 DB.Model(&Model{}).Where("sort_order = 0").Update("sort_order", 999999) DB.Model(&Vendor{}).Where("sort_order = 0").Update("sort_order", 999999) return nil } ``` 改为: ```go // 将现有 sort_order=0 的模型和供应商更新为默认大数 DB.Model(&Model{}).Where("sort_order = 0").Update("sort_order", 999999) DB.Model(&Vendor{}).Where("sort_order = 0").Update("sort_order", 999999) migrateChannelPublicName() return nil } ``` - [ ] **Step 2: 在文件末尾(第 696 行后)添加迁移函数** ```go func migrateChannelPublicName() { result := DB.Model(&Channel{}). Where("public_name = '' OR public_name IS NULL"). Update("public_name", gorm.Expr("name")) if result.Error != nil { common.SysError("[Migration] migrateChannelPublicName failed: " + result.Error.Error()) } else if result.RowsAffected > 0 { common.SysLog(fmt.Sprintf("[Migration] migrateChannelPublicName: backfilled %d channels", result.RowsAffected)) } } ``` **说明:** - AutoMigrate(`migrateDB()` 第 262 行 `&Channel{}`)会先创建列 - WHERE 条件保证幂等 - `gorm.Expr("name")` 引用列名,SQLite/MySQL/PostgreSQL 通用 - `model/main.go` 已有 `fmt`、`gorm`、`common` 的 import,无需额外导入 - [ ] **Step 3: 验证编译通过** Run: `cd D:/code/new-api && go build ./model/...` Expected: 编译成功 - [ ] **Step 4: Commit** ```bash git add model/main.go git commit -m "feat: add migration to backfill public_name from name" ``` --- ### Task 6: controller 层验证 + API 返回 **Files:** - Modify: `controller/channel.go:582-585` (验证) - Modify: `controller/channel.go:2110-2115` (API 返回) - [ ] **Step 1: 在 validateChannel 的 isAdd 块中添加 public_name 校验** 当前代码(`controller/channel.go:582-585`): ```go if isAdd { if channel == nil || channel.Key == "" { return fmt.Errorf("channel cannot be empty") } // 检查模型名称长度是否超过 255 ``` 改为: ```go if isAdd { if channel == nil || channel.Key == "" { return fmt.Errorf("channel cannot be empty") } if strings.TrimSpace(channel.PublicName) == "" { return fmt.Errorf("public name cannot be empty") } // 检查模型名称长度是否超过 255 ``` **说明:** 保持英文错误消息与现有代码一致。 - [ ] **Step 2: 在 GetUserChannelsForBinding 返回值中添加 public_name** 当前代码(`controller/channel.go:2110-2115`): ```go result = append(result, gin.H{ "id": ch.Id, "name": ch.Name, "type": ch.Type, "remark": ch.Remark, }) ``` 改为: ```go result = append(result, gin.H{ "id": ch.Id, "name": ch.Name, "public_name": ch.PublicName, "type": ch.Type, "remark": ch.Remark, }) ``` - [ ] **Step 3: 验证编译通过** Run: `cd D:/code/new-api && go build ./controller/...` Expected: 编译成功 - [ ] **Step 4: Commit** ```bash git add controller/channel.go git commit -m "feat: validate public_name on channel creation and return in binding API" ``` --- ### Task 7: 前端 i18n 翻译 **Files:** - Modify: `web/src/i18n/locales/zh-CN.json` - Modify: `web/src/i18n/locales/en.json` - [ ] **Step 1: zh-CN.json 添加翻译** 在 `"请为渠道命名"` 条目(第 2454 行)之后添加: ```json "对外名称": "对外名称", "用户看到的渠道名称,如"标准通道"、"高速通道"": "用户看到的渠道名称,如"标准通道"、"高速通道"", "请填写对外名称": "请填写对外名称", "请填写渠道名称、对外名称和渠道密钥!": "请填写渠道名称、对外名称和渠道密钥!", ``` 注意:zh-CN.json 的 key 和 value 相同(中文 → 中文)。 - [ ] **Step 2: en.json 添加翻译** 在 `"Please name the channel"` 条目(第 2473 行)之后添加: ```json "对外名称": "Public Name", "用户看到的渠道名称,如"标准通道"、"高速通道"": "User-facing channel name, e.g. \"Standard\", \"Fast\"", "请填写对外名称": "Please enter a public name", "请填写渠道名称、对外名称和渠道密钥!": "Please enter channel name, public name and key!", ``` - [ ] **Step 3: Commit** ```bash git add web/src/i18n/locales/zh-CN.json web/src/i18n/locales/en.json git commit -m "feat: add i18n translations for channel public_name" ``` --- ### Task 8: EditChannelModal 表单添加对外名称字段 **Files:** - Modify: `web/src/components/table/channels/modals/EditChannelModal.jsx:142` (originInputs) - Modify: `web/src/components/table/channels/modals/EditChannelModal.jsx:1980` (表单 UI) - Modify: `web/src/components/table/channels/modals/EditChannelModal.jsx:1329` (提交校验) - [ ] **Step 1: originInputs 添加 public_name 默认值** 当前代码(第 142-143 行): ```javascript const originInputs = { name: '', type: 1, ``` 改为: ```javascript const originInputs = { name: '', public_name: '', type: 1, ``` - [ ] **Step 2: 在 name 的 Form.Input 后添加 public_name 输入框** 当前代码(第 1972-1980 行): ```jsx