当前位置: 首页 > news >正文

微服务的编程测评系统7-题库接口

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1. 题库管理
    • 1.1 数据库设计
    • 1.2 后端开发01
    • 1.3 后端开发02
    • 1.4 MyBatis 分⻚插件 PageHelper
    • 1.5 查询数据总量
    • 1.6 优化
    • 1.7 前端设计
    • 1.8 快捷键美化样式
    • 1.9 前端开发
    • 1.10 交互逻辑书写
    • 1.11 分页动态展示数据
  • 2. 添加题目
    • 2.1 后端
  • 3. 编辑题目功能
    • 3.1 后端
  • 4. 删除题目
    • 4.1 后端
  • 总结


前言

在这里插入图片描述

1. 题库管理

1.1 数据库设计

create table tb_question(
question_id bigint unsigned not null comment '题目id',
title varchar(50) not null  comment '题目标题',
difficulty tinyint not null comment '题目难度1:简单  2:中等 3:困难',
time_limit int not null comment '时间限制',
space_limit int not null comment '空间限制',
content varchar(1000) not null comment '题目内容',
question_case varchar(1000)  comment '题目用例',
default_code varchar(500) not null comment '默认代码块',
main_fuc varchar(500) not null comment 'main函数',
create_by    bigint unsigned not null  comment '创建人',
create_time  datetime not null comment '创建时间',
update_by    bigint unsigned  comment '更新人',
update_time  datetime comment '更新时间',
primary key(`question_id`)
);

1.2 后端开发01

@TableName("tb_question")
@Getter
@Setter
public class Question extends BaseEntity {@TableId(type = IdType.ASSIGN_ID)private Long questionId;private String title;private Integer difficulty;private Long timeLimit;private Long spaceLimit;private String content;private String questionCase;private String defaultCode;private String mainFuc;
}
@Data
public class QuestionQueryDTO {private String title;private Integer difficulty;private Integer pageNum = 1;private Integer pageSize = 10;
}

对于请求返回的表格数据,我们也对其返回数据像R一样处理,在core中
其中pageNum 和pageSize 是必传的


/*** 表格分页数据对象*/
@Getter
@Setter
public class TableDataInfo {/*** 总记录数*/private long total;/*** 列表数据*/private List<?> rows;/*** 消息状态码*/private int code;/*** 消息内容*/private String msg;/*** 表格数据对象*/public TableDataInfo() {}//未查出任何数据时调用public static TableDataInfo empty() {TableDataInfo rspData = new TableDataInfo();rspData.setCode(ResultCode.SUCCESS.getCode());rspData.setRows(new ArrayList<>());rspData.setMsg(ResultCode.SUCCESS.getMsg());rspData.setTotal(0);return rspData;}//查出数据时调用public static TableDataInfo success(List<?> list,long total) {TableDataInfo rspData = new TableDataInfo();rspData.setCode(ResultCode.SUCCESS.getCode());rspData.setRows(list);rspData.setMsg(ResultCode.SUCCESS.getMsg());rspData.setTotal(total);return rspData;}
}
    @GetMapping("/list")public TableDataInfo list(QuestionQueryDTO questionQueryDTO) {log.info("查询题库列表,questionQueryDTO:{}", questionQueryDTO);return null;}

这样就很好了

1.3 后端开发02

这个查询操作比较复杂,我们用xml来写sql语句

@Data
public class QuestionVO {private Long questionId;private String title;private Integer difficulty;private String createName;private LocalDateTime createTime;
}

然后再resource下创建QuestionMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ck.system.mapper.question.QuestionMapper"><select id="selectQuestionList" resultType="com.ck.system.domain.question.vo.QuestionVO">SELECTtq.question_id,tq.title,tq.difficulty,ts.nick_name as create_name,tq.create_timeFROMtb_question tqleft jointb_sys_user tsontq.create_by = ts.user_id<where><if test="difficulty !=null ">AND difficulty = #{difficulty}</if><if test="title !=null and title !='' ">AND title LIKE CONCAT('%',#{title},'%')</if></where>ORDER BYcreate_time DESC</select></mapper>

这个没有增加分页查询的

