feat: 新增文档源和技能管理相关功能

1. 新增文档源管理模块(documentSource)
   - 控制器:documentSourceController.py
   - DAO层:documentSourceDao.py
   - 模型:documentSourceModel.py
   - 服务层:documentSourceService.py

2. 新增技能管理模块(skill)
   - 控制器:skillController.py
   - DAO层:skillDao.py
   - 模型:skillModel.py
   - 服务层:skillService.py

3. 新增AI服务(aiService.py)

4. 新增配置文件
   - AI配置:config/ai_config.py
   - 技能配置:config/skills/test-case-generator/

5. 新增SQL脚本
   - 文档权限:add_document_permissions.sql
   - 模块状态字段:add_module_status_field.sql
   - 文档源表:create_document_source_table.sql
   - 技能规则:skills_rules_pgsql.sql
This commit is contained in:
qiaoxinjiu
2026-05-18 10:23:07 +08:00
parent 65524de6fc
commit 420b9e37fa
38 changed files with 9613 additions and 0 deletions

View File

@@ -0,0 +1,608 @@
# 测试 Skills 与业务规则接口文档
## 1. 基础说明
接口前缀:`/it/api`
统一响应:
```json
{
"code": 20000,
"msg": "success",
"data": {}
}
```
鉴权:需要登录态 token。
建议请求头:
```http
Authorization: Bearer ${token}
```
也兼容:
```http
accessToken: ${token}
```
***
## 2. 枚举
### 2.1 Skill 类型 skill\_type
| 值 | 含义 |
| -- | ------ |
| 1 | 通用测试策略 |
| 2 | 历史缺陷模式 |
| 3 | 边界场景 |
| 4 | 接口测试 |
| 5 | UI 测试 |
| 6 | 性能测试 |
| 7 | 安全测试 |
| 8 | 数据一致性 |
| 9 | 并发/幂等 |
| 99 | 其他 |
### 2.2 风险等级 risk\_level
| 值 | 含义 |
| - | ---- |
| 0 | 高风险 |
| 1 | 中高风险 |
| 2 | 中风险 |
| 3 | 低风险 |
### 2.3 业务规则优先级 priority
| 值 | 含义 |
| - | ----- |
| 0 | 高优先级 |
| 1 | 中高优先级 |
| 2 | 中优先级 |
| 3 | 低优先级 |
### 2.4 状态 status
| 值 | 含义 |
| - | -- |
| 1 | 启用 |
| 2 | 停用 |
| 3 | 草稿 |
***
## 3. Skill 接口
### 3.1 创建 Skill
```http
POST /it/api/skill/create
```
权限:`skill:create`
请求体:
```json
{
"projectId": 1,
"moduleId": 10,
"name": "支付金额边界校验",
"description": "用于支付金额相关需求的边界测试生成",
"skillType": 3,
"riskLevel": 0,
"tags": ["支付", "金额", "边界"],
"status": 1
}
```
参数:
| 字段 | 类型 | 必填 | 说明 |
| ------------- | ----------- | ---- | ---- |
| projectId | number | 是 | 项目 ID |
| moduleId | number | 否 | 模块 ID |
| name | string | 是 | Skill 名称 |
| description | string | 否 | 用户补充描述,会作为大模型生成 Skill 内容的输入 |
| skillType | number | 否 | Skill 类型,未传时默认 1大模型也可能根据内容修正 |
| riskLevel | number | 否 | 风险等级,未传时默认 2大模型也可能根据内容修正 |
| tags | string\[] | 否 | 初始标签数组,大模型可能补全 |
| status | number | 否 | 状态,默认 1 |
说明:
- `code` 不需要前端传,后端自动生成项目内唯一编码。
- `triggerCondition` 不需要前端传,后端默认使用当前 AI 生成用例的触发条件。
- `outputSpec` 不需要前端传,后端默认使用当前 AI 生成用例的输出规范。
- `reasoningPath` 不需要前端传,后端会调用大模型并根据 `config/skills/skill-creator/SKILL.md` 规则生成。
- `ownerId` 不需要前端传,后端默认取当前登录人 ID。
- 创建成功后,后端会在 `config/skills/{产品名称}/{项目名称}/{模块名称}/{Skill名称}/SKILL.md` 生成 Skill 文件。
- 数据库会保存生成文件路径到 `skill_file_path`
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"id": 1
}
}
```
常见失败:
```json
{
"code": 40009,
"msg": "projectId、name 为必传参数"
}
```
```json
{
"code": 40009,
"msg": "AI生成 Skill 内容失败: xxx"
}
```
***
### 3.2 更新 Skill
```http
POST /it/api/skill/update
```
权限:`skill:update`
请求体:
```json
{
"skillId": 1,
"name": "支付金额边界校验",
"description": "更新后的描述",
"triggerCondition": "更新后的触发条件",
"reasoningPath": "更新后的推理路径",
"outputSpec": "更新后的输出规范",
"skillType": 3,
"riskLevel": 0,
"tags": ["支付", "金额", "边界", "参数校验"],
"status": 1,
"ownerId": 8
}
```
说明:
- 当前接口不支持更新 `code`
- 更新成功后,后端会根据更新后的 Skill 内容重新创建 `SKILL.md` 文件。
- 新文件路径会同步更新到数据库 `skill_file_path`
- 数据库更新成功后会删除原 Skill 文件夹。
- 如果数据库更新失败,后端会删除新创建的文件夹并保留旧数据库记录和旧文件,避免数据库与文件不一致。
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"id": 1
}
}
```
***
### 3.3 删除 Skill
```http
POST /it/api/skill/delete
```
权限:`skill:delete`
请求体:
```json
{
"skillId": 1
}
```
说明:软删除,设置 `is_delete = 1`,并删除该 Skill 对应的 `config/skills/{产品名称}/{项目名称}/{模块名称}/{Skill名称}` 文件夹。
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"id": 1
}
}
```
***
### 3.4 Skill 详情
```http
GET /it/api/skill/detail?skillId=1
```
权限:`skill:detail`
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"id": 1,
"project_id": 1,
"module_id": 10,
"name": "支付金额边界校验",
"code": "PAY_AMOUNT_BOUNDARY",
"description": "用于支付金额相关需求的边界测试生成",
"trigger_condition": "需求中出现支付、金额、扣款、退款、余额等关键词时触发",
"reasoning_path": "识别金额字段构造最小值、最大值、0、负数、小数精度、超限金额等场景",
"output_spec": "必须覆盖正常金额、0元、负数、超大金额、小数精度、余额不足",
"skill_file_path": "D:\\zhyy\\effekt-interface\\config\\skills\\产品A\\项目A\\支付模块\\支付金额边界校验\\SKILL.md",
"skill_type": 3,
"risk_level": 0,
"tags": ["支付", "金额", "边界"],
"status": 1,
"owner_id": 8,
"created_by": 6,
"usage_count": 0,
"is_delete": 0,
"created_time": "2025-09-20 12:00:00",
"updated_time": "2025-09-20 12:00:00"
}
}
```
***
### 3.5 Skill 列表
```http
GET /it/api/skill/list
```
权限:`skill:list`
Query 参数:
| 参数 | 类型 | 必填 | 说明 |
| --------- | ------ | -- | ------------------------------------------- |
| pageNo | number | 否 | 页码,默认 1 |
| pageSize | number | 否 | 每页数量,默认 20 |
| projectId | number | 否 | 项目 ID |
| moduleId | number | 否 | 模块 ID |
| status | number | 否 | 状态 |
| skillType | number | 否 | Skill 类型 |
| riskLevel | number | 否 | 风险等级 |
| keyword | string | 否 | 搜索 name/code/description/trigger\_condition |
| tag | string | 否 | 单个标签过滤 |
请求示例:
```http
GET /it/api/skill/list?pageNo=1&pageSize=20&projectId=1&moduleId=10&keyword=&status=1
```
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"list": [
{
"id": 1,
"project_id": 1,
"module_id": 10,
"name": "支付金额边界校验",
"code": "PAY_AMOUNT_BOUNDARY",
"description": "用于支付金额相关需求的边界测试生成",
"trigger_condition": "需求中出现支付、金额、扣款、退款、余额等关键词时触发",
"reasoning_path": "识别金额字段...",
"output_spec": "必须覆盖正常金额...",
"skill_file_path": "D:\\zhyy\\effekt-interface\\config\\skills\\产品A\\项目A\\支付模块\\支付金额边界校验\\SKILL.md",
"skill_type": 3,
"risk_level": 0,
"tags": ["支付", "金额", "边界"],
"status": 1,
"owner_id": 8,
"usage_count": 0,
"created_time": "2025-09-20 12:00:00",
"updated_time": "2025-09-20 12:00:00"
}
],
"total": 1
}
}
```
***
## 4. Business Rule 接口
### 4.1 创建业务规则
```http
POST /it/api/business-rule/create
```
权限:`business-rule:create`
请求体:
```json
{
"projectId": 1,
"moduleId": 10,
"name": "支付金额必须大于 0",
"description": "用于支付、充值、扣款、退款等金额输入场景的参数校验规则",
"priority": 0,
"tags": ["支付", "金额", "参数校验"],
"status": 1
}
```
参数:
| 字段 | 类型 | 必填 | 说明 |
| ----------- | ----------- | ---- | ---- |
| projectId | number | 是 | 项目 ID |
| moduleId | number | 否 | 模块 ID |
| name | string | 是 | 规则名称 |
| description | string | 否 | 用户补充描述,会作为大模型生成规则内容的输入 |
| priority | number | 否 | 优先级,默认 2大模型也可能根据内容修正 |
| tags | string\[] | 否 | 初始标签数组,大模型可能补全 |
| status | number | 否 | 状态,默认 1 |
说明:
- `ruleCode` 不需要前端传,后端自动生成项目内唯一编码。
- `ruleContent``applicableScene``example` 不需要前端传,后端会调用大模型生成。
- `ownerId` 不需要前端传,后端默认取当前登录人 ID。
- 创建成功后,后端会在 `config/rules/{产品名称}/{项目名称}/{模块名称}/{规则名称}/RULE.md` 生成业务规则文件。
- 数据库会保存生成文件路径到 `rule_file_path`
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"id": 1
}
}
```
***
### 4.2 更新业务规则
```http
POST /it/api/business-rule/update
```
权限:`business-rule:update`
请求体:
```json
{
"ruleId": 1,
"name": "支付金额必须大于 0",
"ruleContent": "支付金额必须大于 0等于 0 或小于 0 时接口应返回参数错误",
"applicableScene": "支付、充值、扣款、退款金额输入",
"example": "amount=0预期返回金额必须大于0",
"priority": 0,
"tags": ["支付", "金额", "参数校验"],
"status": 1,
"ownerId": 8
}
```
说明:
- 当前接口不支持更新 `ruleCode`
- 更新成功后,后端会根据更新后的业务规则内容重新创建 `RULE.md` 文件。
- 新文件路径会同步更新到数据库 `rule_file_path`
- 数据库更新成功后会删除原业务规则文件夹。
- 如果数据库更新失败,后端会删除新创建的文件夹并保留旧数据库记录和旧文件,避免数据库与文件不一致。
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"id": 1
}
}
```
***
### 4.3 删除业务规则
```http
POST /it/api/business-rule/delete
```
权限:`business-rule:delete`
请求体:
```json
{
"ruleId": 1
}
```
说明:软删除,设置 `is_delete = 1`,并删除该业务规则对应的 `config/rules/{产品名称}/{项目名称}/{模块名称}/{规则名称}` 文件夹。
***
### 4.4 业务规则详情
```http
GET /it/api/business-rule/detail?ruleId=1
```
权限:`business-rule:detail`
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"id": 1,
"project_id": 1,
"module_id": 10,
"name": "支付金额必须大于 0",
"rule_code": "PAY_AMOUNT_GT_ZERO",
"rule_content": "支付金额必须大于 0等于 0 或小于 0 时接口应返回参数错误",
"applicable_scene": "支付、充值、扣款、退款金额输入",
"example": "amount=0预期返回金额必须大于0",
"rule_file_path": "D:\\zhyy\\effekt-interface\\config\\rules\\产品A\\项目A\\支付模块\\支付金额必须大于 0\\RULE.md",
"priority": 0,
"tags": ["支付", "金额", "参数校验"],
"status": 1,
"owner_id": 8,
"created_by": 6,
"usage_count": 0,
"is_delete": 0,
"created_time": "2025-09-20 12:00:00",
"updated_time": "2025-09-20 12:00:00"
}
}
```
***
### 4.5 业务规则列表
```http
GET /it/api/business-rule/list
```
权限:`business-rule:list`
Query 参数:
| 参数 | 类型 | 必填 | 说明 |
| --------- | ------ | -- | -------------------------------------------------- |
| pageNo | number | 否 | 页码,默认 1 |
| pageSize | number | 否 | 每页数量,默认 20 |
| projectId | number | 否 | 项目 ID |
| moduleId | number | 否 | 模块 ID |
| status | number | 否 | 状态 |
| priority | number | 否 | 优先级 |
| keyword | string | 否 | 搜索 name/rule\_code/rule\_content/applicable\_scene |
| tag | string | 否 | 单个标签过滤 |
请求示例:
```http
GET /it/api/business-rule/list?pageNo=1&pageSize=20&projectId=1&moduleId=10&keyword=&status=1
```
成功响应:
```json
{
"code": 20000,
"msg": "success",
"data": {
"list": [
{
"id": 1,
"project_id": 1,
"module_id": 10,
"name": "支付金额必须大于 0",
"rule_code": "PAY_AMOUNT_GT_ZERO",
"rule_content": "支付金额必须大于 0等于 0 或小于 0 时接口应返回参数错误",
"applicable_scene": "支付、充值、扣款、退款金额输入",
"example": "amount=0预期返回金额必须大于0",
"priority": 0,
"tags": ["支付", "金额", "参数校验"],
"status": 1,
"owner_id": 8,
"usage_count": 0,
"created_time": "2025-09-20 12:00:00",
"updated_time": "2025-09-20 12:00:00"
}
],
"total": 1
}
}
```
***
## 5. 调用示例
### 5.1 curl 创建 Skill
```bash
curl -X POST 'http://localhost:5010/it/api/skill/create' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer your-token' \
-d '{
"projectId": 1,
"name": "支付金额边界校验",
"code": "PAY_AMOUNT_BOUNDARY",
"triggerCondition": "需求中出现支付、金额、扣款、退款、余额等关键词时触发",
"skillType": 3,
"riskLevel": 0,
"tags": ["支付", "金额", "边界"]
}'
```
### 5.2 curl 创建业务规则
```bash
curl -X POST 'http://localhost:5010/it/api/business-rule/create' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer your-token' \
-d '{
"projectId": 1,
"name": "支付金额必须大于 0",
"ruleCode": "PAY_AMOUNT_GT_ZERO",
"ruleContent": "支付金额必须大于 0等于 0 或小于 0 时接口应返回参数错误",
"priority": 0,
"tags": ["支付", "金额", "参数校验"]
}'
```
***
## 6. 注意事项
1. 返回字段是后端数据库下划线风格,例如 `project_id``created_time`
2. `tags` 是数组,创建和更新时必须传数组。
3. 删除是软删除。
4. Skill 的 `code` 和业务规则的 `ruleCode` 当前不支持更新。
5. 本次接口只做 Skills / Rules 管理,暂未接入 PRD AI 生成用例链路。

