第一版提交,答题功能OK,题库管理待完善
This commit is contained in:
198
.opencode/100-business/domain.yaml
Normal file
198
.opencode/100-business/domain.yaml
Normal file
@@ -0,0 +1,198 @@
|
||||
domains:
|
||||
- name: "User"
|
||||
description: "参与答题的用户"
|
||||
entities:
|
||||
- name: "User"
|
||||
description: "用户信息实体"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "用户唯一标识"
|
||||
- name: "name"
|
||||
type: "string"
|
||||
description: "用户姓名,2-20位中英文"
|
||||
- name: "phone"
|
||||
type: "string"
|
||||
description: "手机号,11位数字"
|
||||
- name: "password"
|
||||
type: "string"
|
||||
description: "登录密码 (敏感字段,仅管理员可见;当前版本为明文存储)"
|
||||
- name: "createdAt"
|
||||
type: "datetime"
|
||||
description: "创建时间"
|
||||
|
||||
- name: "Question"
|
||||
description: "题库管理"
|
||||
entities:
|
||||
- name: "QuestionCategory"
|
||||
description: "题目类别"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "类别唯一标识"
|
||||
- name: "name"
|
||||
type: "string"
|
||||
description: "类别名称 (例如:通用/网络/数据库等)"
|
||||
- name: "createdAt"
|
||||
type: "datetime"
|
||||
description: "创建时间"
|
||||
- name: "Question"
|
||||
description: "题目实体"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "题目唯一标识"
|
||||
- name: "content"
|
||||
type: "string"
|
||||
description: "题目内容"
|
||||
- name: "type"
|
||||
type: "enum"
|
||||
values: ["single", "multiple", "judgment", "text"]
|
||||
description: "题型"
|
||||
- name: "category"
|
||||
type: "string"
|
||||
description: "题目类别名称 (默认:通用)"
|
||||
- name: "options"
|
||||
type: "json"
|
||||
description: "选项内容 (JSON格式)"
|
||||
- name: "answer"
|
||||
type: "string"
|
||||
description: "标准答案"
|
||||
- name: "score"
|
||||
type: "integer"
|
||||
description: "分值"
|
||||
- name: "createdAt"
|
||||
type: "datetime"
|
||||
description: "创建时间"
|
||||
|
||||
- name: "Exam"
|
||||
description: "考试科目与考试任务"
|
||||
entities:
|
||||
- name: "ExamSubject"
|
||||
description: "考试科目 (一套出题规则)"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "科目唯一标识"
|
||||
- name: "name"
|
||||
type: "string"
|
||||
description: "科目名称"
|
||||
- name: "totalScore"
|
||||
type: "integer"
|
||||
description: "总分"
|
||||
- name: "timeLimitMinutes"
|
||||
type: "integer"
|
||||
description: "答题时间限制(分钟),默认60"
|
||||
- name: "typeRatios"
|
||||
type: "json"
|
||||
description: "题型比重 (single/multiple/judgment/text)"
|
||||
- name: "categoryRatios"
|
||||
type: "json"
|
||||
description: "题目类别比重 (categoryName -> ratio)"
|
||||
- name: "createdAt"
|
||||
type: "datetime"
|
||||
description: "创建时间"
|
||||
- name: "updatedAt"
|
||||
type: "datetime"
|
||||
description: "更新时间"
|
||||
- name: "ExamTask"
|
||||
description: "考试任务 (给用户分派某个科目)"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "任务唯一标识"
|
||||
- name: "name"
|
||||
type: "string"
|
||||
description: "任务名称"
|
||||
- name: "subjectId"
|
||||
type: "string"
|
||||
description: "关联科目ID"
|
||||
- name: "startAt"
|
||||
type: "datetime"
|
||||
description: "开始答题时间"
|
||||
- name: "endAt"
|
||||
type: "datetime"
|
||||
description: "截止答题时间"
|
||||
- name: "createdAt"
|
||||
type: "datetime"
|
||||
description: "创建时间"
|
||||
- name: "ExamTaskUser"
|
||||
description: "任务参与用户"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "关联唯一标识"
|
||||
- name: "taskId"
|
||||
type: "string"
|
||||
description: "任务ID"
|
||||
- name: "userId"
|
||||
type: "string"
|
||||
description: "用户ID"
|
||||
- name: "assignedAt"
|
||||
type: "datetime"
|
||||
description: "分派时间"
|
||||
|
||||
- name: "Quiz"
|
||||
description: "答题业务"
|
||||
entities:
|
||||
- name: "QuizRecord"
|
||||
description: "答题记录"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "记录唯一标识"
|
||||
- name: "userId"
|
||||
type: "string"
|
||||
description: "用户ID"
|
||||
- name: "totalScore"
|
||||
type: "integer"
|
||||
description: "总得分"
|
||||
- name: "correctCount"
|
||||
type: "integer"
|
||||
description: "正确题数"
|
||||
- name: "totalCount"
|
||||
type: "integer"
|
||||
description: "总题数"
|
||||
- name: "createdAt"
|
||||
type: "datetime"
|
||||
description: "答题时间"
|
||||
- name: "QuizAnswer"
|
||||
description: "单题答题详情"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "答案唯一标识"
|
||||
- name: "recordId"
|
||||
type: "string"
|
||||
description: "关联的答题记录ID"
|
||||
- name: "questionId"
|
||||
type: "string"
|
||||
description: "题目ID"
|
||||
- name: "userAnswer"
|
||||
type: "string"
|
||||
description: "用户提交的答案"
|
||||
- name: "isCorrect"
|
||||
type: "boolean"
|
||||
description: "是否正确"
|
||||
- name: "score"
|
||||
type: "integer"
|
||||
description: "该题得分"
|
||||
|
||||
- name: "System"
|
||||
description: "系统配置"
|
||||
entities:
|
||||
- name: "SystemConfig"
|
||||
description: "系统全局配置"
|
||||
attributes:
|
||||
- name: "id"
|
||||
type: "string"
|
||||
description: "配置唯一标识"
|
||||
- name: "configType"
|
||||
type: "string"
|
||||
description: "配置类型键"
|
||||
- name: "configValue"
|
||||
type: "string"
|
||||
description: "配置值 (通常为JSON字符串)"
|
||||
- name: "updatedAt"
|
||||
type: "datetime"
|
||||
description: "更新时间"
|
||||
648
.opencode/200-api/api.yaml
Normal file
648
.opencode/200-api/api.yaml
Normal file
@@ -0,0 +1,648 @@
|
||||
openapi: "3.0.0"
|
||||
info:
|
||||
title: "Survey System API"
|
||||
version: "1.1.0"
|
||||
components:
|
||||
securitySchemes:
|
||||
AdminBearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
schemas:
|
||||
User:
|
||||
type: object
|
||||
required: ["id", "name", "phone", "createdAt"]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
AdminUserView:
|
||||
type: object
|
||||
required: ["id", "name", "phone", "createdAt", "password"]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
password:
|
||||
type: string
|
||||
description: "密码 (敏感字段;前端掩码显示)"
|
||||
QuestionCategory:
|
||||
type: object
|
||||
required: ["id", "name", "createdAt"]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
Question:
|
||||
type: object
|
||||
required: ["content", "type", "answer", "score"]
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: ["single", "multiple", "judgment", "text"]
|
||||
category:
|
||||
type: string
|
||||
description: "题目类别名称,缺省为通用"
|
||||
default: "通用"
|
||||
options:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
answer:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: array
|
||||
items:
|
||||
type: string
|
||||
score:
|
||||
type: number
|
||||
ExamSubject:
|
||||
type: object
|
||||
required: ["id", "name", "totalScore", "timeLimitMinutes", "typeRatios", "categoryRatios"]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
totalScore:
|
||||
type: integer
|
||||
timeLimitMinutes:
|
||||
type: integer
|
||||
default: 60
|
||||
typeRatios:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
single:
|
||||
type: number
|
||||
multiple:
|
||||
type: number
|
||||
judgment:
|
||||
type: number
|
||||
text:
|
||||
type: number
|
||||
categoryRatios:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: number
|
||||
ExamTask:
|
||||
type: object
|
||||
required: ["id", "name", "subjectId", "startAt", "endAt"]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
subjectId:
|
||||
type: string
|
||||
startAt:
|
||||
type: string
|
||||
format: date-time
|
||||
endAt:
|
||||
type: string
|
||||
format: date-time
|
||||
Pagination:
|
||||
type: object
|
||||
required: ["page", "limit", "total", "pages"]
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
limit:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
pages:
|
||||
type: integer
|
||||
paths:
|
||||
/api/users:
|
||||
post:
|
||||
summary: "创建用户或登录"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: ["name", "phone", "password"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: "用户姓名,2-20位中英文"
|
||||
phone:
|
||||
type: string
|
||||
description: "手机号"
|
||||
password:
|
||||
type: string
|
||||
description: "登录密码"
|
||||
responses:
|
||||
"200":
|
||||
description: "User created"
|
||||
|
||||
/api/users/{id}:
|
||||
get:
|
||||
summary: "获取用户信息"
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "User details"
|
||||
|
||||
/api/questions/import:
|
||||
post:
|
||||
summary: "Excel导入题目"
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
responses:
|
||||
"200":
|
||||
description: "Import result"
|
||||
|
||||
/api/questions:
|
||||
get:
|
||||
summary: "获取题目列表"
|
||||
parameters:
|
||||
- name: "type"
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: ["single", "multiple", "judgment", "text"]
|
||||
- name: "category"
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: "page"
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- name: "limit"
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: "List of questions"
|
||||
post:
|
||||
summary: "添加单题"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Question"
|
||||
responses:
|
||||
"200":
|
||||
description: "Question created"
|
||||
|
||||
/api/questions/{id}:
|
||||
put:
|
||||
summary: "更新题目"
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Question"
|
||||
responses:
|
||||
"200":
|
||||
description: "Question updated"
|
||||
delete:
|
||||
summary: "删除题目"
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Question deleted"
|
||||
|
||||
/api/quiz/generate:
|
||||
post:
|
||||
summary: "生成随机试卷"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: ["userId", "subjectId"]
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
subjectId:
|
||||
type: string
|
||||
description: "考试科目ID"
|
||||
responses:
|
||||
"200":
|
||||
description: "Generated quiz"
|
||||
|
||||
/api/quiz/submit:
|
||||
post:
|
||||
summary: "提交答题"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: ["userId", "answers"]
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
answers:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
responses:
|
||||
"200":
|
||||
description: "Submission result"
|
||||
|
||||
/api/quiz/records/{userId}:
|
||||
get:
|
||||
summary: "获取用户答题记录"
|
||||
parameters:
|
||||
- name: "userId"
|
||||
in: "path"
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "User quiz records"
|
||||
|
||||
/api/quiz/records/detail/{recordId}:
|
||||
get:
|
||||
summary: "获取答题记录详情"
|
||||
parameters:
|
||||
- name: "recordId"
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Record detail"
|
||||
|
||||
/api/admin/login:
|
||||
post:
|
||||
summary: "管理员登录"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: ["username", "password"]
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Login success"
|
||||
|
||||
/api/admin/statistics:
|
||||
get:
|
||||
summary: "获取统计数据"
|
||||
responses:
|
||||
"200":
|
||||
description: "Statistics data"
|
||||
|
||||
/api/admin/users:
|
||||
get:
|
||||
summary: "获取用户列表 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: "page"
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- name: "limit"
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: "User list"
|
||||
delete:
|
||||
summary: "删除用户 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: ["userId"]
|
||||
properties:
|
||||
userId:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Deleted"
|
||||
|
||||
/api/admin/users/export:
|
||||
get:
|
||||
summary: "导出用户 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: "Export users"
|
||||
|
||||
/api/admin/users/import:
|
||||
post:
|
||||
summary: "导入用户 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
responses:
|
||||
"200":
|
||||
description: "Import users"
|
||||
|
||||
/api/admin/users/{userId}/records:
|
||||
get:
|
||||
summary: "获取用户历史答题记录 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: userId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "User records"
|
||||
|
||||
/api/admin/question-categories:
|
||||
get:
|
||||
summary: "获取题目类别列表 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: "Category list"
|
||||
post:
|
||||
summary: "新增题目类别 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Created"
|
||||
|
||||
/api/admin/question-categories/{id}:
|
||||
put:
|
||||
summary: "更新题目类别 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Updated"
|
||||
delete:
|
||||
summary: "删除题目类别 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Deleted"
|
||||
|
||||
/api/admin/subjects:
|
||||
get:
|
||||
summary: "获取考试科目列表 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: "Subjects"
|
||||
post:
|
||||
summary: "新增考试科目 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ExamSubject"
|
||||
responses:
|
||||
"200":
|
||||
description: "Created"
|
||||
|
||||
/api/admin/subjects/{id}:
|
||||
put:
|
||||
summary: "更新考试科目 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ExamSubject"
|
||||
responses:
|
||||
"200":
|
||||
description: "Updated"
|
||||
delete:
|
||||
summary: "删除考试科目 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Deleted"
|
||||
|
||||
/api/subjects:
|
||||
get:
|
||||
summary: "获取考试科目列表 (用户)"
|
||||
responses:
|
||||
"200":
|
||||
description: "Subjects"
|
||||
|
||||
/api/admin/tasks:
|
||||
get:
|
||||
summary: "获取考试任务列表 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: "Tasks"
|
||||
post:
|
||||
summary: "新增考试任务并分派用户 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: ["name", "subjectId", "startAt", "endAt", "userIds"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
subjectId:
|
||||
type: string
|
||||
startAt:
|
||||
type: string
|
||||
format: date-time
|
||||
endAt:
|
||||
type: string
|
||||
format: date-time
|
||||
userIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Created"
|
||||
|
||||
/api/admin/tasks/{id}:
|
||||
put:
|
||||
summary: "更新考试任务 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
"200":
|
||||
description: "Updated"
|
||||
delete:
|
||||
summary: "删除考试任务 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Deleted"
|
||||
|
||||
/api/admin/tasks/{id}/report:
|
||||
get:
|
||||
summary: "导出任务报表 (管理员)"
|
||||
security:
|
||||
- AdminBearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: "Task report"
|
||||
|
||||
/api/admin/config:
|
||||
get:
|
||||
summary: "获取抽题配置"
|
||||
responses:
|
||||
"200":
|
||||
description: "Quiz config"
|
||||
put:
|
||||
summary: "更新抽题配置"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
"200":
|
||||
description: "Config updated"
|
||||
256
.opencode/300-database/schema.yaml
Normal file
256
.opencode/300-database/schema.yaml
Normal file
@@ -0,0 +1,256 @@
|
||||
database:
|
||||
type: "sqlite"
|
||||
version: "3"
|
||||
tables:
|
||||
- name: "users"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "name"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
constraints: "CHECK(length(name) >= 2 AND length(name) <= 20)"
|
||||
- name: "phone"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
unique: true
|
||||
constraints: "CHECK(length(phone) = 11 AND phone LIKE '1%' AND substr(phone, 2, 1) BETWEEN '3' AND '9')"
|
||||
- name: "password"
|
||||
type: "TEXT"
|
||||
comment: "用户登录密码 (当前版本为明文存储)"
|
||||
- name: "created_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
indexes:
|
||||
- name: "idx_users_phone"
|
||||
columns: ["phone"]
|
||||
- name: "idx_users_created_at"
|
||||
columns: ["created_at"]
|
||||
|
||||
- name: "question_categories"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "name"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
unique: true
|
||||
- name: "created_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
indexes:
|
||||
- name: "idx_question_categories_name"
|
||||
columns: ["name"]
|
||||
|
||||
- name: "questions"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "content"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
- name: "type"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
constraints: "CHECK(type IN ('single', 'multiple', 'judgment', 'text'))"
|
||||
- name: "category"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
default: "'通用'"
|
||||
comment: "题目类别名称 (无类别时默认通用)"
|
||||
- name: "options"
|
||||
type: "TEXT"
|
||||
comment: "JSON格式存储选项"
|
||||
- name: "answer"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
- name: "score"
|
||||
type: "INTEGER"
|
||||
nullable: false
|
||||
constraints: "CHECK(score > 0)"
|
||||
- name: "created_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
indexes:
|
||||
- name: "idx_questions_type"
|
||||
columns: ["type"]
|
||||
- name: "idx_questions_category"
|
||||
columns: ["category"]
|
||||
- name: "idx_questions_score"
|
||||
columns: ["score"]
|
||||
|
||||
- name: "exam_subjects"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "name"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
unique: true
|
||||
- name: "total_score"
|
||||
type: "INTEGER"
|
||||
nullable: false
|
||||
constraints: "CHECK(total_score > 0)"
|
||||
- name: "time_limit_minutes"
|
||||
type: "INTEGER"
|
||||
nullable: false
|
||||
default: "60"
|
||||
constraints: "CHECK(time_limit_minutes > 0)"
|
||||
- name: "type_ratios"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
comment: "题型比重 JSON (single/multiple/judgment/text)"
|
||||
- name: "category_ratios"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
comment: "题目类别比重 JSON (categoryName->ratio)"
|
||||
- name: "created_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
- name: "updated_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
indexes:
|
||||
- name: "idx_exam_subjects_name"
|
||||
columns: ["name"]
|
||||
|
||||
- name: "exam_tasks"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "name"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
- name: "subject_id"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
foreignKey:
|
||||
table: "exam_subjects"
|
||||
column: "id"
|
||||
- name: "start_at"
|
||||
type: "DATETIME"
|
||||
nullable: false
|
||||
- name: "end_at"
|
||||
type: "DATETIME"
|
||||
nullable: false
|
||||
- name: "created_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
indexes:
|
||||
- name: "idx_exam_tasks_subject_id"
|
||||
columns: ["subject_id"]
|
||||
- name: "idx_exam_tasks_start_at"
|
||||
columns: ["start_at"]
|
||||
- name: "idx_exam_tasks_end_at"
|
||||
columns: ["end_at"]
|
||||
|
||||
- name: "exam_task_users"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "task_id"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
foreignKey:
|
||||
table: "exam_tasks"
|
||||
column: "id"
|
||||
- name: "user_id"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
foreignKey:
|
||||
table: "users"
|
||||
column: "id"
|
||||
- name: "assigned_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
indexes:
|
||||
- name: "idx_exam_task_users_task_id"
|
||||
columns: ["task_id"]
|
||||
- name: "idx_exam_task_users_user_id"
|
||||
columns: ["user_id"]
|
||||
|
||||
- name: "quiz_records"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "user_id"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
foreignKey:
|
||||
table: "users"
|
||||
column: "id"
|
||||
- name: "total_score"
|
||||
type: "INTEGER"
|
||||
nullable: false
|
||||
- name: "correct_count"
|
||||
type: "INTEGER"
|
||||
nullable: false
|
||||
- name: "total_count"
|
||||
type: "INTEGER"
|
||||
nullable: false
|
||||
- name: "created_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
indexes:
|
||||
- name: "idx_quiz_records_user_id"
|
||||
columns: ["user_id"]
|
||||
- name: "idx_quiz_records_created_at"
|
||||
columns: ["created_at"]
|
||||
|
||||
- name: "quiz_answers"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "record_id"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
foreignKey:
|
||||
table: "quiz_records"
|
||||
column: "id"
|
||||
- name: "question_id"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
foreignKey:
|
||||
table: "questions"
|
||||
column: "id"
|
||||
- name: "user_answer"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
- name: "score"
|
||||
type: "INTEGER"
|
||||
nullable: false
|
||||
- name: "is_correct"
|
||||
type: "BOOLEAN"
|
||||
nullable: false
|
||||
- name: "created_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
indexes:
|
||||
- name: "idx_quiz_answers_record_id"
|
||||
columns: ["record_id"]
|
||||
- name: "idx_quiz_answers_question_id"
|
||||
columns: ["question_id"]
|
||||
|
||||
- name: "system_configs"
|
||||
columns:
|
||||
- name: "id"
|
||||
type: "TEXT"
|
||||
primaryKey: true
|
||||
- name: "config_type"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
unique: true
|
||||
- name: "config_value"
|
||||
type: "TEXT"
|
||||
nullable: false
|
||||
- name: "updated_at"
|
||||
type: "DATETIME"
|
||||
default: "CURRENT_TIMESTAMP"
|
||||
25
.opencode/command/openspec-apply.md
Normal file
25
.opencode/command/openspec-apply.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
agent: build
|
||||
description: Implement an approved OpenSpec change and keep tasks in sync.
|
||||
---
|
||||
The user has requested to implement the following change proposal. Find the change proposal and follow the instructions below. If you're not sure or if ambiguous, ask for clarification from the user.
|
||||
<UserRequest>
|
||||
$ARGUMENTS
|
||||
</UserRequest>
|
||||
<!-- OPENSPEC:START -->
|
||||
**Guardrails**
|
||||
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
|
||||
- Keep changes tightly scoped to the requested outcome.
|
||||
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
|
||||
|
||||
**Steps**
|
||||
Track these steps as TODOs and complete them one by one.
|
||||
1. Read `changes/<id>/proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria.
|
||||
2. Work through tasks sequentially, keeping edits minimal and focused on the requested change.
|
||||
3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished.
|
||||
4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality.
|
||||
5. Reference `openspec list` or `openspec show <item>` when additional context is required.
|
||||
|
||||
**Reference**
|
||||
- Use `openspec show <id> --json --deltas-only` if you need additional context from the proposal while implementing.
|
||||
<!-- OPENSPEC:END -->
|
||||
28
.opencode/command/openspec-archive.md
Normal file
28
.opencode/command/openspec-archive.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
agent: build
|
||||
description: Archive a deployed OpenSpec change and update specs.
|
||||
---
|
||||
<ChangeId>
|
||||
$ARGUMENTS
|
||||
</ChangeId>
|
||||
<!-- OPENSPEC:START -->
|
||||
**Guardrails**
|
||||
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
|
||||
- Keep changes tightly scoped to the requested outcome.
|
||||
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
|
||||
|
||||
**Steps**
|
||||
1. Determine the change ID to archive:
|
||||
- If this prompt already includes a specific change ID (for example inside a `<ChangeId>` block populated by slash-command arguments), use that value after trimming whitespace.
|
||||
- If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
|
||||
- Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
|
||||
- If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
|
||||
2. Validate the change ID by running `openspec list` (or `openspec show <id>`) and stop if the change is missing, already archived, or otherwise not ready to archive.
|
||||
3. Run `openspec archive <id> --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work).
|
||||
4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`.
|
||||
5. Validate with `openspec validate --strict` and inspect with `openspec show <id>` if anything looks off.
|
||||
|
||||
**Reference**
|
||||
- Use `openspec list` to confirm change IDs before archiving.
|
||||
- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off.
|
||||
<!-- OPENSPEC:END -->
|
||||
29
.opencode/command/openspec-proposal.md
Normal file
29
.opencode/command/openspec-proposal.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
agent: build
|
||||
description: Scaffold a new OpenSpec change and validate strictly.
|
||||
---
|
||||
The user has requested the following change proposal. Use the openspec instructions to create their change proposal.
|
||||
<UserRequest>
|
||||
$ARGUMENTS
|
||||
</UserRequest>
|
||||
<!-- OPENSPEC:START -->
|
||||
**Guardrails**
|
||||
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
|
||||
- Keep changes tightly scoped to the requested outcome.
|
||||
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
|
||||
- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files.
|
||||
|
||||
**Steps**
|
||||
1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification.
|
||||
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes/<id>/`.
|
||||
3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing.
|
||||
4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs.
|
||||
5. Draft spec deltas in `changes/<id>/specs/<capability>/spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant.
|
||||
6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work.
|
||||
7. Validate with `openspec validate <id> --strict` and resolve every issue before sharing the proposal.
|
||||
|
||||
**Reference**
|
||||
- Use `openspec show <id> --json --deltas-only` or `openspec show <spec> --type spec` to inspect details when validation fails.
|
||||
- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones.
|
||||
- Explore the codebase with `rg <keyword>`, `ls`, or direct file reads so proposals align with current implementation realities.
|
||||
<!-- OPENSPEC:END -->
|
||||
14
.opencode/manifest.yaml
Normal file
14
.opencode/manifest.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
specVersion: "0.1.0"
|
||||
info:
|
||||
title: "问卷调查系统 (Survey System)"
|
||||
description: "一个功能完善的在线考试/问卷调查平台,支持多种题型、随机抽题、免注册答题。"
|
||||
version: "1.1.0"
|
||||
license:
|
||||
name: "Proprietary"
|
||||
references:
|
||||
- path: "./100-business/domain.yaml"
|
||||
type: "domain"
|
||||
- path: "./200-api/api.yaml"
|
||||
type: "api"
|
||||
- path: "./300-database/schema.yaml"
|
||||
type: "database"
|
||||
Reference in New Issue
Block a user