typora-copy-images-to: img

面面项目第三天

今日目标

  • 能够完成基础题库列表展示
  • 能够完成学科的初始化
  • 能够完成行业方向的初始化
  • 能够完成数据字典(城市)的初始化
  • 能够完成企业的初始化
  • 能够完成题目新增

第一章-题库管理分析

知识点-题库管理模块分析

1.目标

  • 知道题库管理的需求

2.路径

  1. 需求分析
  2. 涉及到的表

3.讲解

3.1需求分析

  1. 基础题库管理是普通录入人员操作的功能,新的题目先经过基础题库录入,加入精选后方可进入精选题库列表,此时题目的状态默认是待审核状态,有操作精选题库权限的用户,可以审核题目。
  2. 基础题库与精选题库,从数据库上都来源于同一张主表t_question,只是题目的类型有所不同。区分两种题库主要是通过is_classic字段来区分,is_classic0是基础题目,为1是精选题目。题目状态是通过status区分,审核状态靠review_status来区分。具体题目的类型、状态及审核状态,总结如下:
  3. 题目类型(is_classic):0 基础题目、1精选题目
  4. 题目状态(status): 0 待发布(待审核、已拒绝)、1 已发布(已审核)、 2 已下架(已审核)
  5. 审核状态(review_status): 0 待审核、1 审核通过、2 审核拒绝

面面项目第三天 - 图1

3.2涉及到的表

  1. 题目业务涉及的表有t_usert_questiont_question_itemt_companyt_courset_catalogt_tag七张表。

4.小结

第二章-基础题库

案例-基础题库列表展示

1.需求

面面项目第三天 - 图2

2.分析

2.1业务分析

  1. 基础题目列表中,需要展示创建者、使用次数及学科名称,故需要三表连接,列表中还需要显示使用次数,需要使用嵌套子查询统计题目被用户使用题的次数。基础题目列表需要分页,所有需要使用相同的条件查询总记录数和数据集。根据页面分析,查询需要组合的条件很多,可以使用QueryPageBeanqueryParams来动态组合查询。试题编号规则采用从1000+主键ID.

面面项目第三天 - 图3

2.2数据模型

2.2.1请求参数格式
  • 前端页面
  1. {
  2. "currentPage": 1,
  3. "pageSize": 10,
  4. "queryParams": {
  5. "courseId": 24,
  6. "difficulty": 2,
  7. "type": 5,
  8. "keyWord": "Java"
  9. }
  10. }
  • 后台使用QueryPageBean接收
  1. public class QueryPageBean implements Serializable{
  2. private Integer currentPage; // 页码
  3. private Integer pageSize; //每页记录数
  4. private Map queryParams; //查询条件
  5. }

2.2.2响应数据格式
  • 前端页面
  1. {
  2. "flag": true,
  3. "message": "获取题目列表成功",
  4. "result": {
  5. "rows": [{
  6. "courseName": "java",
  7. "createDate": "2019-08-08 00:00:00.0",
  8. "creator": "admin",
  9. "difficulty": 4,
  10. "id": 11,
  11. "number": "",
  12. "status": 0,
  13. "subject": "<p>反爬虫措施?</p>\r\n",
  14. "type": 3,
  15. "usedQty": "0"
  16. },
  17. {
  18. "courseName": "java",
  19. "createDate": "2019-08-08 00:00:00.0",
  20. "creator": "admin",
  21. "difficulty": 5,
  22. "id": 12,
  23. "number": "",
  24. "status": 0,
  25. "subject": "<p>加入Redis里面有1亿个key,其中10w个key是以某个固定的一直的前缀开头的,如何将他们全部找出来。</p>\r\n",
  26. "type": 3,
  27. "usedQty": "0"
  28. }
  29. ],
  30. "total": 20
  31. }
  32. }
  • 后台使用Result
  1. public class Result implements java.io.Serializable {
  2. private boolean flag;//执行结果,true为执行成功 false为执行失败
  3. private String message;//返回结果信息
  4. private Object result;//返回数据
  5. }
  • Result里面的Object result有两个属性(total,rows), 所以我们创建PageResult对象 给Object result赋值
  1. public class PageResult implements Serializable{
  2. private Long total;//总记录数
  3. private List rows;//当前页结果
  4. }

2.3思路分析

2.3.1加载所有学科思路

面面项目第三天 - 图4

  1. 在questionBasicList.html的initCourses()里面, 发送Ajax请求CourseController
  2. 在CourseController里面创建findAll()方法
  1. //1.调用业务 获得学科的列表 List<Course> list
  2. //2.封装, 响应
  1. 在CourseService里面创建findAll
  1. public List<Course> findAll(){
  2. //调用Dao
  3. }
  1. 在CourseDao查询所有的学科

