基础项目
Some checks failed
CI / init (pull_request) Has been cancelled
CI / Frontend node 18.16.0 (pull_request) Has been cancelled
CI / Backend go (1.22) (pull_request) Has been cancelled
CI / release-pr (pull_request) Has been cancelled
CI / devops-test (1.22, 18.16.0) (pull_request) Has been cancelled
CI / release-please (pull_request) Has been cancelled
CI / devops-prod (1.22, 18.x) (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
Some checks failed
CI / init (pull_request) Has been cancelled
CI / Frontend node 18.16.0 (pull_request) Has been cancelled
CI / Backend go (1.22) (pull_request) Has been cancelled
CI / release-pr (pull_request) Has been cancelled
CI / devops-test (1.22, 18.16.0) (pull_request) Has been cancelled
CI / release-please (pull_request) Has been cancelled
CI / devops-prod (1.22, 18.x) (pull_request) Has been cancelled
CI / docker (pull_request) Has been cancelled
This commit is contained in:
181
server/.ai-transition/admin-web-prd/book-admin-prd.md
Normal file
181
server/.ai-transition/admin-web-prd/book-admin-prd.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# 书籍后台管理页面需求
|
||||
|
||||
## 说明
|
||||
|
||||
- 本文档面向前端 `admin` 后台页面实现。
|
||||
- 只描述菜单、页面跳转、页面内功能和弹出框关系。
|
||||
- 通用 CRUD 能力不重复描述,按后台管理通用列表页、详情页、编辑页能力实现。
|
||||
- 纯关联表不单独做管理菜单;例如 `书籍作者关系` 在 `书籍管理` 的书籍编辑/详情中维护。
|
||||
|
||||
## 一级菜单
|
||||
|
||||
- 书籍管理
|
||||
|
||||
## 一级页面清单
|
||||
|
||||
- 书籍管理
|
||||
- 章节管理
|
||||
- 作者管理
|
||||
- 系列管理
|
||||
- 评论管理
|
||||
- 阅读记录管理
|
||||
- 收藏记录管理
|
||||
- 评论点赞记录管理
|
||||
|
||||
## 书籍管理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["一级页面:书籍管理"]
|
||||
|
||||
A --> B1["功能跳转:书籍详情页"]
|
||||
A --> B2["功能跳转:书籍编辑页"]
|
||||
|
||||
B1 --> C1["页面内功能:查看基础信息"]
|
||||
B1 --> C2["页面内功能:查看状态信息"]
|
||||
B1 --> C3["页面内功能:查看关联作者"]
|
||||
B1 --> C4["页面内功能:查看关联章节"]
|
||||
B1 --> C5["页面内功能:查看关联评论"]
|
||||
|
||||
B2 --> C6["页面内功能:维护基础信息"]
|
||||
B2 --> C7["页面内功能:维护类型/标签"]
|
||||
B2 --> C8["页面内功能:维护系列归属"]
|
||||
B2 --> C9["页面内功能:维护作者绑定"]
|
||||
B2 --> C10["页面内功能:维护作者排序"]
|
||||
|
||||
C8 --> D1["弹出框:选择系列"]
|
||||
C9 --> D2["弹出框:选择作者"]
|
||||
C10 --> D3["弹出框:调整作者排序"]
|
||||
|
||||
A --> C11["页面内功能:上下架状态调整"]
|
||||
A --> C12["页面内功能:完结状态调整"]
|
||||
C11 --> D4["弹出框:状态调整确认"]
|
||||
C12 --> D5["弹出框:状态调整确认"]
|
||||
|
||||
X["不独立成页:书籍作者关系"] -.-> C9
|
||||
X -.-> C10
|
||||
```
|
||||
|
||||
## 章节管理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["一级页面:章节管理"]
|
||||
|
||||
A --> B1["功能跳转:章节详情页"]
|
||||
A --> B2["功能跳转:章节编辑页"]
|
||||
|
||||
B1 --> C1["页面内功能:查看章节基础信息"]
|
||||
B1 --> C2["页面内功能:查看章节内容/文件信息"]
|
||||
B1 --> C3["页面内功能:查看所属书籍"]
|
||||
|
||||
B2 --> C4["页面内功能:维护章节基础信息"]
|
||||
B2 --> C5["页面内功能:维护章节内容/文件"]
|
||||
B2 --> C6["页面内功能:维护所属书籍"]
|
||||
|
||||
C6 --> D1["弹出框:选择所属书籍"]
|
||||
|
||||
A --> C7["页面内功能:章节开放/发布状态调整"]
|
||||
C7 --> D2["弹出框:状态调整确认"]
|
||||
```
|
||||
|
||||
## 作者管理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["一级页面:作者管理"]
|
||||
|
||||
A --> B1["功能跳转:作者详情页"]
|
||||
A --> B2["功能跳转:作者编辑页"]
|
||||
|
||||
B1 --> C1["页面内功能:查看作者基础信息"]
|
||||
B1 --> C2["页面内功能:查看作者状态"]
|
||||
B1 --> C3["页面内功能:查看关联书籍"]
|
||||
|
||||
B2 --> C4["页面内功能:维护作者基础信息"]
|
||||
|
||||
A --> C5["页面内功能:作者状态调整"]
|
||||
C5 --> D1["弹出框:状态调整确认"]
|
||||
```
|
||||
|
||||
## 系列管理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["一级页面:系列管理"]
|
||||
|
||||
A --> B1["功能跳转:系列详情页"]
|
||||
A --> B2["功能跳转:系列编辑页"]
|
||||
|
||||
B1 --> C1["页面内功能:查看系列基础信息"]
|
||||
B1 --> C2["页面内功能:查看系列状态"]
|
||||
B1 --> C3["页面内功能:查看系列下书籍"]
|
||||
|
||||
B2 --> C4["页面内功能:维护系列基础信息"]
|
||||
|
||||
A --> C5["页面内功能:系列启停/展示状态调整"]
|
||||
C5 --> D1["弹出框:状态调整确认"]
|
||||
```
|
||||
|
||||
## 评论管理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["一级页面:评论管理"]
|
||||
|
||||
A --> B1["功能跳转:评论详情页"]
|
||||
|
||||
B1 --> C1["页面内功能:查看评论内容"]
|
||||
B1 --> C2["页面内功能:查看评论锚点"]
|
||||
B1 --> C3["页面内功能:查看关联书籍"]
|
||||
B1 --> C4["页面内功能:查看关联章节"]
|
||||
B1 --> C5["页面内功能:查看用户信息"]
|
||||
|
||||
A --> C6["页面内功能:评论状态处理"]
|
||||
C6 --> D1["弹出框:状态处理确认"]
|
||||
```
|
||||
|
||||
## 阅读记录管理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["一级页面:阅读记录管理"]
|
||||
|
||||
A --> B1["功能跳转:阅读记录详情页"]
|
||||
|
||||
B1 --> C1["页面内功能:查看用户信息"]
|
||||
B1 --> C2["页面内功能:查看书籍信息"]
|
||||
B1 --> C3["页面内功能:查看续读锚点"]
|
||||
B1 --> C4["页面内功能:查看章节进度"]
|
||||
```
|
||||
|
||||
## 收藏记录管理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["一级页面:收藏记录管理"]
|
||||
|
||||
A --> B1["功能跳转:收藏记录详情页"]
|
||||
|
||||
B1 --> C1["页面内功能:查看用户信息"]
|
||||
B1 --> C2["页面内功能:查看书籍信息"]
|
||||
B1 --> C3["页面内功能:查看收藏关系"]
|
||||
```
|
||||
|
||||
## 评论点赞记录管理
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["一级页面:评论点赞记录管理"]
|
||||
|
||||
A --> B1["功能跳转:评论点赞记录详情页"]
|
||||
|
||||
B1 --> C1["页面内功能:查看用户信息"]
|
||||
B1 --> C2["页面内功能:查看评论信息"]
|
||||
B1 --> C3["页面内功能:查看点赞关系"]
|
||||
```
|
||||
|
||||
## 不单独成页
|
||||
|
||||
- 书籍作者关系:不做独立管理菜单,不做独立一级页面;在 `书籍管理` 的书籍详情/编辑中作为作者绑定与排序功能维护。
|
||||
|
||||
132
server/.ai-transition/database-upgrade-doc/v1.sql
Normal file
132
server/.ai-transition/database-upgrade-doc/v1.sql
Normal file
@@ -0,0 +1,132 @@
|
||||
-- API 权限分配接口初始化补齐 / sys_apis, casbin_rule / 2026-04-25
|
||||
|
||||
INSERT INTO sys_apis (created_at, updated_at, path, description, api_group, method)
|
||||
SELECT NOW(), NOW(), '/api/getApiRoles', '获取API关联角色ID列表', 'api', 'GET'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_apis WHERE path = '/api/getApiRoles' AND method = 'GET'
|
||||
);
|
||||
|
||||
INSERT INTO sys_apis (created_at, updated_at, path, description, api_group, method)
|
||||
SELECT NOW(), NOW(), '/api/setApiRoles', '全量覆盖API关联角色', 'api', 'POST'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_apis WHERE path = '/api/setApiRoles' AND method = 'POST'
|
||||
);
|
||||
|
||||
INSERT INTO casbin_rule (ptype, v0, v1, v2)
|
||||
SELECT 'p', '888', '/api/getApiRoles', 'GET'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM casbin_rule
|
||||
WHERE ptype = 'p' AND v0 = '888' AND v1 = '/api/getApiRoles' AND v2 = 'GET'
|
||||
);
|
||||
|
||||
INSERT INTO casbin_rule (ptype, v0, v1, v2)
|
||||
SELECT 'p', '888', '/api/setApiRoles', 'POST'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM casbin_rule
|
||||
WHERE ptype = 'p' AND v0 = '888' AND v1 = '/api/setApiRoles' AND v2 = 'POST'
|
||||
);
|
||||
|
||||
-- book 业务字典初始化补齐 / sys_dictionaries, sys_dictionary_details / 2026-04-26
|
||||
|
||||
WITH dict_seed(name, type, status, description) AS (
|
||||
VALUES
|
||||
('作者状态', 'book_author_status', true, '作者状态字典'),
|
||||
('书籍评论状态', 'book_comment_status', true, '书籍评论状态字典'),
|
||||
('书籍完结状态', 'book_completion_status', true, '书籍完结状态字典'),
|
||||
('书籍时代标签', 'book_era_tag', true, '书籍时代标签字典'),
|
||||
('书籍上下架状态', 'book_publish_status', true, '书籍上下架状态字典'),
|
||||
('书籍类型', 'book_type', true, '书籍类型动态字典')
|
||||
)
|
||||
UPDATE sys_dictionaries d
|
||||
SET name = s.name,
|
||||
status = s.status,
|
||||
"desc" = s.description,
|
||||
updated_at = NOW(),
|
||||
deleted_at = NULL
|
||||
FROM dict_seed s
|
||||
WHERE d.type = s.type;
|
||||
|
||||
WITH dict_seed(name, type, status, description) AS (
|
||||
VALUES
|
||||
('作者状态', 'book_author_status', true, '作者状态字典'),
|
||||
('书籍评论状态', 'book_comment_status', true, '书籍评论状态字典'),
|
||||
('书籍完结状态', 'book_completion_status', true, '书籍完结状态字典'),
|
||||
('书籍时代标签', 'book_era_tag', true, '书籍时代标签字典'),
|
||||
('书籍上下架状态', 'book_publish_status', true, '书籍上下架状态字典'),
|
||||
('书籍类型', 'book_type', true, '书籍类型动态字典')
|
||||
)
|
||||
INSERT INTO sys_dictionaries (created_at, updated_at, name, type, status, "desc")
|
||||
SELECT NOW(), NOW(), s.name, s.type, s.status, s.description
|
||||
FROM dict_seed s
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_dictionaries d WHERE d.type = s.type
|
||||
);
|
||||
|
||||
WITH detail_seed(dict_type, label, value, sort, status) AS (
|
||||
VALUES
|
||||
('book_author_status', '启用', 'enabled', 10, true),
|
||||
('book_author_status', '禁用', 'disabled', 20, true),
|
||||
('book_comment_status', '正常', 'normal', 10, true),
|
||||
('book_comment_status', '隐藏', 'hidden', 20, true),
|
||||
('book_completion_status', '完结', 'completed', 10, true),
|
||||
('book_completion_status', '连载', 'serializing', 20, true),
|
||||
('book_era_tag', '未知时代', 'unknown', 10, true),
|
||||
('book_era_tag', '远古', 'ancient', 20, true),
|
||||
('book_era_tag', '汉', 'han', 30, true),
|
||||
('book_era_tag', '唐', 'tang', 40, true),
|
||||
('book_era_tag', '宋', 'song', 50, true),
|
||||
('book_era_tag', '元', 'yuan', 60, true),
|
||||
('book_era_tag', '明', 'ming', 70, true),
|
||||
('book_era_tag', '清', 'qing', 80, true),
|
||||
('book_era_tag', '近代', 'modern', 90, true),
|
||||
('book_era_tag', '现代', 'contemporary', 100, true),
|
||||
('book_publish_status', '草稿', 'draft', 10, true),
|
||||
('book_publish_status', '下架', 'off_shelf', 20, true),
|
||||
('book_publish_status', '上架', 'on_shelf', 30, true)
|
||||
)
|
||||
UPDATE sys_dictionary_details d
|
||||
SET label = s.label,
|
||||
sort = s.sort,
|
||||
status = s.status,
|
||||
extend = '',
|
||||
level = 0,
|
||||
path = '',
|
||||
updated_at = NOW(),
|
||||
deleted_at = NULL
|
||||
FROM detail_seed s
|
||||
JOIN sys_dictionaries dict ON dict.type = s.dict_type
|
||||
WHERE d.sys_dictionary_id = dict.id
|
||||
AND d.value = s.value;
|
||||
|
||||
WITH detail_seed(dict_type, label, value, sort, status) AS (
|
||||
VALUES
|
||||
('book_author_status', '启用', 'enabled', 10, true),
|
||||
('book_author_status', '禁用', 'disabled', 20, true),
|
||||
('book_comment_status', '正常', 'normal', 10, true),
|
||||
('book_comment_status', '隐藏', 'hidden', 20, true),
|
||||
('book_completion_status', '完结', 'completed', 10, true),
|
||||
('book_completion_status', '连载', 'serializing', 20, true),
|
||||
('book_era_tag', '未知时代', 'unknown', 10, true),
|
||||
('book_era_tag', '远古', 'ancient', 20, true),
|
||||
('book_era_tag', '汉', 'han', 30, true),
|
||||
('book_era_tag', '唐', 'tang', 40, true),
|
||||
('book_era_tag', '宋', 'song', 50, true),
|
||||
('book_era_tag', '元', 'yuan', 60, true),
|
||||
('book_era_tag', '明', 'ming', 70, true),
|
||||
('book_era_tag', '清', 'qing', 80, true),
|
||||
('book_era_tag', '近代', 'modern', 90, true),
|
||||
('book_era_tag', '现代', 'contemporary', 100, true),
|
||||
('book_publish_status', '草稿', 'draft', 10, true),
|
||||
('book_publish_status', '下架', 'off_shelf', 20, true),
|
||||
('book_publish_status', '上架', 'on_shelf', 30, true)
|
||||
)
|
||||
INSERT INTO sys_dictionary_details (created_at, updated_at, label, value, extend, status, sort, sys_dictionary_id, level, path)
|
||||
SELECT NOW(), NOW(), s.label, s.value, '', s.status, s.sort, dict.id, 0, ''
|
||||
FROM detail_seed s
|
||||
JOIN sys_dictionaries dict ON dict.type = s.dict_type
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys_dictionary_details d
|
||||
WHERE d.sys_dictionary_id = dict.id
|
||||
AND d.value = s.value
|
||||
);
|
||||
@@ -0,0 +1,86 @@
|
||||
# GORM 自动迁移与升级 SQL 交接说明
|
||||
|
||||
## 背景
|
||||
|
||||
- 当前服务启动时会执行 `main.go -> initialize.RegisterTables() -> initialize.bizModel()`。
|
||||
- `initialize/gorm_biz.go` 中注册了 `book` 业务模型,并调用 `db.AutoMigrate(...)`。
|
||||
- 因此即使没有手动执行 `.sql` 文件,只要服务启动并连接到数据库,`book` 相关业务表也可能被 GORM 自动创建。
|
||||
|
||||
## AutoMigrate 的信息来源
|
||||
|
||||
- GORM 不读取 `.ai-specs/doc-sql/*.sql` 中的基线 SQL。
|
||||
- 表名来自各模型的 `TableName()`。
|
||||
- 字段名来自 Go struct 字段名的 snake_case 映射。
|
||||
- 字段类型、默认值、非空、索引、唯一索引、注释主要来自 `gorm` tag。
|
||||
- 基础字段来自模型嵌入结构,例如 `model/book/base.go` 的 `HardDeleteModel`。
|
||||
|
||||
示例:
|
||||
|
||||
```go
|
||||
Title string `gorm:"type:varchar(255);not null;comment:书名主标题"`
|
||||
BookType string `gorm:"type:varchar(64);not null;index;comment:书籍类型字典值,对应 book_type"`
|
||||
Rating float64 `gorm:"type:numeric(3,1);not null;default:0.0;comment:书籍评分,范围 0-10"`
|
||||
```
|
||||
|
||||
## AutoMigrate 能做什么
|
||||
|
||||
- 新表不存在时,按 Model 创建表。
|
||||
- 新字段不存在时,通常会补充字段。
|
||||
- 新索引不存在时,通常会尝试创建索引。
|
||||
- 部分字段类型、默认值、注释可能会按数据库驱动能力尝试调整。
|
||||
|
||||
## AutoMigrate 不能当作升级 SQL
|
||||
|
||||
- 它不知道旧版本到新版本的业务升级意图。
|
||||
- 它不会可靠删除废弃字段。
|
||||
- 它不会可靠删除废弃索引。
|
||||
- 字段重命名通常会被识别为新增字段,旧字段仍保留。
|
||||
- 字段类型收缩、非空约束、唯一约束、CHECK 约束、历史数据回填等需要人工判断。
|
||||
- 它不能保证生产升级顺序、幂等性、兼容性和数据安全。
|
||||
|
||||
结论:`AutoMigrate` 是开发期快速补结构工具,不是正式数据库版本迁移方案。
|
||||
|
||||
## 升级 SQL 维护规则
|
||||
|
||||
- 修改 `.ai-specs/doc-sql/*.sql` 并导致表结构、字段、索引、约束、默认值或注释变化时,必须同步维护升级 SQL。
|
||||
- 修改 `.ai-specs/doc-dict/*.md` 并导致系统字典主数据或字典项变化时,必须同步维护初始化数据和升级 SQL。
|
||||
- 升级 SQL 固定放在 `.ai-transition/database-upgrade-doc`。
|
||||
- 当前版本号以 `.ai-specs/sys-specs/database-upgrade-doc-spec.md` 的 `当前数据库表结构版本` 为准。
|
||||
- 当前为 `v1` 时,兼容变更写入 `.ai-transition/database-upgrade-doc/v1.sql`。
|
||||
- 升级 SQL 只写从既有数据库升级到当前目标结构的变更,不重复粘贴完整建表 SQL。
|
||||
- 同一次业务变更涉及多张表时,写入同一个当前版本 SQL 文件,并按表分组。
|
||||
- 字典主数据升级按 `sys_dictionaries.type` 防重复。
|
||||
- 字典明细升级按 `sys_dictionary_details.sys_dictionary_id + value` 防重复。
|
||||
- 固定值域字典需要写 `sys_dictionaries` 和 `sys_dictionary_details`;动态值域字典默认只写 `sys_dictionaries`。
|
||||
- 字典升级 SQL 禁止裸 `INSERT`,必须兼容重复执行、手工已建数据和部分明细缺失。
|
||||
- 字典升级 SQL 命中已软删数据时,恢复启用数据必须同步置空 `deleted_at`。
|
||||
|
||||
## 常见变更处理
|
||||
|
||||
- 新增字段:写 `ALTER TABLE ... ADD COLUMN ...`,并补 `COMMENT ON COLUMN`。
|
||||
- 新增 `NOT NULL` 字段:先给默认值或先回填历史数据,再追加 `NOT NULL`。
|
||||
- 修改字段类型:先评估历史数据是否兼容,再写 `ALTER TABLE ... ALTER COLUMN ... TYPE ...`。
|
||||
- 删除字段:必须人工确认无业务读写依赖,再写 `ALTER TABLE ... DROP COLUMN IF EXISTS ...`。
|
||||
- 新增普通索引:写 `CREATE INDEX IF NOT EXISTS idx_<table>_<field> ...`。
|
||||
- 删除索引:写 `DROP INDEX IF EXISTS ...`。
|
||||
- 新增唯一索引:先排查历史重复数据,必要时先清洗,再创建唯一索引。
|
||||
- 修改字段名:优先写 `ALTER TABLE ... RENAME COLUMN ...`,不要依赖 GORM 自动识别。
|
||||
- 修改注释:写 `COMMENT ON TABLE` 或 `COMMENT ON COLUMN`。
|
||||
- 新增固定值域字典:同步修改 `source/system/dictionary.go`、`source/system/dictionary_detail.go` 和当前版本 SQL。
|
||||
- 新增动态值域字典:同步修改 `source/system/dictionary.go` 和当前版本 SQL;没有固定值项时不改 `dictionary_detail.go`。
|
||||
- 修改固定字典项展示信息:升级 SQL 先 `UPDATE` 已有 `value`,再 `INSERT` 缺失项。
|
||||
- 废弃固定字典项:优先将 `status` 改为 `false`,不要直接删除历史值项。
|
||||
|
||||
## 配置建议
|
||||
|
||||
- 开发环境可以保留 `config.yaml` 中 `system.disable-auto-migrate: false`,便于快速补表。
|
||||
- 生产或正式联调环境建议设置 `system.disable-auto-migrate: true`,统一使用 `.ai-transition/database-upgrade-doc/*.sql` 做可审计升级。
|
||||
- 如果生产环境仍开启 `AutoMigrate`,必须接受它可能自动补字段或索引、但不会完整处理删除和复杂变更的风险。
|
||||
|
||||
## 交接检查清单
|
||||
|
||||
- 已确认 `.ai-specs/doc-sql/*.sql` 是目标结构说明,不会被程序自动执行。
|
||||
- 已确认真实自动建表入口是 `initialize/gorm_biz.go` 的 `AutoMigrate`。
|
||||
- 已确认 Model 的 `gorm` tag 是 AutoMigrate 生成 DDL 的主要依据。
|
||||
- 已确认结构升级 SQL 需要人工维护,不能指望 GORM 生成。
|
||||
- 已确认生产环境是否关闭自动迁移,并形成团队约定。
|
||||
Reference in New Issue
Block a user