View File

@@ -0,0 +1,26 @@
-- 添加文档源模块的权限和菜单
BEGIN;
-- 1. 添加文档源相关权限
INSERT INTO public.permission (code, name, module, action, description, status, is_delete, created_time, updated_time) VALUES
('document:list', '文档源列表', 'document', 'list', '查看文档源列表', 1, 0, NOW(), NOW()),
('document:detail', '文档源详情', 'document', 'detail', '查看文档源详情', 1, 0, NOW(), NOW()),
('document:create', '文档源创建', 'document', 'create', '创建文档源', 1, 0, NOW(), NOW()),
('document:update', '文档源更新', 'document', 'update', '更新文档源', 1, 0, NOW(), NOW()),
('document:delete', '文档源删除', 'document', 'delete', '删除文档源', 1, 0, NOW(), NOW()),
('document:generate', '文档源生成用例', 'document', 'generate', '根据文档生成测试用例', 1, 0, NOW(), NOW()),
('document:import', '文档源导入用例', 'document', 'import', '导入生成的测试用例', 1, 0, NOW(), NOW())
ON CONFLICT (code) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description;
-- 2. 添加文档源菜单(作为测试用例的子菜单)
-- 先查找测试用例菜单的ID
WITH case_menu AS (
SELECT id FROM public.menu WHERE code = 'case' AND is_delete = 0
)
INSERT INTO public.menu (parent_id, name, code, type, path, component, icon, permission_code, sort, visible, status, is_delete, created_time, updated_time)
SELECT id, '文档源管理', 'document', 2, '/document', 'document/index', 'file-text', 'document:list', 10, 1, 1, 0, NOW(), NOW()
FROM case_menu
ON CONFLICT (code) DO UPDATE SET name=EXCLUDED.name, path=EXCLUDED.path, component=EXCLUDED.component;
COMMIT;

