OJ 后端接口开发
03-后端接口开发
数据表设计&代码结构设计扩展知识点
【业务前缀】概念
什么情况下要加业务前缀?什么情况下不加?
加业务前缀的好处,防止多个表都有类似的类,产生冲突;不加的前提,因为可能这个类是多个业务之间共享的,能够复用的。
【定义VO类】概念
作用是专门给前端返回对象,可以节约网络传输大小、或者过滤字段(脱敏)、保证安全性。
【用户ID生成规则】
为了防止用户按照id顺序爬取题目,建议把id的生成规则改为ASSIGN_ID,而不是从1开始自增(@TableId(type = IdType.ASSIGN_ID)
)
数据表设计
1.用户表
-- 用户表
create table if not exists user
(
id bigint auto_increment comment 'id' primary key,
userAccount varchar(256) not null comment '账号',
userPassword varchar(512) not null comment '密码',
unionId varchar(256) null comment '微信开放平台id',
mpOpenId varchar(256) null comment '公众号openId',
userName varchar(256) null comment '用户昵称',
userAvatar varchar(1024) null comment '用户头像',
userProfile varchar(512) null comment '用户简介',
userRole varchar(256) default 'user' not null comment '用户角色:user/admin/ban',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除',
index idx_unionId (unionId)
) comment '用户' collate = utf8mb4_unicode_ci;
2.题目表
设计思路
题目标题
题目内容:存放题目的介绍、输入输出提示、描述、具体的详情
题目标签(json数组字符串):栈、队列、链表、简单、中等、困难
题目答案:管理员/用户设置的标准答案
提交数、通过题目的人数等;便于分析统计(可以考虑根据通过率自动给题目打难易度标签)
判题相关字段:
如果题目不是很复杂,用例文件的大小不大,可以直接存在数据库表里
但是用例文件很大,>512KB建议单独存放在一个文件中,数据库中只保存文件url(类似存储用户头像)
judgeConfig判题配置(json对象):
- 时间限制 timeLimit
- 内存限制 memoryLimit
judgeCase 判题用例(json数组):
- 每一个元素是:一个输入用例对应一个输出用例
[
{
"input": "1 2",
"output": "3 4"
},
{
"input": "1 2",
"output" "2 4"
}
]
存json的好处:便于拓展,只需要改变对象内部的字段,而不用修改数据库表(可能会影响数据库)
{
"timeLimit": 1000,
"memoryLimit": 1000,
"stackLimit": 1000
}
存json的前提:
(1)不需要根据每个字段去倒查这条数据
(2)字段含义相关,属于同类的值
(3)字段存储空间占用不能太大
其他扩展字段:
- 通过率
- 判题类型
SQL
-- 题目表
create table if not exists question
(
id bigint auto_increment comment 'id' primary key,
title varchar(512) null comment '标题',
content text null comment '内容',
tags varchar(1024) null comment '标签列表(json 数组)',
answer text null comment '题目答案',
submitNum int default 0 not null comment '题目提交数',
acceptedNum int default 0 not null comment '题目通过数',
judgeCase text null comment '判题用例(json 数组)',
judgeConfig text null comment '判题配置(json 对象)',
thumbNum int default 0 not null comment '点赞数',
favourNum int default 0 not null comment '收藏数',
userId bigint not null comment '创建用户 id',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除',
index idx_userId (userId)
) comment '题目' collate = utf8mb4_unicode_ci;
3.用户提交表
设计思路
用户提交表:用于存储 哪个用户提交了哪道题,存放判题结果等,其相关字段参考:
提交用户id:userId
题目id:questionId
语言:language
用户提交的代码:code
题状态:status(待判题、判题中、成功、失败)
判题信息(判题过程中得到的一些信息,比如程序的失败原因、程序执行消耗的时间、空间):
judgeInfo(json对象)
{
"message": "程序执行信息",
"time": 1000,// 单位为 ms
"memory": 1000,// 单位为 kb
}
判断信息枚举值:
- Accepted 成功
- Wrong Answer 答案错误
- Compile Error 变异错误
- Memory Limit Exceeded 内存溢出
- Time Limit Exceeded 超时
- Presentation Error 展示错误
- Output Limit Exceeded 输出溢出
- Waiting 等待中
- Dangerous Operation 危险操作
- Runtime Error 运行错误(用户程序的问题)
- System Error 系统错误(做系统人的问题)
SQL
-- 题目提交表
create table if not exists question_submit
(
id bigint auto_increment comment 'id' primary key,
language varchar(128) not null comment '编程语言',
code text not null comment '用户代码',
judgeInfo text null comment '判题信息(json 对象)',
status int default 0 not null comment '判题状态(0 - 待判题、1 - 判题中、2 - 成功、3 - 失败)',
questionId bigint not null comment '题目 id',
userId bigint not null comment '创建用户 id',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除',
index idx_questionId (questionId),
index idx_userId (userId)
) comment '题目提交';
4.数据库优化
扩展知识点:数据库索引的构建
什么情况下适合加索引?如何选择给哪个字段加索引呢?
首先从业务出发,无论是单个索引、还是联合索引,都要从实际的查询语句、字段枚举值的区分度、字段的类型考虑(where条件指定的字段)
比如:where userId = 1 and questionId = 2
可以选择根据userId和questionId分别建立索引(需要分别根据这两个字段单独查询);也可以选择给这两个字段建立联合索引(所查询的字段是绑定在一起的)。
原则上:能不用索引就不用索引;能用单个索引就别用联合/多个索引;不要给没区分度的字段加索引(比如性别,就男/女)。因为索引也是要占用空间的。
模块开发
后端开发流程
构建流程说明
(1)根据功能设计库表
(2)自动生成基本的增删改查(mapper 和 service 层的基本功能)
(3)编写Controller层,实现基本的增删改查和权限校验(复制粘贴)
(4)去根据业务定制开发新的功能/编写新的代码
更好的方法,编写自己的代码生成器
构建参考(Question、QuestionSubmit)
构建参考
(1)在module下创建一个oj模块,存放oj相关的所有内容
(2)借助MybatisX生成question、question_sumbit相关的代码文件,将其按照一定规范放入oj模块下,其中entity中的实体需要调整两个内容:isDelete添加@TableLogic
逻辑删除注解,id生成规则指定@TableId(type = IdType.ASSIGN_ID)
(3)修改application.yml文件中接口文档生成扫描的contoller:com.noob.module.oj.controller(引入项目设定的controller)
(4)构建controller层,随后依次调试接口内容(controller、service、dto等相关)
将生成的代码移动到oj模块
构建controller层(复制TemplateController,分别构建QuestionController、QuestionSubmitController)
字段调整:模板=》问题、Template=》Question、template=》question(以此类推,需要注意一些关键字不要被替换掉)
替换完成,关注导入的包部分(根据飘红信息提示分别按需引入相关的包)
- QuestionConstant:问题相关常量
- QuestionVO:问题视图(封装问题详情,返回给前端)
- QuestionAddRequest:添加操作请求参数
- QuestionUpdateRequest:修改操作请求参数
- QuestionStatusUpdateRequest:状态修改请求参数(如果此处不涉及状态修改,暂时不需要设定,可以取消相关的内容)
- QuestionQueryRequest:查找操作请求参数
有关请求参数的设定可以结合接口交互实际需要什么参数来决定,如果是构建基础的CRUD操作,则选择一些需要用户填充的核心字段即可
- service:service接口定义和实现(此处分页查询调整为构建动态模板实现,简化业务代码层处理)
- 对应mapper层构建:构建动态SQL
出现类型转化异常,需要在mapper.xml中设定resultMap显式指定对应字段映射类型,或者需要借助mybatis组件拦截配置(日期类型转化)
Question模块构建完成,基于SQL动态构建搜索、分页,则QuestionSubmit则可基于Question再快速构建,其构建参考
补充说明
区分不同Request的内容:XXXUpdateRequest是提供给管理员更新用的(可以指定更多的字段);XXXEditRequest是提供给用户用的(只允许修改指定的字段信息)
JSON字段映射构建:为了更方便处理数据表字段中定义的字段信息,给对应的json字段创建对应的实体类用于程序中转化:例如judgeConfig、judgeInfo、judgeCase分别创建对应的实体
// 参考
/**
* 题目用例
*/
public class JudgeCase {
/**
* 输入用例
*/
private String input;
/**
* 输出用例
*/
private String output;
}
枚举参数构建:编写项目所需的枚举类
public enum QuestionSubmitLanguageEnum {
JAVA("Java", "Java"),
CPLUSPLUS("C++", "C++"),
GOLANG("Golang", "Golang");
private final String text;
private final String value;
QuestionSubmitLanguageEnum(String text, String value) {
this.text = text;
this.value = value;
}
/**
* 获取值列表
*
* @return
*/
public static List<String> getValues() {
return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
}
/**
* 根据 value 获取枚举
*
* @param value
* @return
*/
public static QuestionSubmitLanguageEnum getEnumByValue(String value) {
if (ObjectUtils.isEmpty(value)) {
return null;
}
for (QuestionSubmitLanguageEnum anEnum : QuestionSubmitLanguageEnum.values()) {
if (anEnum.value.equals(value)) {
return anEnum;
}
}
return null;
}
public String getValue() {
return value;
}
public String getText() {
return text;
}
}
todo:数据脱敏
根据不同角色权限,设定数据访问限制
需要注意的问题
逻辑删除
此处需要注意,如果说是自定义SQL实现查询逻辑,则相应的逻辑删除概念也要引入(原始实现是MyBatis接管逻辑删除操作),现自定义模板应该显式指定逻辑删除标识