@Mapper
public interface QuestionMapper extends BaseMapper<Question> {List<QuestionVO> selectQuestionList(QuestionQueryDTO questionQueryDTO);}

这样就OK了

    @Overridepublic TableDataInfo list(QuestionQueryDTO questionQueryDTO) {List<QuestionVO> questionVOS = questionMapper.selectQuestionList(questionQueryDTO);if(CollectionUtil.isEmpty(questionVOS)){return TableDataInfo.empty();}return TableDataInfo.success(questionVOS, questionVOS.size());}

注意这里success返回的第二个参数应该是全部的数据量,是符合查询条件的size,但是不是满足了分页后的数据量,因为后面还要添加分页的sql语句
这样查询出来就是那一页的数据总量
但是我们要符合条件的所有的数据总量,所有页的数据总量

1.4 MyBatis 分⻚插件 PageHelper

官网

优势:

⾃动分⻚:PageHelper 会⾃动解析你执⾏的 SQL 语句,并根据你提供的⻚码和每⻚显⽰的记录数,⾃动添加分⻚相关的 SQL ⽚段,从⽽返回正确的分⻚结果。⽆需在 SQL语句中⼿动编写复杂的分⻚逻辑。

配置简单:在 Spring Boot 项⽬中通过添加依赖和简单配置就可以启⽤它。

易于集成:PageHelper 可以与 MyBatis 很好地集成,⽆论你是使⽤ XML 映射⽂件还是注解⽅式,都可以⽅便地使⽤ PageHelper。

性能优化:PageHelper 采⽤了物理分⻚的⽅式,相⽐于内存分⻚,它可以减少内存消耗并提⾼查询性能。

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>${pagehelper.boot.version}</version>
</dependency><pagehelper.boot.version>2.0.0</pagehelper.boot.version>

直接加载system下使用

    @Overridepublic TableDataInfo list(QuestionQueryDTO questionQueryDTO) {PageHelper.startPage(questionQueryDTO.getPageNum(), questionQueryDTO.getPageSize());List<QuestionVO> questionVOS = questionMapper.selectQuestionList(questionQueryDTO);if(CollectionUtil.isEmpty(questionVOS)){return TableDataInfo.empty();}return TableDataInfo.success(questionVOS, questionVOS.size());}

直接这样写就可以了,就可以自动添加分页的sql语句

1.5 查询数据总量

在这里插入图片描述
在PageHelper.startPage中,这个方法其实已经把总量给查出来了
DEFAULT_COUNT传的值是true
就是PageHelper在执行分页之前,会先执行条件的sql语句,并把数量给查出来,然后再加上分页的sql语句,查出对应的数据
现在的问题就是怎么获取它查出来的数量呢

long total = new PageInfo<>(questionVOList).getTotal();

这个就是获取满足条件的数据总量,而不是那一页的数据总量

1.6 优化

在core里面增加一个类,PageQueryDTO

@Data
public class PageQueryDTO {private Integer pageNum = 1;private Integer pageSize = 10;
}
@Data
public class QuestionQueryDTO extends PageQueryDTO {private String title;private Integer difficulty;
}

因为每个分页的请求参数都会具有这两个参数

然后第二个优化就是service返回list,controller用baseController来返回

baseController中增加方法
然后把PageHelper的依赖增加到core中

在baseController中

    public TableDataInfo getTableDataInfo(List<?> list){if(CollectionUtil.isEmpty(list)){return TableDataInfo.empty();}long total = new PageInfo<>(list).getTotal();return TableDataInfo.success(list, total);}

service

    @Overridepublic List<QuestionVO> list(QuestionQueryDTO questionQueryDTO) {PageHelper.startPage(questionQueryDTO.getPageNum(), questionQueryDTO.getPageSize());return questionMapper.selectQuestionList(questionQueryDTO);}

controller

    @GetMapping("/list")public TableDataInfo list(QuestionQueryDTO questionQueryDTO) {log.info("查询题库列表,questionQueryDTO:{}", questionQueryDTO);return getTableDataInfo(questionService.list(questionQueryDTO));}

开始测试
数据库中随便插入一些数据测试

