11 KiB
YMhut Unified Management Backend Architecture Review
结论
server/unified-management 目前已经具备统一服务的基本形态:一个 Go 后端同时承接旧版更新 JSON、下载、媒体源、反馈提交、新客户端 bootstrap、管理后台、SQLite/MySQL 存储和旧项目同步。现有 go test ./... 全部通过,说明核心兼容路由和主要模块有基本回归保障。
但当前实现仍属于“快速整合型单体”:internal/web/router.go 和 internal/db/store.go 承担了过多职责,后台能力已经铺开,但业务边界、数据访问边界、兼容适配边界还不够清晰。继续扩展前,应先把后台架构拆成稳定的分层单体,保留旧访问方式和旧反馈协议作为独立兼容层。
当前主要问题
-
路由层过重
internal/web/router.go同时处理公共门户、旧版路由、客户端 API、后台 API、静态资源、错误本地化、SSE、CSV 导出和文件下载。后续新增后台功能时,容易出现路径匹配顺序冲突、权限遗漏和响应结构不一致。 -
数据层过重
internal/db/store.go同时包含表结构迁移、实体定义、Repository、密码哈希、业务默认值、统计聚合、SQLite/MySQL failover、全表同步和旧原型 JSON 导入。它已经变成“全系统共享内核”,任何改动都容易影响多个业务域。 -
兼容逻辑与新业务逻辑混杂
旧版
update-info.json、media-types.json、tool-status.json、modules.json、/downloads/*和旧反馈POST /都是必须保留的外部契约,但现在它们和新 API 共享服务/存储细节,长期看会让新后台被旧 JSON 格式牵着走。 -
安全策略需要后台化
当前有验证码、HttpOnly session cookie、CSRF、上传包校验和路径逃逸防护,这是好基础。但后台还需要更明确的密码策略、登录失败限流、会话持久化/吊销、审计事件规范、管理员角色模型。当前密码哈希是静态 SHA-256,建议迁移到 Argon2id 或 bcrypt。
-
数据库双写/同步语义不清
配置里有 failover 和 hot sync 字段,但当前更接近手动全表复制和运行时 MySQL 失败回退 SQLite。需要明确“单主数据库、备份数据库、灾备同步”的关系,否则后台管理员很难判断哪些操作会覆盖数据。
-
前端后台状态集中
web/admin/src/App.vue集中了 API client、页面路由、全局状态、业务动作、表单转换、错误翻译和 SSE 连接。后台页面已经很多,建议拆成api/、stores/、features/,否则维护体验会快速变差。
推荐后台架构
保持 Go 单体部署,但按边界拆成分层模块:
cmd/unified-management
app/ # 进程启动、依赖组装、优雅退出
internal/http
middleware/ # auth、csrf、security headers、request id、rate limit
legacy/ # 旧访问方式适配器
clientapi/ # 新客户端 API
adminapi/ # 后台管理 API
setupapi/ # 首次初始化 API
static/ # admin/portal/setup 前端资源
internal/domain
feedback/ # 反馈工单聚合、状态流转、附件规则
releases/ # 发布包、版本公告、manifest
sources/ # 数据源目录、健康检查、调用日志
compatibility/ # 旧 JSON 文档模型和转换
audit/ # 审计事件
admin/ # 管理员、角色、会话
system/ # 健康检查、配置、数据库状态
internal/storage
migrations/ # schema 和版本迁移
repos/ # 按领域拆分 Repository
sqlstore/ # SQLite/MySQL 方言、连接、事务
sync/ # SQLite/MySQL 同步策略
internal/jobs
sourcecheck/ # 数据源健康检测
legacysync/ # 旧项目导入/同步
cleanup/ # 过期会话、旧日志、临时文件清理
internal/contracts
legacy/ # 旧客户端响应 DTO
client/ # 新客户端响应 DTO
admin/ # 后台响应 DTO
核心原则:
legacy只负责旧协议输入输出,不直接写业务表细节。domain负责业务规则,不依赖 HTTP request/response。storage负责持久化和事务,不包含业务默认文案、状态流转和 UI 语义。adminapi是后台编排层,只调用 domain service,不直接拼 SQL。- 所有对外 JSON 响应都定义 DTO,避免直接暴露数据库结构。
必须保留的旧版访问契约
这些路径应作为长期兼容 API,不随后台重构而改变:
| 旧版入口 | 当前用途 | 建议归属 |
|---|---|---|
GET /update-info.json、GET /update-info |
旧客户端更新信息 | internal/http/legacy/update.go |
GET /tool-status.json、GET /tool-status |
旧工具状态 | internal/http/legacy/static_json.go |
GET /modules.json、GET /modules、GET /api/modules |
旧模块配置 | internal/http/legacy/static_json.go |
GET /media-types.json、GET /media-types |
旧媒体源目录 | internal/http/legacy/media_types.go |
GET /downloads/:filename |
旧下载包 | internal/http/legacy/downloads.go |
POST / |
旧反馈提交 | internal/http/legacy/feedback.go |
GET /?api=status&code=:code |
旧反馈状态查询 | internal/http/legacy/feedback.go |
兼容层的响应字段应保持“只增不删、不改名、不改含义”。新后台可以增加内部字段,但旧接口输出必须通过 legacy DTO 过滤,避免把后台工单详情、内部备注、附件路径等泄漏给旧客户端。
旧版反馈兼容设计
旧反馈应当分为三个入口模型:
-
简单 JSON 表单
兼容旧客户端或网页直接提交
title/type/severity/contact/body/message。服务端生成反馈码,返回旧状态结构。 -
普通 multipart 表单
兼容旧表单字段
title/subject/category/priority/message/description/email。如果没有签名字段,按简单反馈处理。 -
签名加密包 multipart
继续保留
payload/timestamp/nonce/packageSha256/signature/package。校验顺序固定为:请求大小 -> 时间窗 -> SHA256 格式 -> HMAC 签名 -> 加密包 magic -> 包哈希 -> 解密 -> zip 安全检查 -> 写入附件 -> 创建工单。
后台内部统一落到 feedback_tickets 聚合:
legacy feedback request
-> LegacyFeedbackAdapter
-> FeedbackCommand(CreateTicket)
-> FeedbackService
-> FeedbackRepository + AttachmentStorage
-> LegacyFeedbackStatusDTO
旧状态查询只返回:
okcodestatusstatusLabelstatusDetailcategorypriorityhasReplyreplyreceivedAtupdatedAtmailSentduplicate
不返回内部 note、assignee、handledBy、本地文件路径、审计日志、mail record。
后台管理能力设计
后台建议分为以下域:
-
仪表盘
提供反馈数量、今日反馈、数据源总数、可见源数量、发布版本、数据库状态、最近心跳、最近客户端调用。只读,适合高频刷新。
-
反馈工单
支持分页、筛选、搜索、状态流转、公开回复、内部备注、评论、标签、分派、优先级、SLA、附件查看、CSV 导出。状态流转应集中在
FeedbackService,不要由 Store 直接决定。 -
发布管理
管理发布包上传、版本号、平台/架构、SHA256、manifest 生成、
update-info.json同步、版本公告保存、公告历史恢复。 -
兼容 JSON
管理
update-info.json和media-types.json,提供验证、预览、保存、修订历史、恢复。该模块属于兼容层后台,不应成为新客户端的唯一数据来源。 -
数据源目录
管理媒体/数据源、健康检测、客户端可见性、缓存时间、代理策略、调用日志。健康检测任务应进入 jobs 模块,并持久化任务结果。
-
数据库与同步
明确 SQLite/MySQL 角色:推荐 SQLite 单机默认,MySQL 生产主库,SQLite 作为本地备份。后台按钮应显示方向、影响范围、覆盖风险和最后同步结果。
-
系统设置
管理管理员密码、会话、旧项目路径、BaseURL、上传限制、签名密钥轮换、服务健康。
-
审计日志
所有后台写操作、旧项目同步、发布包上传、JSON 恢复、密码修改都写入审计。审计事件统一字段:
actor/type/target/message/ip/userAgent/createdAt。
数据模型建议
将数据库实体按业务域拆分:
admin_usersadmin_sessionsfeedback_ticketsfeedback_commentsfeedback_attachmentsfeedback_eventsrelease_packagesrelease_noticesrelease_notice_revisionslegacy_json_revisionssource_categoriessource_endpointsendpoint_health_checksendpoint_call_logsaudit_logsdatabase_sync_jobslegacy_sync_jobs
建议新增:
schema_migrations:记录数据库迁移版本。admin_roles、admin_user_roles:为后续多管理员预留。settings:保存可后台修改的配置项,和config.json做边界区分。background_jobs:统一记录旧同步、源检查、清理任务。api_tokens:为未来自动发布、CI 上传包、客户端管理接口预留。
改造优先级
第一阶段:稳住兼容契约
- 为旧版路径建立专门测试:
/update-info.json、/tool-status.json、/modules.json、/media-types.json、/downloads/*、POST /、/?api=status。 - 把旧响应结构固定为 DTO 测试快照。
- 给旧反馈三种提交方式分别补测试。
第二阶段:拆 HTTP 层
- 把
router.go拆成legacy_routes.go、client_routes.go、admin_feedback_routes.go、admin_release_routes.go、admin_source_routes.go、admin_system_routes.go、static_routes.go。 - 引入统一
RouteGroup或http.ServeMux包装,避免一个巨大 switch 继续增长。
第三阶段:拆 Store
- 将实体移动到
internal/domain/*或internal/contracts/*。 - 将 SQL 拆成
FeedbackRepository、ReleaseRepository、SourceRepository、AuditRepository、AdminRepository。 - 将迁移、连接、failover、sync 从业务 repo 中拆出。
第四阶段:完善后台安全
- 密码哈希迁移到 Argon2id/bcrypt,并保留旧 SHA-256 登录后自动升级。
- 登录失败限流和验证码刷新频率限制。
- 会话持久化、会话吊销、Secure cookie 配置。
- 敏感配置和密钥不在 bootstrap 或日志中明文输出。
第五阶段:前端后台模块化
web/admin/src/api/*:封装后台 API。web/admin/src/stores/*:按域拆状态。web/admin/src/features/*:按页面拆业务动作。App.vue只保留壳层、导航、路由出口和全局 toast。
验证清单
当前已通过:
go test ./...
后续每次重构必须至少验证:
- 旧版更新 JSON 路由仍返回 200 且字段不减少。
- 旧版反馈
POST /能创建工单,重复反馈码返回 duplicate。 - 旧版反馈状态查询不泄漏内部字段。
- 管理后台写操作未登录返回 401,未带 CSRF 返回 403。
- 发布包上传拒绝路径逃逸和不支持扩展名。
- 数据源健康检测拒绝非 HTTP/HTTPS 重定向。
- SQLite 默认启动正常,MySQL 配置失败时 failover 行为明确。
- 旧项目同步 dry-run 不写数据,run 前有备份。