Files
HaHafeng 66255368b7 feat(admin): Add user management and upgrade to module permission system
Features - User Management (Phase 4.1):
- Database: Add user_modules table for fine-grained module permissions
- Database: Add 4 user permissions (view/create/edit/delete) to role_permissions
- Backend: UserService (780 lines) - CRUD with tenant isolation
- Backend: UserController + UserRoutes (648 lines) - 13 API endpoints
- Backend: Batch import users from Excel
- Frontend: UserListPage (412 lines) - list/filter/search/pagination
- Frontend: UserFormPage (341 lines) - create/edit with module config
- Frontend: UserDetailPage (393 lines) - details/tenant/module management
- Frontend: 3 modal components (592 lines) - import/assign/configure
- API: GET/POST/PUT/DELETE /api/admin/users/* endpoints

Architecture Upgrade - Module Permission System:
- Backend: Add getUserModules() method in auth.service
- Backend: Login API returns modules array in user object
- Frontend: AuthContext adds hasModule() method
- Frontend: Navigation filters modules based on user.modules
- Frontend: RouteGuard checks requiredModule instead of requiredVersion
- Frontend: Remove deprecated version-based permission system
- UX: Only show accessible modules in navigation (clean UI)
- UX: Smart redirect after login (avoid 403 for regular users)

Fixes:
- Fix UTF-8 encoding corruption in ~100 docs files
- Fix pageSize type conversion in userService (String to Number)
- Fix authUser undefined error in TopNavigation
- Fix login redirect logic with role-based access check
- Update Git commit guidelines v1.2 with UTF-8 safety rules

Database Changes:
- CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled)
- ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code)
- INSERT 4 permissions + role assignments
- UPDATE PUBLIC tenant with 8 module subscriptions

Technical:
- Backend: 5 new files (~2400 lines)
- Frontend: 10 new files (~2500 lines)
- Docs: 1 development record + 2 status updates + 1 guideline update
- Total: ~4900 lines of code

Status: User management 100% complete, module permission system operational
2026-01-16 13:42:10 +08:00

540 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AIA - AI智能问答模块数据库设计
> **版本:** v1.0
> **更新时间:** 2025-11-12
> **数据库Schema** `aia_schema`
> **状态:** ✅ 已实施并迁移
---
## 📋 目录
1. [模块概述](#模块概述)
2. [Schema信息](#schema信息)
3. [数据库表设计](#数据库表设计)
4. [表关系图](#表关系图)
5. [索引设计](#索引设计)
6. [数据类型说明](#数据类型说明)
7. [变更历史](#变更历史)
---
## 模块概述
### 功能定位
**AIAAI Intelligent Assistant- AI智能问答模块**是平台的核心对话引擎,提供:
1. **项目管理** - 研究项目的创建和管理
2. **智能对话** - 基于LLM的专业领域对话
3. **通用问答** - 不绑定项目的通用AI对话
4. **消息管理** - 对话历史记录和检索
### 核心业务场景
- 用户创建临床研究项目
- 在项目内与AI进行专业对话
- 使用通用对话功能快速咨询
- 查看和管理对话历史
---
## Schema信息
### Schema名称
```sql
aia_schema
```
### 创建语句
```sql
CREATE SCHEMA IF NOT EXISTS aia_schema;
GRANT ALL ON SCHEMA aia_schema TO aiclinical_admin;
```
### 数据迁移
- **迁移时间:** 2025-11-12
- **源Schema** public
- **迁移脚本:** `docs/09-架构实施/migration-scripts/003-migrate-aia.sql`
- **数据完整性:** ✅ 100%迁移成功
---
## 数据库表设计
### 表列表
| 表名 | 用途 | 行数(估计) | 状态 |
|------|------|------------|------|
| `projects` | 研究项目 | 1-100/用户 | ✅ 已部署 |
| `conversations` | 项目对话 | 10-500/项目 | ✅ 已部署 |
| `messages` | 对话消息 | 10-1000/对话 | ✅ 已部署 |
| `general_conversations` | 通用对话 | 10-100/用户 | ✅ 已部署 |
| `general_messages` | 通用对话消息 | 10-1000/对话 | ✅ 已部署 |
**总计:** 5个表
---
### 1. projects - 研究项目表
**用途:** 存储用户创建的临床研究项目
#### 表结构
| 字段名 | 数据类型 | 约束 | 说明 |
|--------|---------|------|------|
| id | TEXT | PRIMARY KEY | 项目唯一标识UUID |
| user_id | TEXT | NOT NULL, FK | 所属用户ID |
| name | TEXT | NOT NULL | 项目名称 |
| background | TEXT | NULL | 研究背景 |
| research_type | TEXT | NULL | 研究类型observational/experimental等 |
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 |
| deleted_at | TIMESTAMPTZ | NULL | 软删除时间 |
#### Prisma Model
```prisma
model Project {
id String @id @default(uuid())
userId String @map("user_id")
name String
background String?
researchType String? @map("research_type")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
conversations Conversation[]
@@index([userId])
@@index([createdAt])
@@index([deletedAt])
@@map("projects")
@@schema("aia_schema")
}
```
#### 业务规则
1. **软删除机制** - 使用`deleted_at`标记删除,不物理删除
2. **级联删除** - 删除项目时,关联的对话也被软删除
3. **用户隔离** - 通过`user_id`实现数据隔离
---
### 2. conversations - 项目对话表
**用途:** 存储项目内的AI对话会话
#### 表结构
| 字段名 | 数据类型 | 约束 | 说明 |
|--------|---------|------|------|
| id | TEXT | PRIMARY KEY | 对话唯一标识UUID |
| user_id | TEXT | NOT NULL, FK | 所属用户ID |
| project_id | TEXT | NULL, FK | 所属项目ID可选 |
| agent_id | TEXT | NOT NULL | 智能体ID |
| title | TEXT | NOT NULL | 对话标题 |
| model_name | TEXT | NOT NULL, DEFAULT 'deepseek-v3' | 使用的LLM模型 |
| message_count | INTEGER | NOT NULL, DEFAULT 0 | 消息数量 |
| total_tokens | INTEGER | NOT NULL, DEFAULT 0 | 累计token数 |
| metadata | JSONB | NULL | 扩展元数据 |
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 |
| deleted_at | TIMESTAMPTZ | NULL | 软删除时间 |
#### Prisma Model
```prisma
model Conversation {
id String @id @default(uuid())
userId String @map("user_id")
projectId String? @map("project_id")
agentId String @map("agent_id")
title String
modelName String @default("deepseek-v3") @map("model_name")
messageCount Int @default(0) @map("message_count")
totalTokens Int @default(0) @map("total_tokens")
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
messages Message[]
@@index([userId])
@@index([projectId])
@@index([agentId])
@@index([createdAt])
@@index([deletedAt])
@@map("conversations")
@@schema("aia_schema")
}
```
#### 业务规则
1. **项目关联可选** - `project_id`可为空,支持项目内外对话
2. **计数器字段** - `message_count``total_tokens`用于统计,需要实时更新
3. **模型选择** - 支持多种LLM模型deepseek-v3/gpt-5-pro/claude-4.5等)
4. **扩展元数据** - `metadata`用于存储配置参数、上下文等
---
### 3. messages - 对话消息表
**用途:** 存储对话中的每条消息
#### 表结构
| 字段名 | 数据类型 | 约束 | 说明 |
|--------|---------|------|------|
| id | TEXT | PRIMARY KEY | 消息唯一标识UUID |
| conversation_id | TEXT | NOT NULL, FK | 所属对话ID |
| role | TEXT | NOT NULL | 角色user/assistant/system |
| content | TEXT | NOT NULL | 消息内容 |
| model | TEXT | NULL | 使用的模型assistant消息 |
| metadata | JSONB | NULL | 扩展元数据 |
| tokens | INTEGER | NULL | 消息token数 |
| is_pinned | BOOLEAN | NOT NULL, DEFAULT false | 是否置顶 |
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
#### Prisma Model
```prisma
model Message {
id String @id @default(uuid())
conversationId String @map("conversation_id")
role String
content String @db.Text
model String?
metadata Json?
tokens Int?
isPinned Boolean @default(false) @map("is_pinned")
createdAt DateTime @default(now()) @map("created_at")
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
@@index([conversationId])
@@index([createdAt])
@@index([isPinned])
@@map("messages")
@@schema("aia_schema")
}
```
#### 业务规则
1. **角色类型** - `role`固定为`user``assistant``system`
2. **只读性** - 消息一旦创建不可修改(只有`is_pinned`可修改)
3. **级联删除** - 删除对话时,消息自动删除
4. **置顶功能** - 重要消息可通过`is_pinned`标记
---
### 4. general_conversations - 通用对话表
**用途:** 存储不绑定项目的通用AI对话
#### 表结构
| 字段名 | 数据类型 | 约束 | 说明 |
|--------|---------|------|------|
| id | TEXT | PRIMARY KEY | 对话唯一标识UUID |
| user_id | TEXT | NOT NULL, FK | 所属用户ID |
| title | TEXT | NOT NULL | 对话标题 |
| model_name | TEXT | NOT NULL, DEFAULT 'qwen-long' | 使用的LLM模型 |
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 |
| deleted_at | TIMESTAMPTZ | NULL | 软删除时间 |
#### Prisma Model
```prisma
model GeneralConversation {
id String @id @default(uuid())
userId String @map("user_id")
title String
modelName String @default("qwen-long") @map("model_name")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
messages GeneralMessage[]
@@index([userId])
@@index([createdAt])
@@index([updatedAt])
@@map("general_conversations")
@@schema("aia_schema")
}
```
#### 业务规则
1. **轻量级对话** - 不需要项目和智能体关联
2. **快速咨询** - 用于用户的临时问题和快速查询
3. **独立管理** - 与项目对话分开管理
---
### 5. general_messages - 通用对话消息表
**用途:** 存储通用对话中的每条消息
#### 表结构
| 字段名 | 数据类型 | 约束 | 说明 |
|--------|---------|------|------|
| id | TEXT | PRIMARY KEY | 消息唯一标识UUID |
| conversation_id | TEXT | NOT NULL, FK | 所属对话ID |
| role | TEXT | NOT NULL | 角色user/assistant/system |
| content | TEXT | NOT NULL | 消息内容 |
| metadata | JSONB | NULL | 扩展元数据 |
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT now() | 创建时间 |
#### Prisma Model
```prisma
model GeneralMessage {
id String @id @default(uuid())
conversationId String @map("conversation_id")
role String
content String @db.Text
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
conversation GeneralConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
@@index([conversationId])
@@index([createdAt])
@@map("general_messages")
@@schema("aia_schema")
}
```
#### 业务规则
1. **简化设计** - 相比项目消息去掉了token统计和置顶功能
2. **级联删除** - 删除对话时,消息自动删除
---
## 表关系图
```mermaid
erDiagram
PLATFORM_USERS ||--o{ PROJECTS : "owns"
PLATFORM_USERS ||--o{ CONVERSATIONS : "owns"
PLATFORM_USERS ||--o{ GENERAL_CONVERSATIONS : "owns"
PROJECTS ||--o{ CONVERSATIONS : "contains"
CONVERSATIONS ||--o{ MESSAGES : "contains"
GENERAL_CONVERSATIONS ||--o{ GENERAL_MESSAGES : "contains"
PLATFORM_USERS {
text id PK
text email
text password
}
PROJECTS {
text id PK
text user_id FK
text name
text research_type
timestamptz created_at
timestamptz deleted_at
}
CONVERSATIONS {
text id PK
text user_id FK
text project_id FK
text agent_id
text title
text model_name
int message_count
timestamptz created_at
}
MESSAGES {
text id PK
text conversation_id FK
text role
text content
int tokens
boolean is_pinned
timestamptz created_at
}
GENERAL_CONVERSATIONS {
text id PK
text user_id FK
text title
text model_name
timestamptz created_at
}
GENERAL_MESSAGES {
text id PK
text conversation_id FK
text role
text content
timestamptz created_at
}
```
### 跨Schema引用
**外键关系:**
- `projects.user_id``platform_schema.users.id`
- `conversations.user_id``platform_schema.users.id`
- `general_conversations.user_id``platform_schema.users.id`
**说明:** Prisma自动处理跨Schema外键应用代码无需关心Schema前缀
---
## 索引设计
### 主键索引
所有表的`id`字段自动创建B-tree主键索引。
### 外键索引
| 表名 | 索引字段 | 用途 |
|------|---------|------|
| projects | user_id | 查询用户的所有项目 |
| conversations | user_id | 查询用户的所有对话 |
| conversations | project_id | 查询项目内的对话 |
| conversations | agent_id | 按智能体过滤对话 |
| messages | conversation_id | 查询对话的所有消息 |
| general_conversations | user_id | 查询用户的通用对话 |
| general_messages | conversation_id | 查询对话的所有消息 |
### 时间索引
| 表名 | 索引字段 | 用途 |
|------|---------|------|
| projects | created_at | 按时间排序项目 |
| projects | deleted_at | 过滤已删除项目 |
| conversations | created_at | 按时间排序对话 |
| conversations | updated_at | 按更新时间排序 |
| conversations | deleted_at | 过滤已删除对话 |
| messages | created_at | 按时间排序消息 |
| general_conversations | created_at | 按时间排序对话 |
| general_conversations | updated_at | 按更新时间排序 |
| general_messages | created_at | 按时间排序消息 |
### 功能索引
| 表名 | 索引字段 | 用途 |
|------|---------|------|
| messages | is_pinned | 快速查询置顶消息 |
---
## 数据类型说明
### 主键类型TEXT vs UUID
**当前实现:** TEXT
```sql
id TEXT PRIMARY KEY
```
**UUID生成** Prisma `@default(uuid())`
```prisma
id String @id @default(uuid())
```
**说明:**
- 存储格式字符串形式的UUID`"a6ce8b46-bac6-4284-a9ae-031d636086bc"`
- 优点:与现有代码兼容,无需迁移
- 索引性能与原生UUID类型相当
### 时间类型TIMESTAMPTZ
**所有时间字段使用`TIMESTAMPTZ`(带时区的时间戳):**
- 自动存储UTC时间
- 支持时区转换
- Prisma映射为`DateTime`
### JSONB类型
**用途:** 存储扩展元数据和配置
- `conversations.metadata`
- `messages.metadata`
- `general_messages.metadata`
**优点:**
- 灵活的数据结构
- 支持GIN索引按需添加
- 支持JSONB操作符查询
---
## 变更历史
### v1.0 - 2025-11-12 - 初始版本 ✅
**变更内容:**
1.`public` schema迁移到`aia_schema`
2. 5个表全部迁移
- projects
- conversations
- messages
- general_conversations
- general_messages
3. 在Prisma中添加`@@schema("aia_schema")`标签
4. 所有数据100%完整迁移
**迁移脚本:** `docs/09-架构实施/migration-scripts/003-migrate-aia.sql`
**验证状态:** ✅ 已验证,功能正常
---
## 📚 相关文档
- [Schema隔离架构设计](../../../09-架构实施/01-Schema隔离架构设计10个.md)
- [Schema迁移完成报告](../../../09-架构实施/Schema迁移完成报告.md)
- [Prisma配置完成报告](../../../09-架构实施/Prisma配置完成报告.md)
- [快速功能测试报告](../../../09-架构实施/快速功能测试报告.md)
---
**文档维护者:** AI助手
**最后更新:** 2025-11-12
**文档状态:** ✅ 已完成并验证