用ai生成数据
在这里插入图片描述
注意这里没有勾选pageNum的话,才会触发默认值1,如果勾选的话,但是没有输入值,就表示传的值是null,而不会触发默认值

1.7 前端设计

question.vue

<template><el-form inline="true"><el-form-item><selector placeholder="请选择题⽬难度" style="width: 200px;"></selector></el-form-item><el-form-item><el-input placeholder="请您输⼊要搜索的题⽬标题" /></el-form-item><el-form-item><el-button plain>搜索</el-button><el-button plain type="info">重置</el-button><el-button plain type="primary" :icon="Plus">添加题⽬</el-button></el-form-item></el-form><el-table height="526px" :data="questionList"><el-table-column prop="questionId" width="180px" label="题⽬id" /><el-table-column prop="title" label="题⽬标题" /><el-table-column prop="difficulty" label="题⽬难度" width="90px"><template #default="{ row }"><div v-if="row.difficulty === 1" style="color:#3EC8FF;">简单</div><div v-if="row.difficulty === 2" style="color:#FE7909;">中等</div><div v-if="row.difficulty === 3" style="color:#FD4C40;">困难</div></template></el-table-column><el-table-column prop="createName" label="创建⼈" width="140px" /><el-table-column prop="createTime" label="创建时间" width="180px" /><el-table-column label="操作" width="100px" fixed="right"><template #default="{ row }"><el-button type="text">编辑</el-button><el-button type="text" class="red">删除</el-button></template></el-table-column></el-table>
</template><script setup>
import { Plus } from "@element-plus/icons-vue";
import Selector from "@/components/QuestionSelector.vue";
import { reactive } from "vue";
const questionList = reactive([{questionId: "1",title: "题⽬1",difficulty: 1,createName: "超级管理员",createTime: "2024-05-30 17:00:00",},{questionId: "2",title: "题⽬2",difficulty: 2,createName: "超级管理员",createTime: "2024-05-30 17:00:00",},{questionId: "3",title: "题⽬1",difficulty: 3,createName: "超级管理员",createTime: "2024-05-30 17:00:00",},
]);
</script>

QuestionSeletor.vue

<template><el-select><el-option v-for="item in difficultyList" :label="item.difficultyName"/></el-select>
</template>
<script setup>
import { reactive } from 'vue'
const difficultyList = reactive([{"difficulty": 1,"difficultyName": "简单",},{"difficulty": 2,"difficultyName": "中等",},{"difficulty": 3,"difficultyName": "困难",}
])
</script>

这个QuestionSeletor.vue其实就是那个题目难度选择框,因为可能会在其他地方也用到,所以我们提取出来
页面级的组件就用在views下,小组件就放在components下

还有css代码没有拷贝过来
我们把css样式也给提取出来了
为什么提取出来呢
因为题目管理,用户管理的表格的样式都是一样的

我们把公共的样式放在assets目录下,创建一个文件main.scss

