From f88030acf5eea53e74e830b4ccd41ca3a492e629 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 5 Sep 2026 15:58:00 +0800 Subject: [PATCH] =?UTF-8?q?chore(seed):=20=E7=A7=8D=E5=AD=90=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=9C=80=E5=B0=8F=E5=8C=96=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E8=99=9A=E6=9E=84=E6=BC=94=E7=A4=BA/?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - seed 仅初始化平台管理员与三个套餐,不再预置假租户/坐席/客户/知识库/公告 - 租户改由平台后台正常开通(自动创建管理员账号与默认网页渠道) - README 与 deploy 文档、脚本中的演示账号说明同步更新 --- README.md | 11 +-- deploy/README.md | 7 +- deploy/scripts/deploy.sh | 2 +- deploy/scripts/dev-local.sh | 6 +- server/cmd/seed/main.go | 140 +++++++----------------------------- 5 files changed, 40 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index 4c74ada..4a5efc5 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ kefu_cloud/ # 仓库目录名(本地可能仍为旧名,以 ├── docs/ # 产品原型 / 需求 HTML ├── server/ # Go module: kefu-cloud/server │ ├── cmd/main.go # 服务入口 -│ ├── cmd/seed/ # 演示数据 +│ ├── cmd/seed/ # 最小种子(平台账号 + 套餐) │ └── internal/ │ ├── config/ # 环境变量配置 │ ├── handler/ # HTTP / Widget / 导出 / 分配 @@ -115,7 +115,7 @@ kefu_cloud/ # 仓库目录名(本地可能仍为旧名,以 # 服务器安装 Docker 后 chmod +x deploy/scripts/*.sh ./deploy/scripts/deploy.sh # 首次会生成 deploy/.env.prod,请改域名与密钥 -./deploy/scripts/deploy.sh --seed # 可选:导入演示账号 +./deploy/scripts/deploy.sh --seed # 可选:初始化平台账号与套餐 ``` 说明见 **[deploy/README.md](deploy/README.md)**(Caddy 共存、安全组、HTTPS、COS、备份升级)。Caddy 站点示例:`deploy/caddy/Caddyfile.example`。 @@ -177,12 +177,13 @@ make db-up ### 默认账号(seed 后) +seed 仅初始化平台管理员与默认套餐,不预置任何虚构的租户/坐席/客户数据: + | 角色 | 用户名 | 密码 | |------|--------|------| | 平台管理员 | `platform_admin` | `kefu_admin123` | -| 租户管理员 | `kefu_admin` | `kefu_admin123` | -| 主管 | `supervisor` | `kefu_admin123` | -| 坐席 | `agent1` / `agent2` / `agent3` | `kefu_admin123` | + +租户请在平台后台「租户管理」开通(会自动创建租户管理员账号与默认网页渠道),坐席由租户管理员在「成员账号」中创建。**生产环境部署后请立即修改默认密码。** ### 常用入口 diff --git a/deploy/README.md b/deploy/README.md index 069b7fa..be43d46 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -80,7 +80,7 @@ chmod +x deploy/scripts/*.sh # 首次会生成 deploy/.env.prod 并提示你改域名/公网地址 ./deploy/scripts/deploy.sh -# 需要演示账号时(kefu_admin / kefu_admin123 等) +# 需要初始化平台账号与套餐时 ./deploy/scripts/deploy.sh --seed ``` @@ -182,10 +182,9 @@ STORAGE_PUBLIC_BASE_URL=https://kefu.example.com/files | 角色 | 用户名 | 密码 | |------|--------|------| | 平台管理员 | `platform_admin` | `kefu_admin123` | -| 租户管理员 | `kefu_admin` | `kefu_admin123` | -| 坐席 | `agent1` 等 | `kefu_admin123` | -**上线后立即修改密码或关闭 seed 账号。** +seed 不再预置虚构的租户/坐席/客户数据;租户请在平台后台开通(自动创建租户管理员)。 +**上线后立即修改默认密码。** ## 目录说明 diff --git a/deploy/scripts/deploy.sh b/deploy/scripts/deploy.sh index 72a6fe7..aad326f 100755 --- a/deploy/scripts/deploy.sh +++ b/deploy/scripts/deploy.sh @@ -256,7 +256,7 @@ cmd_seed_only() { ensure_env log "运行 seed…" compose --profile seed run --rm seed - log "种子完成。默认租户管理员 kefu_admin / kefu_admin123 (若 seed 成功)" + log "种子完成。平台管理员 platform_admin / kefu_admin123(租户请在平台后台开通)" } cmd_down() { diff --git a/deploy/scripts/dev-local.sh b/deploy/scripts/dev-local.sh index 17e744b..41e06bd 100755 --- a/deploy/scripts/dev-local.sh +++ b/deploy/scripts/dev-local.sh @@ -346,7 +346,7 @@ cmd_seed() { fi go run ./cmd/seed ) - log "种子完成。默认账号见下方摘要(密码均为 kefu_admin123)" + log "种子完成。平台管理员 platform_admin / kefu_admin123" } print_summary() { @@ -363,8 +363,8 @@ print_summary() { echo " MinIO API http://127.0.0.1:9100" echo " MinIO 控制台 http://127.0.0.1:9101 (minioadmin/minioadmin)" echo - echo " 演示账号(需 --seed): platform_admin / kefu_admin / agent1 …" - echo " 密码: kefu_admin123" + echo " 种子账号(需 --seed): platform_admin(租户请在平台后台开通)" + echo " 密码: kefu_admin123(上线后请立即修改)" echo echo " 日志: ${API_LOG}" echo " ${WEB_LOG}" diff --git a/server/cmd/seed/main.go b/server/cmd/seed/main.go index e1d2c66..db02b65 100644 --- a/server/cmd/seed/main.go +++ b/server/cmd/seed/main.go @@ -2,7 +2,6 @@ package main import ( "log" - "time" "golang.org/x/crypto/bcrypt" @@ -18,125 +17,40 @@ func main() { log.Println("种子数据填充完成") } +// seed 仅初始化平台运行所需的最小数据:平台管理员账号与套餐定价。 +// 租户、坐席、渠道、知识库等业务数据请通过平台管理后台正常开通创建, +// 不再预置任何虚构的演示/测试数据。 func seed() { hash, _ := bcrypt.GenerateFromPassword([]byte("kefu_admin123"), bcrypt.DefaultCost) pwd := string(hash) model.EnsurePermissions() - // Plans - plans := []model.Plan{ - {Name: "基础版", PriceMonthly: 299, Seats: 2, StorageDays: 30, KBLimit: 50, Status: "active", Features: `{"stats":"basic","api":false,"channels":["web"],"brand":false}`}, - {Name: "专业版", PriceMonthly: 699, Seats: 10, StorageDays: 180, KBLimit: 500, Status: "active", Features: `{"stats":"advanced","api":true,"channels":["web","wechat","app"],"brand":true}`}, - {Name: "企业版", PriceMonthly: 1999, Seats: 50, StorageDays: 0, KBLimit: 0, Status: "active", Features: `{"stats":"custom","api":true,"channels":["all"],"brand":true,"dedicated":true}`}, - } - model.DB.Create(&plans) - - // Tenants - now := time.Now() - tenants := []model.Tenant{ - {Name: "赵六科技", PlanID: &plans[2].ID, SeatCount: 30, ExpireAt: now.AddDate(1, 0, 0), Status: "normal", ContactName: "赵总", ContactPhone: "13800001111", ContactEmail: "zhao@tech.com"}, - {Name: "李四商贸", PlanID: &plans[1].ID, SeatCount: 10, ExpireAt: now.AddDate(0, 6, 0), Status: "normal", ContactName: "李经理", ContactPhone: "13900002222", ContactEmail: "li@trade.com"}, - {Name: "王五集团", PlanID: &plans[2].ID, SeatCount: 50, ExpireAt: now.AddDate(0, 0, 7), Status: "expiring", ContactName: "王总", ContactPhone: "13700003333", ContactEmail: "wang@group.com"}, - {Name: "孙八信息", PlanID: &plans[0].ID, SeatCount: 2, ExpireAt: now.AddDate(1, 0, 0), Status: "suspended", ContactName: "孙经理", ContactPhone: "13600004444", ContactEmail: "sun@info.com"}, - {Name: "周九科技", PlanID: &plans[1].ID, SeatCount: 8, ExpireAt: now.AddDate(0, -1, 0), Status: "expired", ContactName: "周总", ContactPhone: "13500005555", ContactEmail: "zhou@tech.com"}, - } - model.DB.Create(&tenants) - - // 为每个租户创建内置角色及默认权限 - for _, t := range tenants { - model.EnsureBuiltinRoles(t.ID) + // 平台管理员(TenantID=0) + var count int64 + model.DB.Model(&model.User{}).Where("username = ?", "platform_admin").Count(&count) + if count == 0 { + model.DB.Create(&model.User{ + TenantID: 0, Role: "platform_admin", Username: "platform_admin", + PasswordHash: pwd, Nickname: "平台管理员", Status: "online", + }) + log.Println("平台管理员账号: platform_admin / kefu_admin123") + } else { + log.Println("平台管理员账号已存在,跳过") } - // Platform admin - model.DB.Create(&model.User{ - TenantID: 0, Role: "platform_admin", Username: "platform_admin", PasswordHash: pwd, Nickname: "平台管理员", Status: "online", - }) - - // Tenant 1 users - model.DB.Create(&model.User{ - TenantID: tenants[0].ID, Role: "admin", Username: "kefu_admin", PasswordHash: pwd, Nickname: "赵总(管理员)", Status: "online", - }) - model.DB.Create(&model.User{ - TenantID: tenants[0].ID, Role: "supervisor", Username: "supervisor", PasswordHash: pwd, Nickname: "客服主管", Status: "online", - }) - agent1 := model.User{TenantID: tenants[0].ID, Role: "agent", Username: "agent1", PasswordHash: pwd, Nickname: "客服小王", Status: "online"} - agent2 := model.User{TenantID: tenants[0].ID, Role: "agent", Username: "agent2", PasswordHash: pwd, Nickname: "客服小李", Status: "online"} - agent3 := model.User{TenantID: tenants[0].ID, Role: "agent", Username: "agent3", PasswordHash: pwd, Nickname: "客服小张", Status: "online"} - model.DB.Create(&[]model.User{agent1, agent2, agent3}) - - // Tenant 2 users - model.DB.Create(&model.User{ - TenantID: tenants[1].ID, Role: "admin", Username: "litrade_admin", PasswordHash: pwd, Nickname: "李经理", Status: "online", - }) - model.DB.Create(&model.User{ - TenantID: tenants[1].ID, Role: "agent", Username: "litrade_agent1", PasswordHash: pwd, Nickname: "客服小李2", Status: "online", - }) - - // Channels for Tenant 1 - channels := []model.Channel{ - {TenantID: tenants[0].ID, Type: "web", Name: "网页聊天", Status: "enabled", ScriptCode: ``}, - {TenantID: tenants[0].ID, Type: "wechat", Name: "微信公众号", Status: "enabled"}, - {TenantID: tenants[0].ID, Type: "app", Name: "APP内嵌", Status: "disabled"}, + // 套餐定价 + var planCount int64 + model.DB.Model(&model.Plan{}).Count(&planCount) + if planCount == 0 { + plans := []model.Plan{ + {Name: "基础版", PriceMonthly: 299, Seats: 2, StorageDays: 30, KBLimit: 50, Status: "active", Features: `{"stats":"basic","api":false,"channels":["web"],"brand":false}`}, + {Name: "专业版", PriceMonthly: 699, Seats: 10, StorageDays: 180, KBLimit: 500, Status: "active", Features: `{"stats":"advanced","api":true,"channels":["web","wechat","app"],"brand":true}`}, + {Name: "企业版", PriceMonthly: 1999, Seats: 50, StorageDays: 0, KBLimit: 0, Status: "active", Features: `{"stats":"custom","api":true,"channels":["all"],"brand":true,"dedicated":true}`}, + } + model.DB.Create(&plans) + log.Println("已初始化默认套餐:基础版 / 专业版 / 企业版") + } else { + log.Println("套餐已存在,跳过") } - model.DB.Create(&channels) - - // Knowledge categories(两级树,对齐设计稿) - tid := tenants[0].ID - rootFAQ := model.Category{TenantID: tid, Name: "产品常见问题"} - rootAfter := model.Category{TenantID: tid, Name: "售后服务"} - rootTech := model.Category{TenantID: tid, Name: "技术支持"} - rootPolicy := model.Category{TenantID: tid, Name: "政策条款"} - rootQuick := model.Category{TenantID: tid, Name: "快捷回复模板"} - model.DB.Create(&rootFAQ) - model.DB.Create(&rootAfter) - model.DB.Create(&rootTech) - model.DB.Create(&rootPolicy) - model.DB.Create(&rootQuick) - subAccount := model.Category{TenantID: tid, Name: "账户相关", ParentID: &rootFAQ.ID} - subFeature := model.Category{TenantID: tid, Name: "功能使用", ParentID: &rootFAQ.ID} - subBilling := model.Category{TenantID: tid, Name: "计费问题", ParentID: &rootFAQ.ID} - model.DB.Create(&subAccount) - model.DB.Create(&subFeature) - model.DB.Create(&subBilling) - entries := []model.KnowledgeEntry{ - {TenantID: tid, CategoryID: subAccount.ID, Title: "如何修改登录密码", Content: "登录后在右上角头像→个人设置→修改密码。也可通过手机验证码或邮箱链接重置。", Status: "published", UsageCount: 156}, - {TenantID: tid, CategoryID: subBilling.ID, Title: "支持哪些支付方式", Content: "支持微信支付、支付宝、银行转账", Status: "published", UsageCount: 98}, - {TenantID: tid, CategoryID: subFeature.ID, Title: "如何升级为高级会员", Content: "进入账户设置页面,选择会员升级选项,支持月付和年付两种方式,年付享8折优惠", Status: "published", UsageCount: 88}, - {TenantID: tid, CategoryID: rootAfter.ID, Title: "退货流程说明", Content: "在线申请→审核→寄回→退款,全程3-5个工作日", Status: "published", UsageCount: 45}, - {TenantID: tid, CategoryID: rootTech.ID, Title: "API接口文档", Content: "开发者文档请访问 docs.example.com/api", Status: "draft", UsageCount: 0}, - {TenantID: tid, CategoryID: rootPolicy.ID, Title: "隐私政策更新说明", Content: "我们会定期更新隐私政策,重大变更将通过站内信通知", Status: "published", UsageCount: 12}, - {TenantID: tid, CategoryID: rootQuick.ID, Title: "欢迎语模板", Content: "您好!欢迎来到客服云,请问有什么可以帮您的?", Status: "published", UsageCount: 230}, - } - model.DB.Create(&entries) - - // 客户标签库(管理员维护,坐席选择) - customerTags := []model.CustomerTag{ - {TenantID: tid, Name: "VIP客户", Color: "amber", SortOrder: 0}, - {TenantID: tid, Name: "新客户", Color: "green", SortOrder: 1}, - {TenantID: tid, Name: "活跃", Color: "blue", SortOrder: 2}, - {TenantID: tid, Name: "沉默", Color: "orange", SortOrder: 3}, - {TenantID: tid, Name: "企业客户", Color: "cyan", SortOrder: 4}, - } - model.DB.Create(&customerTags) - - // 团队快捷回复(独立模块;知识库「快捷回复模板」分类可逐步弃用) - quickReplies := []model.QuickReply{ - {TenantID: tid, Scope: "team", Title: "打招呼", Content: "您好!欢迎来到客服云,请问有什么可以帮您的?", Shortcut: "nh", GroupName: "通用", Status: "published", UsageCount: 120}, - {TenantID: tid, Scope: "team", Title: "稍等查询", Content: "您好,正在为您查询,请稍候。", Shortcut: "sd", GroupName: "通用", Status: "published", UsageCount: 80}, - {TenantID: tid, Scope: "team", Title: "提供订单号", Content: "为更快处理,请您提供订单号或相关截图,谢谢。", Shortcut: "ddh", GroupName: "售后", Status: "published", UsageCount: 56}, - {TenantID: tid, Scope: "team", Title: "结束语", Content: "感谢您的咨询,祝您生活愉快!如有其它问题随时联系我们。", Shortcut: "js", GroupName: "通用", Status: "draft", UsageCount: 0}, - } - model.DB.Create(&quickReplies) - - // Announcements - model.DB.Create(&[]model.Announcement{ - {Title: "系统维护通知", Content: "平台将于7月20日 02:00-04:00 进行例行维护", Status: "published"}, - {Title: "新功能上线", Content: "知识库批量导入功能已上线", Status: "published"}, - }) - - log.Println("默认租户管理员: kefu_admin / kefu_admin123") - log.Println("其它种子账号密码均为: kefu_admin123") } - -func ptr[T any](v T) *T { return &v }