View File

@@ -0,0 +1,5 @@
-- 为module表添加status字段默认0待确认1正常2弃用
ALTER TABLE module ADD COLUMN IF NOT EXISTS status INTEGER DEFAULT 0 COMMENT '0待确认1正常2弃用';
-- 将历史数据的status设置为1
UPDATE module SET status = 1 WHERE status IS NULL OR status = 0;

View File

@@ -0,0 +1,56 @@
-- 创建文档源表 document_source
-- 用于存储PRD文档PDF和飞书链接
BEGIN;
-- 创建文档源表
CREATE TABLE IF NOT EXISTS public.document_source (
id BIGSERIAL PRIMARY KEY,
product_id BIGINT NOT NULL,
project_id BIGINT NOT NULL,
type SMALLINT NOT NULL DEFAULT 1,
source VARCHAR(512) NOT NULL,
content TEXT,
version INTEGER NOT NULL DEFAULT 1,
status SMALLINT NOT NULL DEFAULT 0,
ai_model VARCHAR(64),
created_by BIGINT,
is_delete INTEGER NOT NULL DEFAULT 0,
created_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_document_source_source UNIQUE (source, is_delete)
);
COMMENT ON TABLE public.document_source IS '文档源表 - 存储PRD文档和飞书链接';
COMMENT ON COLUMN public.document_source.id IS '主键ID';
COMMENT ON COLUMN public.document_source.product_id IS '产品ID';
COMMENT ON COLUMN public.document_source.project_id IS '项目ID';
COMMENT ON COLUMN public.document_source.type IS '类型1-PDF文件2-飞书链接';
COMMENT ON COLUMN public.document_source.source IS '文件路径或飞书链接';
COMMENT ON COLUMN public.document_source.content IS '解析后的文本内容(缓存)';
COMMENT ON COLUMN public.document_source.version IS '版本号';
COMMENT ON COLUMN public.document_source.status IS '状态0-待解析1-已解析2-已生成用例';
COMMENT ON COLUMN public.document_source.ai_model IS '使用的AI模型';
COMMENT ON COLUMN public.document_source.created_by IS '创建人ID';
COMMENT ON COLUMN public.document_source.is_delete IS '0未删除1已删除';
COMMENT ON COLUMN public.document_source.created_time IS '创建时间';
COMMENT ON COLUMN public.document_source.updated_time IS '更新时间';
-- 为 test_case 表添加字段
ALTER TABLE public.test_case ADD COLUMN IF NOT EXISTS document_id BIGINT;
COMMENT ON COLUMN public.test_case.document_id IS '关联的文档源ID';
ALTER TABLE public.test_case ADD COLUMN IF NOT EXISTS document_version INTEGER;
COMMENT ON COLUMN public.test_case.document_version IS '关联的文档版本';
-- 添加外键约束
ALTER TABLE public.test_case ADD CONSTRAINT fk_test_case_document_id FOREIGN KEY (document_id) REFERENCES public.document_source(id);
-- 添加索引
CREATE INDEX IF NOT EXISTS idx_document_source_product_id ON public.document_source(product_id);
CREATE INDEX IF NOT EXISTS idx_document_source_project_id ON public.document_source(project_id);
CREATE INDEX IF NOT EXISTS idx_document_source_type ON public.document_source(type);
CREATE INDEX IF NOT EXISTS idx_document_source_status ON public.document_source(status);
CREATE INDEX IF NOT EXISTS idx_test_case_document_id ON public.test_case(document_id);
COMMIT;