.el-table {th {background-color: #32C5FF !important;color: #ffff;}
}.el-button--primary {color: #32c5ff !important;
}.el-button--text {color: #32c5ff !important;&.red span{color: #FD4C40;}
}.el-pagination {margin-top: 20px;justify-content: flex-end;.el-pager li.is-active {background-color: #32c5ff !important;}
}

在这里插入图片描述

1.8 快捷键美化样式

代码格式化:Shift+Alt + F

1.9 前端开发

Plus是图标添加
Selector是自定义的组件

但是我们还没有加载

import "@/assets/main.scss"

我们在main.js里面输入这个代码就可以了

然后开始分析代码
el-from是表单组件
el-table是表格组件
在这里插入图片描述
上面是表单
下面是表格

    <el-form-item><selector placeholder="请选择题⽬难度" style="width: 200px;"></selector></el-form-item>

这个就是我们自定义的组件QuestionSelector.vue
el-select是选择器

  <el-select><el-option v-for="item in difficultyList" :label="item.difficultyName" /></el-select>

v-for是遍历数组的指令
:label就是选择器显示的内容

      <el-button plain>搜索</el-button>

plain属性的作用就是光标移到那里显示更明显好看
plain 属性的主要目的是在需要弱化按钮视觉强度的场景下使用,比如在表单中作为辅助操作按钮,或者在已经有主要操作按钮的情况下,突出主次关系

  <el-form inline="true">

inline="true"表示为行内表单模式,如果没有这个,表单就是竖着展示的
我们去掉某个属性就知道这个属性的作用了

    <el-table-column prop="difficulty" label="题⽬难度" width="90px"><template #default="{ row }"><div v-if="row.difficulty === 1" style="color:#3EC8FF;">简单</div><div v-if="row.difficulty === 2" style="color:#FE7909;">中等</div><div v-if="row.difficulty === 3" style="color:#FD4C40;">困难</div></template></el-table-column>

#default="{ row }"中的row表示这一行的数据

1.10 交互逻辑书写

在apis里面创建question.js

import service from "@/utils/request";export function getQuestionListService(params) {return service({url: "/question/list",method: "get",params,});
}
const questionList = ref([]);const paranms = reactive({pageNum: 1,pageSize: 10,title: '',difficulty : null
});async function getQuestionList() {const res = await getQuestionListService(paranms);console.log(res);questionList.value = res.rows;
}getQuestionList();

这样就成功了
在这里插入图片描述

  <el-table height="526px" :data="questionList">

前端这里是会自动调用data属性的

1.11 分页动态展示数据

先高搞出一个分页器

  <el-pagination background layout="prev, pager, next" :total="1000" />

在这里插入图片描述
因为多个管理都有分页器,所以把分页器的样式加在公共的main.scss中

.el-pagination {margin-top: 20px;justify-content: flex-end;.el-pager li.is-active {background-color: #32c5ff !important;}
}

查看官网,加上size属性就可以把分页器变小了

  <el-pagination background  size="small" layout="prev, pager, next"   :total="1000" />

注意使用这个size的前提是elementplus的版本要大于2.7.6

在这里插入图片描述

npm uninstall element-plus
npm install element-plus

删除在重新安装一下就可以更新版本了

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
看这个示例

  <el-pagination background  size="small"      layout="total, sizes, prev, pager, next, jumper"   :total="1000" />

在这里插入图片描述
怎么中文变英文呢

在这里插入图片描述
这里就可以改变了

import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'app.use(ElementPlus, {locale: zhCn,
})

粘贴到main.js里面就可以了

在这里插入图片描述
这样就可以变中文了
总共条数怎么显示呢
就是配置total属性

const total = ref(0);async function getQuestionList() {const res = await getQuestionListService(paranms);console.log("获取题目管理表单数据:",res);questionList.value = res.rows;total.value = res.total;
}
  <el-pagination background  size="small"      layout="total, sizes, prev, pager, next, jumper"   :total="total" />

在这里插入图片描述
这样就可以了
然后是分页选项,我们要5,10,15,20,30
在这里插入图片描述

  <el-pagination background  size="small"   :page-sizes="[5, 10, 15, 20 , 30]"   layout="total, sizes, prev, pager, next, jumper"   :total="total" />

在这里插入图片描述
但是我们切换pageNUm和pageSize就没有反应

页数会根据总数和每页数而自动变化,没有问题,问题就是不切换
调整之后就要向后端发送数据
就是@click,监听点击事件
在这里插入图片描述
用这两个就可以了

  <el-pagination background  size="small"   :page-sizes="[5, 10, 15, 20 , 30]"  layout="total, sizes, prev, pager, next, jumper"  :total="total"@size-change="handleSizeChange"@current-change="handleCurrentChange"/>

触发这两个函数的时候,会自动传一个参数,就是修改后的页数或者每页数

function handleSizeChange(newSize) {paranms.pageSize = newSize;getQuestionList();
}
function handleCurrentChange(newPage) {paranms.pageNum = newPage;getQuestionList();
}

在这里插入图片描述
现在还有一个问题就是切换pageSize的时候,如果上一次是第二页,那么切换之后还是第二页开始看的
切换pageSize之后我们应该默认从第一页看,开始看
让pageNum变为1就可以了
然后请求就可以了,对应也要切换到第一页显示
用v-model来绑定就可以了
在这里插入图片描述
然后拷贝示例里面的代码就可以了

  <el-pagination background  size="small"   :page-sizes="[5, 10, 15, 20 , 30]"  layout="total, sizes, prev, pager, next, jumper"  :total="total"@size-change="handleSizeChange"@current-change="handleCurrentChange"v-model:current-page="paranms.pageNum"/>
function handleSizeChange(newSize) {paranms.pageSize = newSize;paranms.pageNum = 1;getQuestionList();
}

这样就行了

所以

function handleCurrentChange(newPage) {// paranms.pageNum = newPage;getQuestionList();
}

这里就可以省略了,因为是双向绑定的,可以随着变化的

所以都可以双向绑定了pageSize

  <el-pagination background  size="small"   :page-sizes="[5, 10, 15, 20 , 30]"  layout="total, sizes, prev, pager, next, jumper"  :total="total"@size-change="handleSizeChange"@current-change="handleCurrentChange"v-model:current-page="paranms.pageNum"v-model:page-size="paranms.pageSize"/>
function handleSizeChange(newSize) {// paranms.pageSize = newSize;paranms.pageNum = 1;getQuestionList();
}
function handleCurrentChange(newPage) {// paranms.pageNum = newPage;getQuestionList();
}

这样就OK了

    <el-form-item><selector placeholder="请选择题⽬难度" v-model="paranms.difficulty" style="width: 200px;"></selector></el-form-item><el-form-item><el-input placeholder="请您输⼊要搜索的题⽬标题" v-model="paranms.title"/></el-form-item><el-form-item><el-button @click="onSearch"  plain>搜索</el-button><el-button @click="onReset" plain type="info">重置</el-button><el-button plain type="primary" :icon="Plus">添加题⽬</el-button></el-form-item>
  <el-select><el-option v-for="item in difficultyList" :label="item.difficultyName" :value="item.difficulty" /></el-select>

label是选择显示的内容,value则是每个选择的值

然后是上面的两个按钮

function onSearch(){paranms.pageNum = 1;getQuestionList();
}function onReset(){paranms.pageNum = 1;paranms.title = '';paranms.pageSize = 10;paranms.difficulty = '';getQuestionList();
}

这样就可以了

2. 添加题目

在这里插入图片描述

2.1 后端

@Data
public class QuestionAddDTO {private String title;private Integer difficulty;private Long timeLimit;private Long spaceLimit;private String content;private String questionCase;private String defaultCode;private String mainFuc;
}
    @PostMapping("/add")public R<Void> add(@RequestBody  QuestionAddDTO questionAddDTO) {log.info("添加题目,questionAddDTO:{}", questionAddDTO);return toR(questionService.add(questionAddDTO));}
    @Overridepublic int add(QuestionAddDTO questionAddDTO) {List<Question> questions = questionMapper.selectList(new LambdaQueryWrapper<Question>().eq(Question::getTitle, questionAddDTO.getTitle()));if(CollectionUtil.isNotEmpty(questions)){throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);}Question question = new Question();BeanUtil.copyProperties(questionAddDTO, question);return questionMapper.insert(question);}