2.3.2基础题库列表思路
  1. 在questionBasicList.html的getList()里面, 发送Ajax请求QuestionController ,携带请求参数
  1. {
  2. "currentPage": 1,
  3. "pageSize": 10,
  4. "queryParams": {
  5. "courseId": 1,
  6. "difficulty": 1,
  7. "keyWord": "String",
  8. "type": 1
  9. }
  10. }
  1. 在QuestionController 里面创建findListByPage()方法
  1. //1.获得请求参数 封装成QueryPageBean
  2. //2.调用业务 获得分页的数据 PageResult
  3. //3.把PageResult封装成Result 响应
  1. 在QuestionService创建
  1. public PageResult findListByPage(QueryPageBean queryPageBean){
  2. //调用dao,封装PageResult
  3. }
  1. 在QuestionDao
    查询题目的总数量(带条件)
    查询题目一页展示的List

3.实现

3.1加载所有学科实现

3.1.1前端
  • questionBasicList.html
  1. //获取所有学科信息,初始化学科下拉列表
  2. initCourses() {
  3. let t = this;
  4. // 必传参数
  5. let params = {
  6. status: 0
  7. };
  8. //1. 发送异步请求,获取所有学科列表
  9. axios.get("/course/findAll.do?status="+status).then(response=>{
  10. if (response.data.flag) {
  11. //获取学科列表成功
  12. this.$message({
  13. type:"success",
  14. message:response.data.message,
  15. showClose:true
  16. })
  17. // 将响应数据设置给数据模型
  18. this.courses = response.data.result
  19. }else {
  20. //获取学科列表失败
  21. this.$message({
  22. type:"error",
  23. message:response.data.message,
  24. showClose:true
  25. })
  26. }
  27. })
  28. }

3.1.2后台
  • CourseController
  1. @RequestMapping("/course/findAll")
  2. public void findAll(HttpServletRequest request, HttpServletResponse response) throws IOException {
  3. try {
  4. //1. 获取请求参数status
  5. String status = request.getParameter("status");
  6. //调用业务层的方法,查询所有学科信息
  7. List<Course> courseList = courseService.findAll(status);
  8. //封装响应结果
  9. JsonUtils.printResult(response,new Result(true,"获取所有学科成功",courseList));
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. JsonUtils.printResult(response,new Result(false,"获取所有学科失败"));
  13. }
  14. }
  • CourseService
  1. public List<Course> findAll(String status) throws Exception {
  2. SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
  3. CourseDao courseDao = sqlSession.getMapper(CourseDao.class);
  4. List<Course> courseList = courseDao.findAll(status);
  5. SqlSessionFactoryUtils.commitAndClose(sqlSession);
  6. return courseList;
  7. }
  • CourseDao
  1. List<Course> findAll();
  • CourseDao.xml
  1. <!--进行一对多的分步查询映射-->
  2. <resultMap id="courseCatalogMap" type="Course">
  3. <id property="id" column="id"></id>
  4. <!--一对多使用collection标签-->
  5. <!--查询二级目录列表-->
  6. <collection property="catalogList" ofType="Catalog" column="id"
  7. select="com.itheima.mm.dao.CatalogDao.findCatalogListByCourseId"></collection>
  8. </resultMap>
  9. <select id="findAll" resultMap="courseCatalogMap" parameterType="string">
  10. select id,name from t_course
  11. <where>
  12. <if test="status != null and status != '' and status != 'null'">
  13. isShow=#{status}
  14. </if>
  15. </where>
  16. </select>
  • CatalogDao
  1. public interface CatalogDao {
  2. List<Catalog> findCatalogListByCourseId(int courseId);
  3. }
  • CatalogDao.xml
  1. <select id="findCatalogListByCourseId" resultType="Catalog" parameterType="int">
  2. select * from t_catalog where courseId=#{courseId}
  3. </select>

3.2基础题库列表实现

