PersonaQuest —— AI 编码代理项目指南
本文档供 AI 编码代理阅读。读者被假设对该项目一无所知。所有信息均基于实际代码与配置,不做假设或泛化。
项目概述
PersonaQuest(超级AI大学人格测试)是一个面向港科广学生的 4 维度人格测试 Web 平台。学生完成 31 道校园情境题后,系统根据四个维度(E/I、S/N、J/P、A/H)的得分组合出 16 种人格之一,并展示个性化解读、像素艺术形象与四维度光谱对比条。
核心用户流程:
首页 → 选择测试模板 → 答题(31题,乱序/正序) → 提交 → 结果页(人格卡片 + 四维度光谱)
项目采用中文作为界面与文档的主要语言。所有用户可见文本、代码注释、文档均使用中文。
技术栈
| 层级 | 技术 |
|---|---|
| 前端框架 | React 19 + React Router DOM 7 |
| 构建工具 | Vite 6(客户端),TypeScript Compiler(服务端) |
| 样式 | Tailwind CSS v4 + @tailwindcss/postcss |
| 状态管理 | Zustand 5 |
| 图标 | Lucide React |
| 动画 | Framer Motion |
| 后端框架 | Express 4 |
| 数据库 | SQLite(通过 Prisma ORM 6 访问) |
| 认证 | JWT(jsonwebtoken)+ bcryptjs |
| 运行时 | Node.js,TypeScript 编译为 ESM |
| 开发工具 | tsx(直接运行 TS)、nodemon(服务端热重载)、concurrently(前后端并行启动) |
项目结构
├── data/ # 可编辑数据源
│ ├── questions.json # 题库(31道情境题、选项、分值)
│ └── personalities.json # 人格库(16型人格定义、描述、像素画)
├── docs/ # 人工撰写文档
│ ├── README.md # 项目框架文档(面向项目 owner)
│ ├── question_design.md # 原始题库设计稿
│ ├── scoring_criteria.md # 评分与划分标准
│ └── todo.md # 协作任务清单
├── prisma/
│ ├── schema.prisma # 数据库模型定义(SQLite)
│ ├── seed.ts # 种子脚本:从 data/*.json 导入数据,并创建默认账号
│ └── migrations/ # Prisma 迁移文件
├── src/
│ ├── client/ # 前端(React + Vite)
│ │ ├── App.tsx # 路由配置 + ProtectedRoute 守卫
│ │ ├── main.tsx # 前端入口(StrictMode + BrowserRouter)
│ │ ├── index.css # Tailwind 导入 + 自定义主题色 + 动画 keyframes
│ │ ├── pages/ # 页面组件
│ │ │ ├── HomePage.tsx
│ │ │ ├── TestPage.tsx
│ │ │ ├── ResultPage.tsx
│ │ │ ├── GalleryPage.tsx
│ │ │ ├── PersonalityDetailPage.tsx
│ │ │ ├── LoginPage.tsx / RegisterPage.tsx
│ │ │ ├── admin/ # AdminDashboard, QuestionManager, PersonalityManager
│ │ │ └── creator/ # CreatorDashboard, SubmitPage
│ │ ├── components/ # 可复用组件
│ │ │ ├── layout/ # Header.tsx, Footer.tsx
│ │ │ ├── test/ # QuestionCard.tsx, ProgressBar.tsx
│ │ │ └── result/ # PersonalityCard.tsx, PixelAvatar.tsx
│ │ ├── stores/ # Zustand 状态管理
│ │ │ ├── authStore.ts # 用户登录态、token、localStorage 同步
│ │ │ └── testStore.ts # 答题进度、题目、答案、当前索引
│ │ ├── hooks/ # React Hooks
│ │ │ ├── useAuth.ts # 挂载时自动校验 token 并拉取 /auth/me
│ │ │ └── useMobile.ts # 响应式断点检测(默认 768px)
│ │ └── lib/ # 工具与常量
│ │ ├── api.ts # fetch 封装:自动注入 Bearer token、统一 ApiResponse 解析
│ │ ├── utils.ts # cn (clsx + twMerge)、formatDate、角色/状态标签映射
│ │ └── constants.ts # APP_NAME、ROUTES 常量表
│ ├── server/ # 后端(Express + Prisma)
│ │ ├── index.ts # 服务入口:启动 HTTP 监听
│ │ ├── app.ts # Express 应用配置:中间件、路由挂载、全局错误处理
│ │ ├── routes/ # API 路由(按领域拆分)
│ │ │ ├── auth.routes.ts
│ │ │ ├── test.routes.ts # 模板/题目获取、提交计分、结果查询
│ │ │ ├── personality.routes.ts
│ │ │ ├── admin.routes.ts # 审核、题目/人格 CRUD(需 ADMIN)
│ │ │ └── creator.routes.ts # 提交新内容、查看自己的提交(需 CREATOR/ADMIN)
│ │ ├── controllers/ # 请求处理层(目前仅 auth.controller.ts 使用)
│ │ ├── services/ # 业务逻辑层(目前仅 auth.service.ts 使用)
│ │ ├── middleware/
│ │ │ ├── auth.middleware.ts # JWT 校验,将用户信息注入 req.user
│ │ │ ├── role.middleware.ts # 角色权限守卫(requireRole)
│ │ │ └── error.middleware.ts # 全局错误处理中间件
│ │ ├── config/
│ │ │ └── auth.ts # JWT_SECRET、JWT_EXPIRES_IN 配置
│ │ └── utils/
│ │ └── prisma.ts # PrismaClient 单例(开发环境复用全局实例)
│ └── shared/ # 前后端共享代码
│ └── types.ts # TypeScript 类型定义(User、Question、Personality、TestResult 等)
├── public/ # 静态资源
├── index.html # Vite 入口 HTML(lang="zh-CN")
├── package.json
├── tsconfig.json # 前端 TS 配置(noEmit,含路径别名 @/*、@shared/*)
├── tsconfig.server.json # 后端 TS 配置(emit 到 dist/server,模块 NodeNext)
├── vite.config.ts # Vite 配置:React 插件、路径别名、端口 5173、/api 代理到 3001
├── tailwind.config.js # Tailwind v4 主题扩展(primary 色阶、自定义动画)
├── postcss.config.js # PostCSS:仅 @tailwindcss/postcss 插件
└── .env.example # 环境变量模板
路径别名
| 别名 | 指向 |
|---|---|
@/* | src/client/* |
@shared/* | src/shared/* |
前后端均使用 .js 扩展名进行模块导入("type": "module" + TypeScript 编译要求),即使源文件是 .ts。
环境变量
复制 .env.example 为 .env 并根据需要修改:
PORT=3001 # Express 服务端口号
DATABASE_URL="file:./dev.db" # SQLite 数据库路径
JWT_SECRET="..." # JWT 签名密钥(生产环境务必更换)
JWT_EXPIRES_IN="7d" # Token 有效期
VITE_API_BASE_URL="http://localhost:3001/api" # 前端 API 基地址
安全提示:JWT_SECRET 在生产环境中必须替换为强随机字符串。当前默认种子账号密码仅用于本地开发。
常用命令
# 安装依赖
npm install
# 开发模式(前后端同时启动)
npm run dev
# 仅启动前端(Vite,端口 5173)
npm run dev:client
# 仅启动后端(nodemon + tsx,端口 3001)
npm run dev:server
# 数据库迁移(修改 schema.prisma 后执行)
npm run db:migrate
# 生成 Prisma Client(schema 变更后)
npm run db:generate
# 导入题库与人格数据(修改 data/*.json 后执行)
npm run db:seed
# Prisma Studio(可视化查看/编辑数据库)
npm run db:studio
# 构建生产包(客户端 + 服务端)
npm run build
# 生产环境启动服务端(需先 build)
npm start
开发时,Vite 开发服务器会将 /api/* 请求代理到 http://localhost:3001,因此前后端可以独立运行而不产生 CORS 问题。
数据库模型
SQLite 数据库,通过 Prisma 管理。核心表:
| 表名 | 作用 | 关键字段 |
|---|---|---|
User | 用户 | email, password, name, role (STUDENT/CREATOR/ADMIN) |
Question | 题目 | content, options (JSON 字符串), category, status |
Personality | 人格类型 | id (如 "ENJA"), name, title, description, traits (JSON), icon, color, pixelArt (JSON) |
Template | 测试模板 | name, description, isDefault |
TemplateQuestion | 模板-题目关联 | templateId, questionId, order |
TestResult | 测试结果 | userId, templateId, personalityId, scores (JSON), answers (JSON) |
Submission | 创作者提交 | type (QUESTION/PERSONALITY), content (JSON), status, remark |
重要约定:options、traits、scores、answers、pixelArt 等字段在数据库中以 JSON 字符串存储,读写时需要通过 JSON.parse / JSON.stringify 转换。前后端共享类型定义在 src/shared/types.ts 中,这些字段在 TypeScript 层面已经是结构化对象。
API 接口概览
所有 API 返回统一格式:{ success: boolean, data?: T, message?: string, error?: string }。
POST /api/auth/register/POST /api/auth/login/GET /api/auth/meGET /api/test/templates/GET /api/test/templates/:id/questions/POST /api/test/submit/GET /api/test/results/:idGET /api/personalities?status=APPROVED/GET /api/personalities/:idGET|POST|PUT|DELETE /api/admin/*(需ADMIN角色)POST|GET /api/creator/*(需CREATOR或ADMIN角色)
详细接口列表见 docs/README.md。
用户角色与权限
| 角色 | 权限 |
|---|---|
STUDENT | 答题、查看结果、浏览图鉴 |
CREATOR | 提交新题目/新人格、查看自己的提交记录 |
ADMIN | 审核创作者提交、管理所有题目和人格、查看全部数据 |
默认种子账号(由 prisma/seed.ts 创建):
- 管理员:
admin@example.com/admin123 - 创作者:
creator@example.com/creator123
计分逻辑
核心代码位于 src/server/routes/test.routes.ts。
- 遍历用户提交的答案。
- 每个选项的
scores累加到对应维度(E、I、S、N、J、P、A、H)。 - 每个维度比较两边得分,高者胜(同分默认左极)。
- 组合成 4 位人格代码(如
ENJA)。 - 查询
Personality表返回结果并持久化到TestResult。
评分标准细节见 docs/scoring_criteria.md。
代码风格与约定
- 语言:TypeScript(严格模式开启)。
- 模块系统:ESM(
"type": "module")。 - 导入扩展名:所有相对导入必须使用
.js扩展名(包括指向.ts文件)。 - 前端严格检查:
noUnusedLocals: true、noUnusedParameters: true。 - 命名:React 组件使用 PascalCase 默认导出;hooks 使用
useXxx命名;store 使用 camelCase。 - 样式:优先使用 Tailwind 工具类;复杂条件类名通过
cn()(clsx+tailwind-merge)合并。 - 状态管理:
- 全局共享状态使用 Zustand(
authStore、testStore)。 - 局部状态使用
useState/useEffect。
- 全局共享状态使用 Zustand(
- API 调用:统一使用
src/client/lib/api.ts中的api.get/post/put/del,禁止直接写裸fetch。 - 类型复用:前后端共享类型必须放在
src/shared/types.ts,通过@shared/types导入。 - 中文内容:所有用户界面文本、错误提示、日志输出均使用中文。
测试策略
当前项目未配置任何测试框架。 没有单元测试、集成测试或 E2E 测试。
若需添加测试,建议:
- 后端 API:使用 Vitest +
supertest测试 Express 路由。 - 前端组件:使用 Vitest + React Testing Library。
- 数据库测试:使用独立测试数据库或 Prisma 的
jest/vitest环境重置方案。
部署说明
- 设置环境变量(
.env)。 - 执行
npm run build:tsc(前端类型检查)vite build(构建客户端到dist/client)tsc -p tsconfig.server.json(编译服务端到dist/server)
- 执行数据库迁移:
npx prisma migrate deploy。 - 执行种子(可选):
npm run db:seed。 - 启动服务:
npm start(运行dist/server/index.js)。
注意:构建产物中 dist/client 为静态前端资源,需要由外部 Web 服务器(如 Nginx)或 Express 自行托管。当前代码中 Express 并未内置静态文件托管逻辑,生产部署时需额外配置。
关键文件速查
| 场景 | 文件 |
|---|---|
| 修改题库内容 | data/questions.json → 运行 npm run db:seed |
| 修改人格定义 | data/personalities.json → 运行 npm run db:seed |
| 修改数据库模型 | prisma/schema.prisma → 运行 npm run db:migrate |
| 添加新 API | src/server/routes/*.routes.ts → 在 app.ts 中挂载 |
| 添加新页面 | src/client/pages/*.tsx → 在 App.tsx 中配置路由 |
| 添加共享类型 | src/shared/types.ts |
| 调整主题色/动画 | tailwind.config.js + src/client/index.css |
| 调整代理/构建 | vite.config.ts |
扩展中的功能(参考 docs/todo.md)
根据 docs/todo.md,以下功能已在计划中或部分实现:
- 题目乱序(固定抽取全部题目,仅打乱顺序)
- 人格详情页(
PersonalityDetailPage.tsx已存在) - 结果页分享卡片生成(适合朋友圈传播的图片导出)
- 测试历史记录(学生查看过往结果)
- 管理员数据统计(各人格占比、各题选项分布)
- 多模板支持(除默认模板外创建不同主题测试)
安全注意事项
- JWT 密钥:生产环境必须将
JWT_SECRET替换为强随机字符串。 - 密码存储:使用
bcryptjs加盐哈希(默认 10 rounds)。 - CORS:当前配置为
app.use(cors()),生产环境应限制为具体域名。 - SQL 注入:通过 Prisma ORM 参数化查询天然防护。
- 认证中间件:
authMiddleware校验 Bearer token 后将用户信息挂载到req.user;后续路由通过req.user!访问,需确保中间件已正确挂载。 - 权限校验:
requireRole中间件必须在authMiddleware之后挂载,否则req.user为空会导致拒绝访问。