其中 BeanUtil.copyProperties(questionAddDTO, question);是hutool的工具类,就是把questionAddDTO里面的属性赋值给question

在这里插入图片描述
在这里插入图片描述

3. 编辑题目功能

在这里插入图片描述

3.1 后端

这个需要先获取题目详细信息,然后是编辑

@Data
public class QuestionDetailVO {private Long questionId;private String title;private Integer difficulty;private Long timeLimit;private Long spaceLimit;private String content;private String questionCase;private String defaultCode;private String mainFuc;
}

因为编辑的时候要传入id,所以获取详细信息的时候就返回id

    @GetMapping("/detail")public R<QuestionDetailVO> detail(Long questionId) {log.info("查询题目详情,questionId:{}", questionId);return questionService.detail(questionId);}
    @Overridepublic R<QuestionDetailVO> detail(Long questionId) {Question question = questionMapper.selectById(questionId);if(question == null){throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);}QuestionDetailVO questionDetailVO = new QuestionDetailVO();BeanUtil.copyProperties(question, questionDetailVO);return R.ok(questionDetailVO);}

在这里插入图片描述

这样就成功了

然后是编辑的接口

@Data
public class QuestionEditDTO extends QuestionAddDTO{private Long questionId;
}
    @Overridepublic int edit(QuestionEditDTO questionEditDTO) {Question question = questionMapper.selectById(questionEditDTO.getQuestionId());if(question == null){throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);}question.setTitle(questionEditDTO.getTitle());question.setDifficulty(questionEditDTO.getDifficulty());question.setTimeLimit(questionEditDTO.getTimeLimit());question.setSpaceLimit(questionEditDTO.getSpaceLimit());question.setContent(questionEditDTO.getContent());question.setQuestionCase(questionEditDTO.getQuestionCase());question.setDefaultCode(questionEditDTO.getDefaultCode());question.setMainFuc(questionEditDTO.getMainFuc());return questionMapper.updateById(question);}