3.2.1前端
  • questionBasicList.html
  1. //查询试题分页列表
  2. getList() {
  3. let t = this;
  4. // 必传参数
  5. let params = {
  6. currentPage: t.pagination.pageNum,//当前页数
  7. pageSize: t.pagination.pageSize//每页条数
  8. };
  9. // 选传参数
  10. // 搜索条件
  11. let queryParams = {};
  12. let courseId = t.requestParameters.courseId;
  13. //如果选择了学科,就将学科id放到搜索条件中
  14. if (courseId !== '') {
  15. queryParams.courseId = courseId;
  16. }
  17. let difficulty = t.requestParameters.difficulty;
  18. //如果选择了难度,就将难度放到搜索条件中
  19. if (difficulty !== '') {
  20. queryParams.difficulty = difficulty;
  21. }
  22. let type = t.requestParameters.type;
  23. //如果选择了提醒,就将提醒放到搜索条件中
  24. if (type !== '') {
  25. queryParams.type = type;
  26. }
  27. let keyWord = t.requestParameters.keyWord;
  28. //如果输入了搜索关键字,就将关键字放到搜索条件中
  29. if (keyWord !== '') {
  30. queryParams.keyWord = keyWord;
  31. }
  32. //如果搜索条件中有内容,那么就将搜索条件添加到请求参数中
  33. if (Object.keys(queryParams).length) {
  34. params.queryParams = queryParams;
  35. }
  36. console.log("基础题库列表请求参数:");
  37. console.log(params);
  38. //发送异步请求
  39. axios.post("/question/findBasicByPage.do",params).then(response=>{
  40. if (response.data.flag) {
  41. //获取基础题库列表成功
  42. //赋值操作
  43. this.pagination.total = response.data.result.total
  44. this.items = response.data.result.rows
  45. }else {
  46. this.$message({
  47. type:"error",
  48. message:response.data.message,
  49. showClose:true
  50. })
  51. }
  52. })
  53. }