View File

@@ -0,0 +1,345 @@
-- Skills / Business Rules / AI Generation Context 初始化脚本
-- 数据库PostgreSQL
CREATE TABLE IF NOT EXISTS test_skill (
id BIGSERIAL PRIMARY KEY,
project_id BIGINT NOT NULL,
module_id BIGINT,
name VARCHAR(128) NOT NULL,
code VARCHAR(64) NOT NULL,
description TEXT,
trigger_condition TEXT NOT NULL,
reasoning_path TEXT,
output_spec TEXT,
skill_file_path VARCHAR(512),
skill_type SMALLINT NOT NULL DEFAULT 1,
risk_level SMALLINT NOT NULL DEFAULT 2,
tags JSONB NOT NULL DEFAULT '[]'::jsonb,
status SMALLINT NOT NULL DEFAULT 1,
owner_id BIGINT,
created_by BIGINT,
usage_count INTEGER NOT NULL DEFAULT 0,
is_delete INTEGER NOT NULL DEFAULT 0,
created_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE test_skill IS '测试 Skills 表:沉淀面向 AI 用例生成的测试策略、历史缺陷模式、边界场景和测试经验';
COMMENT ON COLUMN test_skill.id IS '主键 ID';
COMMENT ON COLUMN test_skill.project_id IS '所属项目 ID';
COMMENT ON COLUMN test_skill.module_id IS '所属模块 ID空表示项目级通用 Skill';
COMMENT ON COLUMN test_skill.name IS 'Skill 名称';
COMMENT ON COLUMN test_skill.code IS 'Skill 编码,建议项目内唯一';
COMMENT ON COLUMN test_skill.description IS 'Skill 描述';
COMMENT ON COLUMN test_skill.trigger_condition IS '触发条件,描述什么需求或场景下应该使用该 Skill';
COMMENT ON COLUMN test_skill.reasoning_path IS '推理路径,指导 AI 如何分析需求并设计测试点';
COMMENT ON COLUMN test_skill.output_spec IS '输出规范,指导 AI 必须生成哪些类型或结构的用例';
COMMENT ON COLUMN test_skill.skill_file_path IS 'Skill 文件路径,指向 config/skills 下生成的 SKILL.md';
COMMENT ON COLUMN test_skill.skill_type IS 'Skill 类型1通用测试策略 2历史缺陷模式 3边界场景 4接口测试 5UI测试 6性能测试 7安全测试 8数据一致性 9并发幂等 99其他';
COMMENT ON COLUMN test_skill.risk_level IS '风险等级0高风险 1中高风险 2中风险 3低风险';
COMMENT ON COLUMN test_skill.tags IS '标签数组,例如 ["支付","金额","边界"]';
COMMENT ON COLUMN test_skill.status IS '状态1启用 2停用 3草稿';
COMMENT ON COLUMN test_skill.owner_id IS '负责人用户 ID';
COMMENT ON COLUMN test_skill.created_by IS '创建人用户 ID';
COMMENT ON COLUMN test_skill.usage_count IS '使用次数,后续 PRD 生成用例引用该 Skill 时累加';
COMMENT ON COLUMN test_skill.is_delete IS '软删除标记0未删除 1已删除';
COMMENT ON COLUMN test_skill.created_time IS '创建时间';
COMMENT ON COLUMN test_skill.updated_time IS '更新时间';
ALTER TABLE test_skill ADD COLUMN IF NOT EXISTS skill_file_path VARCHAR(512);
COMMENT ON COLUMN test_skill.skill_file_path IS 'Skill 文件路径,指向 config/skills 下生成的 SKILL.md';
CREATE UNIQUE INDEX IF NOT EXISTS uk_test_skill_project_code
ON test_skill(project_id, code)
WHERE is_delete = 0;
COMMENT ON INDEX uk_test_skill_project_code IS '同一项目下未删除 Skill 编码唯一';
CREATE INDEX IF NOT EXISTS idx_test_skill_project_module
ON test_skill(project_id, module_id)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_skill_project_module IS '按项目和模块查询 Skill';
CREATE INDEX IF NOT EXISTS idx_test_skill_status
ON test_skill(status)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_skill_status IS '按 Skill 状态过滤';
CREATE INDEX IF NOT EXISTS idx_test_skill_type
ON test_skill(skill_type)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_skill_type IS '按 Skill 类型过滤';
CREATE INDEX IF NOT EXISTS idx_test_skill_risk_level
ON test_skill(risk_level)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_skill_risk_level IS '按风险等级过滤';
CREATE INDEX IF NOT EXISTS idx_test_skill_created_time
ON test_skill(created_time DESC);
COMMENT ON INDEX idx_test_skill_created_time IS 'Skill 列表按创建时间排序';
CREATE INDEX IF NOT EXISTS idx_test_skill_file_path
ON test_skill(skill_file_path)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_skill_file_path IS '按 Skill 文件路径查询';
CREATE INDEX IF NOT EXISTS idx_test_skill_tags_gin
ON test_skill USING GIN(tags);
COMMENT ON INDEX idx_test_skill_tags_gin IS 'Skill 标签 JSONB GIN 索引';
CREATE TABLE IF NOT EXISTS test_business_rule (
id BIGSERIAL PRIMARY KEY,
project_id BIGINT NOT NULL,
module_id BIGINT,
name VARCHAR(128) NOT NULL,
rule_code VARCHAR(64),
rule_content TEXT NOT NULL,
applicable_scene TEXT,
example TEXT,
rule_file_path VARCHAR(512),
priority SMALLINT NOT NULL DEFAULT 2,
tags JSONB NOT NULL DEFAULT '[]'::jsonb,
status SMALLINT NOT NULL DEFAULT 1,
owner_id BIGINT,
created_by BIGINT,
usage_count INTEGER NOT NULL DEFAULT 0,
is_delete INTEGER NOT NULL DEFAULT 0,
created_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE test_business_rule IS '业务规则表:沉淀确定性的业务约束、参数校验规则和场景规则';
COMMENT ON COLUMN test_business_rule.id IS '主键 ID';
COMMENT ON COLUMN test_business_rule.project_id IS '所属项目 ID';
COMMENT ON COLUMN test_business_rule.module_id IS '所属模块 ID空表示项目级通用规则';
COMMENT ON COLUMN test_business_rule.name IS '业务规则名称';
COMMENT ON COLUMN test_business_rule.rule_code IS '业务规则编码,建议项目内唯一,可为空';
COMMENT ON COLUMN test_business_rule.rule_content IS '业务规则内容';
COMMENT ON COLUMN test_business_rule.applicable_scene IS '适用场景';
COMMENT ON COLUMN test_business_rule.example IS '规则示例';
COMMENT ON COLUMN test_business_rule.rule_file_path IS '业务规则文件路径,指向 config/rules 下生成的 RULE.md';
COMMENT ON COLUMN test_business_rule.priority IS '优先级0高 1中高 2中 3低';
ALTER TABLE test_business_rule ADD COLUMN IF NOT EXISTS rule_file_path VARCHAR(512);
COMMENT ON COLUMN test_business_rule.rule_file_path IS '业务规则文件路径,指向 config/rules 下生成的 RULE.md';
COMMENT ON COLUMN test_business_rule.tags IS '标签数组,例如 ["支付","金额","参数校验"]';
COMMENT ON COLUMN test_business_rule.status IS '状态1启用 2停用 3草稿';
COMMENT ON COLUMN test_business_rule.owner_id IS '负责人用户 ID';
COMMENT ON COLUMN test_business_rule.created_by IS '创建人用户 ID';
COMMENT ON COLUMN test_business_rule.usage_count IS '使用次数,后续 PRD 生成用例引用该规则时累加';
COMMENT ON COLUMN test_business_rule.is_delete IS '软删除标记0未删除 1已删除';
COMMENT ON COLUMN test_business_rule.created_time IS '创建时间';
COMMENT ON COLUMN test_business_rule.updated_time IS '更新时间';
CREATE UNIQUE INDEX IF NOT EXISTS uk_test_business_rule_project_code
ON test_business_rule(project_id, rule_code)
WHERE is_delete = 0 AND rule_code IS NOT NULL;
COMMENT ON INDEX uk_test_business_rule_project_code IS '同一项目下未删除业务规则编码唯一rule_code 为空时不参与唯一约束';
CREATE INDEX IF NOT EXISTS idx_test_business_rule_project_module
ON test_business_rule(project_id, module_id)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_business_rule_project_module IS '按项目和模块查询业务规则';
CREATE INDEX IF NOT EXISTS idx_test_business_rule_status
ON test_business_rule(status)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_business_rule_status IS '按业务规则状态过滤';
CREATE INDEX IF NOT EXISTS idx_test_business_rule_priority
ON test_business_rule(priority)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_business_rule_priority IS '按业务规则优先级过滤';
CREATE INDEX IF NOT EXISTS idx_test_business_rule_created_time
ON test_business_rule(created_time DESC);
COMMENT ON INDEX idx_test_business_rule_created_time IS '业务规则列表按创建时间排序';
CREATE INDEX IF NOT EXISTS idx_test_business_rule_file_path
ON test_business_rule(rule_file_path)
WHERE is_delete = 0;
COMMENT ON INDEX idx_test_business_rule_file_path IS '按业务规则文件路径查询';
CREATE INDEX IF NOT EXISTS idx_test_business_rule_tags_gin
ON test_business_rule USING GIN(tags);
COMMENT ON INDEX idx_test_business_rule_tags_gin IS '业务规则标签 JSONB GIN 索引';
CREATE TABLE IF NOT EXISTS test_ai_generation_context (
id BIGSERIAL PRIMARY KEY,
generation_id BIGINT,
project_id BIGINT NOT NULL,
module_id BIGINT,
source_type SMALLINT NOT NULL,
source_id BIGINT NOT NULL,
source_name VARCHAR(128),
match_score INTEGER NOT NULL DEFAULT 0,
created_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE test_ai_generation_context IS 'AI 生成上下文引用记录表:记录某次 PRD/AI 生成用例使用了哪些 Skill 或业务规则';
COMMENT ON COLUMN test_ai_generation_context.id IS '主键 ID';
COMMENT ON COLUMN test_ai_generation_context.generation_id IS 'AI 生成任务 ID兼容现有 PRD 生成功能的任务 ID';
COMMENT ON COLUMN test_ai_generation_context.project_id IS '项目 ID';
COMMENT ON COLUMN test_ai_generation_context.module_id IS '模块 ID';
COMMENT ON COLUMN test_ai_generation_context.source_type IS '来源类型1 Skill 2业务规则';
COMMENT ON COLUMN test_ai_generation_context.source_id IS '来源 IDSkill ID 或 Business Rule ID';
COMMENT ON COLUMN test_ai_generation_context.source_name IS '来源名称快照';
COMMENT ON COLUMN test_ai_generation_context.match_score IS '匹配分数';
COMMENT ON COLUMN test_ai_generation_context.created_time IS '创建时间';
CREATE INDEX IF NOT EXISTS idx_test_ai_generation_context_generation
ON test_ai_generation_context(generation_id);
COMMENT ON INDEX idx_test_ai_generation_context_generation IS '按 AI 生成任务 ID 查询上下文引用';
CREATE INDEX IF NOT EXISTS idx_test_ai_generation_context_project_module
ON test_ai_generation_context(project_id, module_id);
COMMENT ON INDEX idx_test_ai_generation_context_project_module IS '按项目和模块查询上下文引用';
CREATE INDEX IF NOT EXISTS idx_test_ai_generation_context_source
ON test_ai_generation_context(source_type, source_id);
COMMENT ON INDEX idx_test_ai_generation_context_source IS '按来源类型和来源 ID 查询上下文引用';
CREATE INDEX IF NOT EXISTS idx_test_ai_generation_context_created_time
ON test_ai_generation_context(created_time DESC);
COMMENT ON INDEX idx_test_ai_generation_context_created_time IS '上下文引用记录按创建时间排序';
CREATE OR REPLACE FUNCTION update_updated_time_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_time = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_test_skill_updated_time ON test_skill;
CREATE TRIGGER trg_test_skill_updated_time
BEFORE UPDATE ON test_skill
FOR EACH ROW
EXECUTE FUNCTION update_updated_time_column();
COMMENT ON TRIGGER trg_test_skill_updated_time ON test_skill IS '自动维护 test_skill.updated_time';
DROP TRIGGER IF EXISTS trg_test_business_rule_updated_time ON test_business_rule;
CREATE TRIGGER trg_test_business_rule_updated_time
BEFORE UPDATE ON test_business_rule
FOR EACH ROW
EXECUTE FUNCTION update_updated_time_column();
COMMENT ON TRIGGER trg_test_business_rule_updated_time ON test_business_rule IS '自动维护 test_business_rule.updated_time';
INSERT INTO permission (code, name, module, action, description, status, is_delete)
VALUES
('skill:create', '创建测试Skill', 'skill', 'create', '创建测试 Skills', 1, 0),
('skill:update', '更新测试Skill', 'skill', 'update', '更新测试 Skills', 1, 0),
('skill:delete', '删除测试Skill', 'skill', 'delete', '软删除测试 Skills', 1, 0),
('skill:list', '查询测试Skill列表', 'skill', 'list', '查询测试 Skills 列表', 1, 0),
('skill:detail', '查询测试Skill详情', 'skill', 'detail', '查询测试 Skills 详情', 1, 0),
('business-rule:create', '创建业务规则', 'business-rule', 'create', '创建业务规则', 1, 0),
('business-rule:update', '更新业务规则', 'business-rule', 'update', '更新业务规则', 1, 0),
('business-rule:delete', '删除业务规则', 'business-rule', 'delete', '软删除业务规则', 1, 0),
('business-rule:list', '查询业务规则列表', 'business-rule', 'list', '查询业务规则列表', 1, 0),
('business-rule:detail', '查询业务规则详情', 'business-rule', 'detail', '查询业务规则详情', 1, 0)
ON CONFLICT (code) DO UPDATE SET
name = EXCLUDED.name,
module = EXCLUDED.module,
action = EXCLUDED.action,
description = EXCLUDED.description,
status = 1,
is_delete = 0,
updated_time = CURRENT_TIMESTAMP;
-- Skills / Business Rules 菜单初始化
-- 默认挂载到“测试平台(test_platform)”目录下;如果不存在则创建测试平台目录。
INSERT INTO menu (parent_id, name, code, type, path, component, icon, permission_code, sort, visible, status, is_delete)
VALUES (0, '测试平台', 'test_platform', 1, '/test-platform', 'Layout', 'test', NULL, 2, 1, 1, 0)
ON CONFLICT (code) DO UPDATE SET
name = EXCLUDED.name,
type = EXCLUDED.type,
path = EXCLUDED.path,
component = EXCLUDED.component,
icon = EXCLUDED.icon,
sort = EXCLUDED.sort,
visible = 1,
status = 1,
is_delete = 0,
updated_time = CURRENT_TIMESTAMP;
WITH parent_menu AS (
SELECT id FROM menu WHERE code = 'test_platform' LIMIT 1
)
INSERT INTO menu (parent_id, name, code, type, path, component, icon, permission_code, sort, visible, status, is_delete)
SELECT id, '测试 Skills', 'skill_manage', 2, '/test-platform/skills', 'test-platform/skills/index', 'skill', 'skill:list', 20, 1, 1, 0 FROM parent_menu
ON CONFLICT (code) DO UPDATE SET
parent_id = EXCLUDED.parent_id,
name = EXCLUDED.name,
type = EXCLUDED.type,
path = EXCLUDED.path,
component = EXCLUDED.component,
icon = EXCLUDED.icon,
permission_code = EXCLUDED.permission_code,
sort = EXCLUDED.sort,
visible = 1,
status = 1,
is_delete = 0,
updated_time = CURRENT_TIMESTAMP;
WITH parent_menu AS (
SELECT id FROM menu WHERE code = 'test_platform' LIMIT 1
)
INSERT INTO menu (parent_id, name, code, type, path, component, icon, permission_code, sort, visible, status, is_delete)
SELECT id, '业务规则', 'business_rule_manage', 2, '/test-platform/business-rules', 'test-platform/business-rules/index', 'rule', 'business-rule:list', 21, 1, 1, 0 FROM parent_menu
ON CONFLICT (code) DO UPDATE SET
parent_id = EXCLUDED.parent_id,
name = EXCLUDED.name,
type = EXCLUDED.type,
path = EXCLUDED.path,
component = EXCLUDED.component,
icon = EXCLUDED.icon,
permission_code = EXCLUDED.permission_code,
sort = EXCLUDED.sort,
visible = 1,
status = 1,
is_delete = 0,
updated_time = CURRENT_TIMESTAMP;
WITH parent_menu AS (
SELECT id FROM menu WHERE code = 'skill_manage' LIMIT 1
)
INSERT INTO menu (parent_id, name, code, type, path, component, icon, permission_code, sort, visible, status, is_delete)
SELECT id, '新增', 'skill:create', 3, '', '', NULL, 'skill:create', 1, 1, 1, 0 FROM parent_menu
UNION ALL SELECT id, '编辑', 'skill:update', 3, '', '', NULL, 'skill:update', 2, 1, 1, 0 FROM parent_menu
UNION ALL SELECT id, '删除', 'skill:delete', 3, '', '', NULL, 'skill:delete', 3, 1, 1, 0 FROM parent_menu
UNION ALL SELECT id, '列表查询', 'skill:list', 3, '', '', NULL, 'skill:list', 4, 1, 1, 0 FROM parent_menu
UNION ALL SELECT id, '详情', 'skill:detail', 3, '', '', NULL, 'skill:detail', 5, 1, 1, 0 FROM parent_menu
ON CONFLICT (code) DO UPDATE SET
parent_id = EXCLUDED.parent_id,
name = EXCLUDED.name,
type = EXCLUDED.type,
path = EXCLUDED.path,
component = EXCLUDED.component,
permission_code = EXCLUDED.permission_code,
sort = EXCLUDED.sort,
visible = 1,
status = 1,
is_delete = 0,
updated_time = CURRENT_TIMESTAMP;
WITH parent_menu AS (
SELECT id FROM menu WHERE code = 'business_rule_manage' LIMIT 1
)
INSERT INTO menu (parent_id, name, code, type, path, component, icon, permission_code, sort, visible, status, is_delete)
SELECT id, '新增', 'business-rule:create', 3, '', '', NULL, 'business-rule:create', 1, 1, 1, 0 FROM parent_menu
UNION ALL SELECT id, '编辑', 'business-rule:update', 3, '', '', NULL, 'business-rule:update', 2, 1, 1, 0 FROM parent_menu
UNION ALL SELECT id, '删除', 'business-rule:delete', 3, '', '', NULL, 'business-rule:delete', 3, 1, 1, 0 FROM parent_menu
UNION ALL SELECT id, '列表查询', 'business-rule:list', 3, '', '', NULL, 'business-rule:list', 4, 1, 1, 0 FROM parent_menu
UNION ALL SELECT id, '详情', 'business-rule:detail', 3, '', '', NULL, 'business-rule:detail', 5, 1, 1, 0 FROM parent_menu
ON CONFLICT (code) DO UPDATE SET
parent_id = EXCLUDED.parent_id,
name = EXCLUDED.name,
type = EXCLUDED.type,
path = EXCLUDED.path,
component = EXCLUDED.component,
permission_code = EXCLUDED.permission_code,
sort = EXCLUDED.sort,
visible = 1,
status = 1,
is_delete = 0,
updated_time = CURRENT_TIMESTAMP;