edit 方法:直接使用 copyProperties 可能会导致以下问题:
参数注入风险:攻击者可能会通过 DTO 传递一些不允许修改的字段,比如 createTime、status 等。
空值覆盖问题:DTO 中没有设置的字段可能会被置为 null,而这并非是我们期望的结果。

一般来说,使用BeanUtil.copyProperties,都是大的转小的

在 “小对象转大对象” 的场景中,若目标对象原本已有属性值,而源对象中没有对应的属性,那么这些属性值会被重置为默认值。

User user = new User();
user.setId(1L);
user.setName("Alice");
user.setAge(20);UserForm form = new UserForm();
form.setName("Bob");BeanUtil.copyProperties(form, user); // user.age 变为 null!

所以一般是大的转小的,不要小的转大的,不然好多字段会变为null

    @Overridepublic void updateFill(MetaObject metaObject) {log.info("开始更新填充...");this.strictUpdateFill(metaObject, "updateBy", Long.class, 1L);this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());}

然后再mybatis中增加这个,也是自动填充字段

这样就成功了

4. 删除题目

在这里插入图片描述

4.1 后端

    @DeleteMapping("/delete")public R<Void> delete(Long questionId) {log.info("删除题目,questionId:{}", questionId);return toR(questionService.delete(questionId));}
    @Overridepublic int delete(Long questionId) {Question question = questionMapper.selectById(questionId);if(question == null){throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);}return questionMapper.deleteById(question);}

总结

http://www.dtcms.com/a/303208.html

相关文章:

  • windows平台计划任务批处理实现定时任务
  • 循环神经网络 中文情感分析案例
  • WAIC首日 | RWKV-7s 新型高效大模型架构正式亮相
  • django 按照外键排序
  • uvm_do sequence marcos
  • SQL之防止误删数据
  • Spring核心机制:深入理解控制反转(IoC)
  • Kotlin的datetime库
  • 荣耀应用市场《2025上半年应用合规治理报告》丨开发加油站
  • C# 静态类_静态方法_静态字段(static 声明静态的关键字 )
  • R语言与作物模型(DSSAT模型)技术应用
  • 未授权访问漏洞 总结
  • mysql 关于树形数据结构的一些操作
  • STM32中集成USB驱动
  • SQL 查询语法笔记
  • C语言笔记03 :关于指针的补充
  • 力扣面试150题--颠倒二进制位
  • 第18章 泛型 笔记
  • 第一第二章笔记整理
  • AutoGen - model_clients和model_context使用示例
  • Docker学习相关视频笔记(一)
  • 机器学习sklearn:决策树的参数、属性、接口
  • redis getshell得方式
  • Redis 部署模式详解
  • stm32开发 -- TFTLCD相关
  • Zabbix 6.0 监控AWS全栈实战|EC2至Lambda的无缝监控
  • 配置 MCP 让 cursor 结合 Figma 自动生成设计稿组件
  • Python defaultdict 的强大之处:告别繁琐的字典键检查: Effective Python 第17条
  • Python动态规划:从基础到高阶优化的全面指南
  • 网络与信息安全有哪些岗位:(3)安全运维工程师