3.2.2后台
  • QuestionController
  1. @RequestMapping("/question/findBasicByPage")
  2. public void findBasicByPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
  3. try {
  4. //1. 获取请求参数,封装到QueryPageBean对象中
  5. QueryPageBean queryPageBean = JsonUtils.parseJSON2Object(request, QueryPageBean.class);
  6. //2. 调用业务层的方法,进行分页查询
  7. PageResult pageResult = questionService.findBasicByPage(queryPageBean);
  8. JsonUtils.printResult(response,new Result(true,"获取基础题库成功",pageResult));
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. JsonUtils.printResult(response,new Result(false,"获取基础题库失败"));
  12. }
  13. }
  • QuestionService
  1. public PageResult findBasicByPage(QueryPageBean queryPageBean) throws Exception {
  2. SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
  3. QuestionDao questionDao = sqlSession.getMapper(QuestionDao.class);
  4. //获取总条数
  5. Long total = questionDao.findTotalBasicCount(queryPageBean);
  6. //获取当前页数据集合
  7. List<Question> basicList = questionDao.findBasicPageList(queryPageBean);
  8. SqlSessionFactoryUtils.commitAndClose(sqlSession);
  9. return new PageResult(total,basicList);
  10. }
  • QuestionDao
  1. Long findTotalBasicCount(QueryPageBean queryPageBean);
  2. List<Question> findBasicPageList(QueryPageBean queryPageBean);
  • 映射配置文件
  1. <sql id="select_where">
  2. <if test="queryParams != null">
  3. <if test="queryParams.courseId != null">
  4. and courseId=#{queryParams.courseId}
  5. </if>
  6. <if test="queryParams.difficulty != null">
  7. and difficulty=#{queryParams.difficulty}
  8. </if>
  9. <if test="queryParams.keyWord != null">
  10. and subject like concat("%",#{queryParams.keyWord},"%")
  11. </if>
  12. <if test="queryParams.type != null">
  13. and type=#{queryParams.type}
  14. </if>
  15. </if>
  16. </sql>
  17. <select id="findTotalBasicCount" resultType="Long" parameterType="QueryPageBean">
  18. select count(*) from t_question
  19. where isClassic=0
  20. <include refid="select_where"></include>
  21. </select>
  22. <select id="findBasicPageList" resultType="Question" parameterType="QueryPageBean">
  23. select
  24. 10000+id number,type,subject,createDate,difficulty,
  25. (select name from t_course where id=q.courseId) courseName,
  26. (select username from t_user where id=q.userId) creator,
  27. (select count(*) from tr_member_question where questionId=q.id) usedQty
  28. from t_question q
  29. where isClassic=0
  30. <include refid="select_where"></include>
  31. limit #{offset},#{pageSize}
  32. </select>

4.小结

4.1数据模型

4.1.1请求参数格式
  • 前端页面
  1. {
  2. "currentPage": 1,
  3. "pageSize": 10,
  4. "queryParams": {
  5. "courseId": 24,
  6. "difficulty": 2,
  7. "type": 5,
  8. "keyWord": "Java"
  9. }
  10. }
  • 后台使用QueryPageBean接收
  1. public class QueryPageBean implements Serializable{
  2. private Integer currentPage; // 页码
  3. private Integer pageSize; //每页记录数
  4. private Map queryParams; //查询条件
  5. }

4.1.2响应数据格式
  • 前端页面
  1. {
  2. "flag": true,
  3. "message": "获取题目列表成功",
  4. "result": {
  5. "rows": [{
  6. "courseName": "java",
  7. "createDate": "2019-08-08 00:00:00.0",
  8. "creator": "admin",
  9. "difficulty": 4,
  10. "id": 11,
  11. "number": "",
  12. "status": 0,
  13. "subject": "<p>反爬虫措施?</p>\r\n",
  14. "type": 3,
  15. "usedQty": "0"
  16. },
  17. {
  18. "courseName": "java",
  19. "createDate": "2019-08-08 00:00:00.0",
  20. "creator": "admin",
  21. "difficulty": 5,
  22. "id": 12,
  23. "number": "",
  24. "status": 0,
  25. "subject": "<p>加入Redis里面有1亿个key,其中10w个key是以某个固定的一直的前缀开头的,如何将他们全部找出来。</p>\r\n",
  26. "type": 3,
  27. "usedQty": "0"
  28. }
  29. ],
  30. "total": 20
  31. }
  32. }
  • 后台使用Result
  1. public class Result implements java.io.Serializable {
  2. private boolean flag;//执行结果,true为执行成功 false为执行失败
  3. private String message;//返回结果信息
  4. private Object result;//返回数据
  5. }
  • Result里面的Object result有两个属性(total,rows), 所以我们创建PageResult对象 给Object result赋值
  1. public class PageResult implements Serializable{
  2. private Long total;//总记录数
  3. private List rows;//当前页结果
  4. }

4.2思路

4.2.1加载所有的学科

查询所有的学科

4.2.2基础题目列表
  1. 前端传给后台 请求参数===> QueryPageBean
  2. 后台响应 new Result(true, “成功”,pageResult)

案例-基础题库新增

1.需求

面面项目第三天 - 图5

面面项目第三天 - 图6

  1. 新增题目页面需要先初始化页面的数据(学科、学科目录及标签、公司、省市列表、行业方向),然后再提交数据进行保存,保存数据需要涉及大约九张表的更新操作。题目选项在单选与复选时可以上传图片,图片需要考虑上传但未保存数据库服务器垃圾图片的处理问题。

2.分析

2.1初始化页面数据分析

2.1.1初始化学科
  1. 页面选择学科时,学科目录及学科标签列表也随之发生改变。所以需要在初始化学科数据时,与之对应的学科目录及标签数据同时获取。 涉及到的表 t_course, t_catalog, t_tag

面面项目第三天 - 图7

  1. 在questionEditor.html的initCourses(), 发送ajax请求CourseController
  2. 在CourseController创建findListAll()方法
  1. //1.调用业务 获得List<Course> list
  2. //2.封装Result 响应
  1. 在CourseService里面创建findListAll()方法, 调用Dao
  2. 在CourseDao, 使用ResultMap封装 使用collocation标签

注意: 根据学科,查询出学科对应的目录数据,以及学科对应的标签数据

2.2数据保存分析

  1. 页面初始化已完成,用户根据页面进行数据输入,输入元素包含单选、复选、简单,其中单选、复选选项可以上传图片,如果不上传图片,页面中输入元素使用的是富文本编辑器输入文本。所谓富文本编辑器是指可以编写带有复杂样式的文本,数据保存到数据库中内容是带有html标签样式的。
  2. 选择学科及学科目录,选择不同的学科,与之对应的标签列表也会不同;
  3. 选择公司后,与之对应的省市、行业方向是自动选择,同时可以再次编辑后与题目一起提交;
  4. 可以为当前题目设置多个标签,标签列表是根据当前选择的学科变化而变化的。
  5. 根据以上分析,保存题目数据的同时,需要更新题目数据、题目选项数据、标签数据、公司数据,故实现本小结功能,我们先把流程业务走通,把数据从前端页面传递控制器,从控制器传递到ServiceService先保证传递到后端的数据是正确的,然后再分步完成数据的保存。
  • 提交的参数
  1. {
  2. "id": 0,
  3. "is_classic": null,
  4. "courseId": 1,
  5. "catalogId": 1,
  6. "companyId": 1,
  7. "cityIds": [2, 10],
  8. "industryIds": [1, 2, 18, 26, 27],
  9. "type": 2,
  10. "difficulty": 3,
  11. "subject": "<p>测试题目</p>",
  12. "questionItemList": [{
  13. "id": 0,
  14. "content": "a",
  15. "isRight": true,
  16. "imgUrl": ""
  17. }, {
  18. "id": 0,
  19. "content": "b",
  20. "isRight": false,
  21. "imgUrl": ""
  22. }, {
  23. "id": 0,
  24. "content": "c",
  25. "isRight": false,
  26. "imgUrl": ""
  27. }, {
  28. "id": 0,
  29. "content": "d",
  30. "isRight": false,
  31. "imgUrl": ""
  32. }, {
  33. "id": 0,
  34. "content": "e",
  35. "isRight": false,
  36. "imgUrl": ""
  37. }, {
  38. "id": 0,
  39. "content": "f",
  40. "isRight": false,
  41. "imgUrl": ""
  42. }],
  43. "analysis": "<p>a答案正确</p>",
  44. "analysisVideo": "http://www.baidu.com",
  45. "remark": "测试题目",
  46. "tagIds": [1, 3]
  47. }

2.2.1保存/更新题目信息

题目的主体数据(题干,难度,题目类型) 保存到t_question, 如果选项Id为0,即为保存,非0为更新。

2.2.2保存/更新题干选项

题目选项数据保存到t_question_item表中,如果选项Id为0,即为保存,非0为更新。

2.2.3保存/更新标签信息
  1. 题目与学科标签是多对多的关系,需要使用关系表tr_question_tag来保存题目与学科标签的关系。当前业务需要考虑题目的新建与更新,对于的标签关系也是如此。如果标签关系发生改变,需要同时删除旧关系,新增新关系的方式来实现。
  1. questionEditor.html的createItem()函数里面 请求QuestionController
  2. 在QuestionController里面创建addOrUpdate()
  1. //1.获得请求参数 封装成Question
  2. //2.获得用户id
  3. //3.调用业务 进行新增或者更新
  4. //4.响应
  1. 在QuestionService里面创建addOrUpdate()
  1. public void addOrUpdate(Question question){
  2. //1.保存或者更新题目信息
  3. //2.保存或者更新题目选项选项
  4. //3.维护题目和标签的关系
  5. }
  1. QuestionDao, QuestionItemDao, TagDao

3.实现

3.1初始化页面数据实现

3.1.1初始化学科

前端
  • questionEditor.html
  1. //获取所有学科,初始化学科下拉列表
  2. initCourses() {
  3. let t = this;
  4. //发送异步请求,获取所有学科数据
  5. axios.get("/course/findAll.do").then(response=>{
  6. if (response.data.flag) {
  7. //获取成功
  8. this.courses = response.data.result
  9. }else {
  10. //获取失败
  11. this.$message({
  12. type:"error",
  13. message:response.data.message,
  14. showClose:true
  15. })
  16. }
  17. })
  18. }

后台
  • CourseController(不需要修改)
  1. @RequestMapping("/course/findAll")
  2. public void findAll(HttpServletRequest request, HttpServletResponse response) throws IOException {
  3. try {
  4. //1. 获取请求参数status
  5. String status = request.getParameter("status");
  6. //调用业务层的方法,查询所有学科信息
  7. List<Course> courseList = courseService.findAll(status);
  8. //封装响应结果
  9. JsonUtils.printResult(response,new Result(true,"获取所有学科成功",courseList));
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. JsonUtils.printResult(response,new Result(false,"获取所有学科失败"));
  13. }
  14. }
  • CourseService(不需要修改)
  1. public List<Course> findAll(String status) throws Exception {
  2. SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
  3. CourseDao courseDao = sqlSession.getMapper(CourseDao.class);
  4. List<Course> courseList = courseDao.findAll(status);
  5. SqlSessionFactoryUtils.commitAndClose(sqlSession);
  6. return courseList;
  7. }
  • CatalogDao

  • TagDao

  1. public interface TagDao {
  2. List<Tag> findTagListByCourseId(int courseId);
  3. }
  4. <select id="findTagListByCourseId" resultType="Tag" parameterType="int">
  5. select * from t_tag where courseId=#{courseId}
  6. </select>
  • CourseDao (添加一个外部查询)
  1. <!--进行一对多的分步查询映射-->
  2. <resultMap id="courseCatalogMap" type="Course">
  3. <id property="id" column="id"></id>
  4. <!--一对多使用collection标签-->
  5. <!--查询二级目录列表-->
  6. <collection property="catalogList" ofType="Catalog" column="id"
  7. select="com.itheima.mm.dao.CatalogDao.findCatalogListByCourseId"></collection>
  8. <!--查询标签列表-->
  9. <collection property="tagList" ofType="Tag" column="id"
  10. select="com.itheima.mm.dao.TagDao.findTagListByCourseId"></collection>
  11. </resultMap>
  12. <select id="findAll" resultMap="courseCatalogMap" parameterType="string">
  13. select id,name from t_course
  14. <where>
  15. <if test="status != null and status != '' and status != 'null'">
  16. isShow=#{status}
  17. </if>
  18. </where>
  19. </select>

3.2数据保存实现

  • questionEditor.html
  1. // 提交表单
  2. createItem() {
  3. // 表单校验
  4. let isValid = false;
  5. this.$refs['formData'].validate((valid) => {
  6. isValid = valid;
  7. });
  8. // 如果表单校验不通过,直接终止
  9. if (!isValid) {
  10. return;
  11. }
  12. // 封装请求参数
  13. let formData = this.formData;
  14. // 包装企业相关数据
  15. let industrys = this.industrys;
  16. let industryList = [];
  17. formData.industryIds.forEach(industryId => {
  18. let industry = industrys.find(industry => {
  19. return industry.id === industryId;
  20. });
  21. // TODO 会有新增的方向
  22. //industryList.push(industry);
  23. if(industry){
  24. industryList.push(industry);
  25. }else{
  26. industryList.push({
  27. id:0,
  28. name:industryId
  29. });
  30. }
  31. });
  32. let company = {
  33. id: formData.companyId,
  34. cityId: formData.cityIds[1],
  35. industryList: industryList
  36. };
  37. formData.company = company;
  38. // 包装选项相关数据
  39. let questionItemList = this.formData.questionItemList;
  40. questionItemList.forEach(val => {
  41. val.isRight = val.isRight ? 1 : 0;
  42. });
  43. // 包装标签相关数据
  44. let tags = this.tags;
  45. let tagList = [];
  46. formData.tagIds.forEach(tagId => {
  47. let tag = tags.find(tag => {
  48. return tag.id === tagId;
  49. });
  50. // TODO 会有新增的标签
  51. //tagList.push(tag);
  52. if(tag){
  53. tagList.push(tag);
  54. }else{
  55. tagList.push({
  56. id:0,
  57. name:tagId
  58. });
  59. }
  60. });
  61. formData.tagList = tagList;
  62. console.log("新增更新题库请求参数:");
  63. console.log(this.formData);
  64. //到这里为止,请求参数已经弄好了
  65. //发送异步请求提交请求参数
  66. axios.post("/question/add.do",formData).then(response=>{
  67. if (response.data.flag) {
  68. this.$message({
  69. type:"success",
  70. message:response.data.message,
  71. showClose:true
  72. })
  73. // 返回到上一级
  74. if (!this.formData.is_classic) {
  75. setTimeout(function () {
  76. window.location.href = "questionBasicList.html";
  77. }
  78. , 1000);
  79. } else {
  80. setTimeout(function () {
  81. window.location.href = "questionClassicList.html";
  82. }, 1000);
  83. }
  84. }else {
  85. this.$message({
  86. type:"error",
  87. message:response.data.message,
  88. showClose:true
  89. })
  90. }
  91. })
  92. }
  • QuestionController
  1. @RequestMapping("/question/add")
  2. public void addQuestion(HttpServletRequest request, HttpServletResponse response) throws IOException {
  3. try {
  4. //1. 获取请求参数,并且封装到Question对象中
  5. Question question = JsonUtils.parseJSON2Object(request, Question.class);
  6. //手动设置缺失的数据: status,reviewStatus,createDate,userId
  7. question.setStatus(Constants.QUESTION_PRE_PUBLISH);
  8. question.setReviewStatus(Constants.QUESTION_PRE_REVIEW);
  9. question.setCreateDate(DateUtils.parseDate2String(new Date()));
  10. User user = (User) request.getSession().getAttribute(Constants.LOGIN_USER);
  11. question.setUserId(user.getId());
  12. //2. 调用业务层的方法,添加题目信息
  13. questionService.add(question);
  14. JsonUtils.printResult(response,new Result(true,"添加试题成功"));
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. JsonUtils.printResult(response,new Result(false,"添加试题失败"));
  18. }
  19. }
  • QuestionService
  1. public void add(Question question){
  2. SqlSession sqlSession = null;
  3. try {
  4. sqlSession = SqlSessionFactoryUtils.openSqlSession();
  5. QuestionDao questionDao = sqlSession.getMapper(QuestionDao.class);
  6. //1. 将t_question表相关的数据存储到t_question表
  7. questionDao.add(question);
  8. //2. 判断选项的集合是否为空,如果不为空将题目的选项信息,存储到t_question_item表
  9. QuestionItemDao questionItemDao = sqlSession.getMapper(QuestionItemDao.class);
  10. List<QuestionItem> questionItemList = question.getQuestionItemList();
  11. if (questionItemList != null && questionItemList.size() > 0) {
  12. for (QuestionItem questionItem : questionItemList) {
  13. //手动设置该选项所属的question的id
  14. questionItem.setQuestionId(question.getId());
  15. //调用dao层的方法,存储每一个题目的选项信息
  16. questionItemDao.add(questionItem);
  17. }
  18. }
  19. //3. 将题目和标签的关联信息,存储到tr_question_tag表中
  20. //获取该题目的所有标签
  21. List<Tag> tagList = question.getTagList();
  22. if (tagList != null && tagList.size() > 0) {
  23. for (Tag tag : tagList) {
  24. Map paramMap = new HashMap<>();
  25. paramMap.put("questionId",question.getId());
  26. paramMap.put("tagId",tag.getId());
  27. //进行关联
  28. questionDao.addQuestionTag(paramMap);
  29. }
  30. }
  31. SqlSessionFactoryUtils.commitAndClose(sqlSession);
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. SqlSessionFactoryUtils.rollbackAndClose(sqlSession);
  35. }
  36. }
  • QuestionDao
  1. <insert id="add" parameterType="Question">
  2. insert into t_question(
  3. subject,type,difficulty,analysis,analysisVideo,
  4. remark,isClassic,status,reviewStatus,createDate,
  5. userId,companyId,catalogId,courseId
  6. )
  7. values (
  8. #{subject},#{type},#{difficulty},#{analysis},#{analysisVideo},
  9. #{remark},#{isClassic},#{status},#{reviewStatus},#{createDate},
  10. #{userId},#{companyId},#{catalogId},#{courseId}
  11. )
  12. <!--获取自增长id-->
  13. <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
  14. select last_insert_id()
  15. </selectKey>
  16. </insert>
  17. <insert id="addQuestionTag" parameterType="map">
  18. insert into tr_question_tag values(#{questionId},#{tagId})
  19. </insert>
  • QuestionItemDao
  1. <insert id="add" parameterType="QuestionItem">
  2. insert into t_question_item(content,imgUrl,isRight,questionId)
  3. values (#{content},#{imgUrl},#{isRight},#{questionId})
  4. </insert>

4.小结

全部完成后,测试题目数据保存的过程,可以使用如下查询,查询数据是否正确保存。

  1. #查询题目
  2. SELECT * from t_question order by id desc;
  3. #查询题目选项
  4. SELECT * from t_question_item order by question_id desc;

案例-图片上传

6.1 功能分析

  1. 题目的单选或复选,可以上传图片作为选项,页面通过后端提供上传图片组件来完成单个图片上传,图片暂时上传到服务端指定的目录保存。图片上传成功后,后端返回一个访问的相对路径(包含图片上传后的新名称),这个路径需要在表单最终提交时,与其他表单数据提交到服务端与题目选项数据一起保存到数据表中。

6.2 实现思路

  1. 后端需要提供一个单独的上传图片组件,然后返回给客户端一个可以保持到数据库的相对路径,另外对上传的图片要重新命名,保证图片名称不冲突。上传图片组件将放在一个名为公共控制器中,本图片上传不涉及其他操作,故不需要ServiceDao
  2. 图片上传涉及到底层IO流的操作,本节将使用三方组件commons-fileupload组件来完成图片上传的功能。
  1. <dependency>
  2. <groupId>commons-fileupload</groupId>
  3. <artifactId>commons-fileupload</artifactId>
  4. <version>1.3.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>commons-io</groupId>
  8. <artifactId>commons-io</artifactId>
  9. <version>2.6</version>
  10. </dependency>

6.3 知识点补充

6.3.1 文件上传表单分析

  • 凡是表单带上传文件,表单提交类型必须是【 enctype=”multipart/form-data”】
  • 必须从底层IO流获取原始数据,然后进行数据解析,获取上传的数据
  • 通过三方组件commons-fileupload(依赖commons-io)来解析IO流数据

6.3.2 commons组件

  • commons-IO组件

    • IOUtils类
    • FileUtils类
  • commons-fileupload组件

    • DiskFileItemFactory 类
    • ServletFileUpload 类
    • FileItem 类

6.4 实现上传组件

  1. 考虑到上传组件的复用性,在完成时考虑同时批量上传的情况。

6.4.1 初始化上传文件路径配置

在webapp下创建img/upload目录

如图所示:

面面项目第三天 - 图8

面面项目第三天 - 图9

6.4.2 新增CommonController

  1. package com.itheima.mm.controller;
  2. import com.itheima.framework.anno.Controller;
  3. import com.itheima.framework.anno.RequestMapping;
  4. import com.itheima.mm.entry.Result;
  5. import com.itheima.mm.utils.JsonUtils;
  6. import com.itheima.mm.utils.UploadUtils;
  7. import org.apache.commons.fileupload.FileItem;
  8. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  9. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  10. import org.apache.commons.io.IOUtils;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13. import java.io.File;
  14. import java.io.FileOutputStream;
  15. import java.io.IOException;
  16. import java.io.InputStream;
  17. import java.util.List;
  18. /**
  19. * 包名:com.itheima.mm.controller
  20. *
  21. * @author Leevi
  22. * 日期2020-08-04 14:38
  23. */
  24. @Controller
  25. public class CommonController {
  26. @RequestMapping("/common/uploadFile")
  27. public void uploadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
  28. InputStream is = null;
  29. FileOutputStream os = null;
  30. try {
  31. //1. 获取客户端上传的图片
  32. //创建磁盘工厂对象
  33. DiskFileItemFactory itemFactory = new DiskFileItemFactory();
  34. //创建Servlet的上传解析对象,构造方法中,传递磁盘工厂对象
  35. ServletFileUpload fileUpload = new ServletFileUpload(itemFactory);
  36. /*
  37. * fileUpload调用方法 parseRequest,解析request对象
  38. * 页面可能提交很多内容 文本框,文件,菜单,复选框 是为FileItem对象
  39. * 返回集合,存储的文件项对象
  40. */
  41. List<FileItem> list = fileUpload.parseRequest(request);
  42. String imgUrl = null;
  43. for (FileItem fileItem : list) {
  44. if (!fileItem.isFormField()) {
  45. //是上传的文件
  46. //获取文件名
  47. String fileName = fileItem.getName();
  48. //获取上传的文件
  49. is = fileItem.getInputStream();
  50. //创建一个输出流,输出文件到指定的目录
  51. //使用字节输出流输出到那个文件夹(img/upload)
  52. String uploadPath = request.getServletContext().getRealPath("img/upload");
  53. //创建一个File
  54. File file = new File(uploadPath);
  55. if (!file.exists()) {
  56. //这个目录不存在
  57. //将这个目录创建出来
  58. file.mkdirs();
  59. }
  60. //文件名会不会重复呢????最好的办法是生成唯一的文件名
  61. String uuidName = UploadUtils.getUUIDName(fileName);
  62. os = new FileOutputStream(new File(file, uuidName));
  63. //将输入流中的文件,通过输出流写到指定的目录中
  64. IOUtils.copy(is, os);
  65. //获取文件的存储路径imgUrl
  66. imgUrl = "img/upload/" + uuidName;
  67. }
  68. }
  69. JsonUtils.printResult(response, new Result(true, "图片上传成功", imgUrl));
  70. } catch (Exception e) {
  71. e.printStackTrace();
  72. JsonUtils.printResult(response, new Result(true, "图片上传失败"));
  73. } finally {
  74. is.close();
  75. os.close();
  76. }
  77. }
  78. }

6.4.3 编写前端代码

  1. questionEditor.html页面中,找到handleHttpRequest方法,加入axios请求。
  1. //文件上传的方法
  2. handleHttpRequest(params, index) {
  3. let file = params.file;
  4. //请求参数
  5. let formData = new FormData();
  6. //键值对: key就是文件名、value就是文件
  7. formData.append(file.name, file);
  8. //文件上传一定是发送post请求,而且一定要设置请求参数的类型(表单数据类型)
  9. axios.defaults.headers.post['Content-Type'] = 'multipart/form-data;charse=UTF-8';
  10. axios.post("/common/uploadFile.do",formData).then(response=>{
  11. if (response.data.flag) {
  12. //文件上传成功
  13. this.$message({
  14. type:"success",
  15. message:response.data.message,
  16. showClose:true
  17. })
  18. //1. 将响应的数据(文件存储路径),设置给选项的imgUrl属性
  19. this.formData.questionItemList[index].imgUrl = response.data.result
  20. //2. 图片回显
  21. }else {
  22. //文件上传失败
  23. this.$message({
  24. type:"error",
  25. message:response.data.message,
  26. showClose:true
  27. })
  28. }
  29. })
  30. }

两处el-upload

  1. <el-upload
  2. class="avatar-uploader"
  3. action=""
  4. :show-file-list="false"
  5. :http-request="function(params) {return handleHttpRequest(params, index)}">
  6. <img v-if="item.imgUrl" :src="'/' + item.imgUrl" class="avatar">
  7. <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  8. </el-upload>

单独测试成功后,输入表单数据,查看数据库表中的数据是否正确,正确如图所示:

面面项目第三天 - 图10