基础项目
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:
2026-04-26 15:32:21 +08:00
parent cc40d743cb
commit 1e33640629
102 changed files with 4088 additions and 197 deletions

View 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["页面内功能:查看点赞关系"]
```
## 不单独成页
- 书籍作者关系:不做独立管理菜单,不做独立一级页面;在 `书籍管理` 的书籍详情/编辑中作为作者绑定与排序功能维护。

View 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
);

View File

@@ -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 生成。
- 已确认生产环境是否关闭自动迁移,并形成团队约定。