项目之显示回答和显示评论(13)

举报
海拥 发表于 2021/08/05 00:37:31 2021/08/05
【摘要】 67. 显示回答列表-持久层 (a) 规划SQL语句 显示某问题的回答列表,需要执行的SQL语句大致是: select * from answer where question_id=16 order by status_of_accept desc, created_time desc 1 以上查询只是针对一张表,并且是查询所有字段,则使用实体类就可以封装以上...

67. 显示回答列表-持久层

(a) 规划SQL语句

显示某问题的回答列表,需要执行的SQL语句大致是:

select * from answer where question_id=16 order by status_of_accept desc, created_time desc

  
 
  • 1

以上查询只是针对一张表,并且是查询所有字段,则使用实体类就可以封装以上查询到的数据!但是,本次查询应该另创建VO类用于封装查询结果,因为后续显示回答时,每个“回答”还可以存在若干个“评论”,则在“回答”的数据中,应该存在List<评论>的属性,由于当前还没有开发“评论”,所以,暂时无法设计这个属性,但是,后续一定用得上,基于“实体类需要对应数据表”,所以,迟早需要改为VO类来表示此次查询结果,就直接使用VO类了!

所以,应该事先创建AnswerVO类,声明与Answer实体类完全相同的属性即可。

@Data
@Accessors(chain = true)
public class AnswerVO { private Integer id; private String content; private Integer countOfLikes; private Integer userId; private String userNickName; private Integer questionId; private LocalDateTime createdTime; private Integer statusOfAccept;

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(b) 接口的抽象方法

AnswerMapper中添加抽象方法:

/**
 * 根据问题的id查询回答的列表
 *
 * @param questionId 问题的id
 * @return 该问题的所有回答的列表
 */
List<AnswerVO> findListByQuestionId(Integer questionId);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注意:尽管可以使用MyBatis Plus实现查询功能,本次操作必须在持久层自定义抽象方法并配置SQL语句,因为当开发了“评论”后,当前查询需要改为关联查询!

© 配置SQL

AnswerMapper.xml中,将原有的<resultMap>复制,修改idtype,将应用于配置以上抽象方法的查询:

<resultMap id="AnswerVO_Map" type="cn.tedu.straw.portal.vo.AnswerVO"> <id column="id" property="id" /> <result column="content" property="content" /> <result column="count_of_likes" property="countOfLikes" /> <result column="user_id" property="userId" /> <result column="user_nick_name" property="userNickName" /> <result column="question_id" property="questionId" /> <result column="created_time" property="createdTime" /> <result column="status_of_accept" property="statusOfAccept" />
</resultMap>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后,配置以上抽象方法对应的SQL语句:

<select id="findListByQuestionId" resultMap="AnswerVO_Map"> SELECT * FROM answer WHERE question_id=#{questionId} ORDER BY status_of_accept DESC, created_time DESC
</select>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(d) 单元测试

创建AnswerMapperTests,编写并执行单元测试:

@SpringBootTest
@Slf4j
public class AnswerMapperTests { @Autowired AnswerMapper mapper; @Test void findListByQuestionId() { Integer questionId = 16; List<AnswerVO> answers = mapper.findListByQuestionId(questionId); for (AnswerVO answer : answers) { log.debug("AnswerVO >>> {}", answer); } }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

68. 显示回答列表-业务层

(a) 设计业务并创建必要的异常

(b) 接口的抽象方法

IAnswerService中添加:

/**
 * 根据问题的id查询回答的列表
 *
 * @param questionId 问题的id
 * @return 该问题的所有回答的列表
 */
List<AnswerVO> getListByQuestionId(Integer questionId);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

© 实现业务

AnswerServiceImpl中实现:

@Override
public List<AnswerVO> getListByQuestionId(Integer questionId) { return answerMapper.findListByQuestionId(questionId);
}

  
 
  • 1
  • 2
  • 3
  • 4

(d) 单元测试

AnswerServiceTests中编写并执行单元测试:

@Test
void getListByQuestionId() { Integer questionId = 16; List<AnswerVO> answers = service.getListByQuestionId(questionId); for (AnswerVO answer : answers) { log.debug("AnswerVO >>> {}", answer); }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

69. 显示回答列表-控制器层

(a) 处理异常

(b) 设计请求

请求路径:/api/v1/answers/question/{questionId}

请求参数:@PathVariable("questionId") Integer questionId

请求方式:GET

响应结果:R<List<AnswerVO>>

© 处理请求

AnswerController中处理请求:

// http://localhost:8080/api/v1/answers/question/16
@GetMapping("/question/{questionId}")
public R<List<AnswerVO>> getListByQuestionId(@PathVariable("questionId") Integer questionId) { return R.ok(answerService.getListByQuestionId(questionId));
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

(d) 测试

http://localhost:8080/api/v1/answers/question/16

70. 显示回答列表-前端界面

将原postAnswers.js改名为answers.js,并在detail.html中修改<script src="/js/question/postAnswers.js"><script src="/js/question/answers.js">

detail.html中调整Vue对象的位置,使用id为answersApp,在answers.js中,修改Vue对象对应页面元素的el值。

修改完成后,在answers.js中添加模拟的”回答“列表:

let answersApp = new Vue({ el: '#answersApp', data: { answers: [ { userNickName: '成老师', createdTimeText: '37分钟前', content: '说了半天还是没答案啊' }, { userNickName: '小刘老师', createdTimeText: '3小时前', content: '你们能不能好好说' }, { userNickName: '范老师', createdTimeText: '6小时前', content: '你们说了几天也等于啥也没说' }, { userNickName: '王老师', createdTimeText: '2天前', content: '不好说也得好好说' }, { userNickName: '大刘老师', createdTimeText: '3天前', content: '这个问题不好说啊' } ] }, // 忽略后续代码
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

并在页面中应用Vue。

测试无误后,添加:

loadAnswers: function() { let id = location.search; if (!id) { alert("非法访问!参数不足!"); location.href = '/index.html'; return; } id = id.substring(1); if (!id || isNaN(id)) { // is not a number alert("非法访问!参数不足!"); location.href = '/index.html'; return; } $.ajax({ url: '/api/v1/answers/question/' + id, success: function(json) { let answers = json.data; for (let i = 0; i < answers.length; i++) { answers[i].createdTimeText = getCreatedTimeText(answers[i].createdTime); } answersApp.answers = answers; } });
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

71. 发表回答后实时更新回答列表

在JavaScript中,定义了unshift(arg)函数,可以将参数添加到数组中并且作为数组的第1个元素!

当老师填写”回答“后,就可以将”回答“的数据添加到Vue的”回答列表“中,且作为第1个元素,则页面就会显示刚刚提交的”回答“数据,例如:

answersApp.answers.unshift(answer);

  
 
  • 1

要实现以上效果,必须保证”服务器端响应的结果中包含新提交的回答数据“!

先在IAnswerService中修改原方法的声明,将返回值从void改为Answer

/**
 * 提交问题的回复
 *
 * @param answerDTO 客户端提交的回复对象
 * @param userId 当前登录的用户id
 * @param userNickName 当前登录的用户昵称
 * @return 新创建的“回答”对象
 */
Answer post(AnswerDTO answerDTO, Integer userId, String userNickName);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

然后,在AnswerServiceImpl实现类,也修改原方法的声明,并返回对象:

@Override
public Answer post(AnswerDTO answerDTO, Integer userId, String userNickName) { // 原有代码不变 // 在最后补充返回 return answer;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在控制器中,将处理”提交回答“请求的方法改为返回R<Answer>

// http://localhost:8080/api/v1/answers/post?questionId=1&content=666
@RequestMapping("/post")
public R<Answer> post(@Validated AnswerDTO answerDTO, BindingResult bindingResult, @AuthenticationPrincipal UserInfo userInfo) { if (bindingResult.hasErrors()) { String message = bindingResult.getFieldError().getDefaultMessage(); throw new ParameterValidationException(message); } Answer answer = answerService.post(answerDTO, userInfo.getId(), userInfo.getNickname()); return R.ok(answer);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

最后,在前端页面中,当成功的提交了”回答“后:

// 获取服务器端返回的新回答案的数据
let answer = json.data;
// unshift():在数组顶部添加元素
answersApp.answers.unshift(answer);

  
 
  • 1
  • 2
  • 3
  • 4

72. 导入评论表并生成文件

导入”评论“的数据表,并运行straw-generator代码生成器项目,生成相关的文件,将这些文件复制到straw-portal项目中。

73. 添加评论-持久层

(a)规划需要执行的SQL语句

添加评论的本质是向数据表中插入数据,由MyBatis Plus已经生成了对应的功能。

(b)接口的抽象方法

(c)配置SQL语句

(d)单元测试

74. 添加评论-业务层

(a)规划业务并创建异常

(b)接口的抽象方法

dto包下创建CommentDTO

@Data
@Accessors(chain=true)
public class CommentDTO { private Integer answerId; private String content;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

ICommentService中添加抽象方法:

/**
 * 发表评论
 *
 * @param commentDTO   评论的数据
 * @param userId 当前登录的用户的id
 * @param userNickName 当前登录的用户的昵称
 * @return 成功发表的评论数据
 */
Comment post(CommentDTO commentDTO, Integer userId, String userNickName);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

(c)实现业务

先规划业务的实现步骤:

public Comment post(CommentDTO commentDTO, Integer userId, String userNickName) { // 创建Comment对象 // 向Comment对象中封装数据:user_id			>>> 参数userId // 向Comment对象中封装数据:user_nick_name	>>> 参数userNickName // 向Comment对象中封装数据:answer_id		>>> commentDTO // 向Comment对象中封装数据:content			>>> commentDTO // 向Comment对象中封装数据:created_time		>>> 创建当前时间对象 // 调用int commentMapper.insert(Comment comment)方法插入评论数据,获取返回的受影响行数 // 判断返回值是否不为1 // 是:抛出InsertException // 返回Comment对象
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

具体实现:

@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements ICommentService { @Autowired private CommentMapper commentMapper; @Override public Comment post(CommentDTO commentDTO, Integer userId, String userNickName) { // 创建Comment对象 // 向Comment对象中封装数据:user_id			>>> 参数userId // 向Comment对象中封装数据:user_nick_name	>>> 参数userNickName // 向Comment对象中封装数据:answer_id		>>> commentDTO // 向Comment对象中封装数据:content			>>> commentDTO // 向Comment对象中封装数据:created_time		>>> 创建当前时间对象 Comment comment = new Comment() .setUserId(userId) .setUserNickName(userNickName) .setAnswerId(commentDTO.getAnswerId()) .setContent(commentDTO.getContent()) .setCreatedTime(LocalDateTime.now()); // 调用int commentMapper.insert(Comment comment)方法插入评论数据,获取返回的受影响行数 int rows = commentMapper.insert(comment); // 判断返回值是否不为1 if (rows != 1) { // 是:抛出InsertException throw new InsertException("发表评论失败!服务器忙,请稍后再次尝试!"); } // 返回Comment对象 return comment; }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

(d)单元测试

创建CommentServiceTests测试类,编写并执行单元测试:

@SpringBootTest
@Slf4j
public class CommentServiceTests { @Autowired ICommentService service; @Test void post() { CommentDTO commentDTO = new CommentDTO() .setAnswerId(4) .setContent("测试评论---1"); Integer userId = 15; String userNickName = "机器猫"; Comment comment = service.post(commentDTO, userId, userNickName); log.debug("OK, comment >>> {}", comment); }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

75. 添加评论-控制器层

(a)处理新的异常

(b)设计请求

请求路径:/api/v1/comments/post

请求参数:CommentDTO commentDTO, UserInfo userInfo

请求类型:post

响应数据:R<Comment>

(c)处理请求

CommentController中处理请求:

@RestController
@RequestMapping("/api/v1/comments")
public class CommentController { @Autowired private ICommentService commentService; // http://localhost:8080/api/v1/comments/post?answerId=4&content=Comment---2 @RequestMapping("/post") public R<Comment> post(CommentDTO commentDTO, @AuthenticationPrincipal UserInfo userInfo) { Comment comment = commentService.post(commentDTO, userInfo.getId(), userInfo.getNickname()); return R.ok(comment); }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

(d)测试

http://localhost:8080/api/v1/comments/post?answerId=4&content=Comment—2

76. 添加评论-前端页面

目前,每个”回答“下方都有”添加评论“按钮,但是,不同的”添加评论“按钮是会互相影响的,是因为:

在这里插入图片描述

修改为:

在这里插入图片描述

然后,修改评论的表单,以保证可以提交请求,且提交请求时可以获取评论内容:

在这里插入图片描述

测试提交请求:

postComment: function(answerId) { let content = $('#commentContent' + answerId).val(); alert("准备发表答案id=" + answerId + "的评论,评论内容是:" + content);
}

  
 
  • 1
  • 2
  • 3
  • 4

完成提交请求:

postComment: function(answerId) { let content = $('#commentContent' + answerId).val(); // alert("准备发表答案id=" + answerId + "的评论,评论内容是:" + content); let data = { answerId: answerId, content: content } $.ajax({ url: '/api/v1/comments/post', data: data, type: 'post', success: function(json) { if (json.state == 2000) { alert("发表评论成功!"); console.log("服务器端返回的新发表的评论数据 >>> " + json.data); // 未完,仍需处理页面的显示 } else { alert(json.message); } } });
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

77. 显示评论列表-持久层

(a)规划需要执行的SQL语句

应该在查询”回答“列表时,就一并查出每个”回答“对应的若干条”评论“!

将涉及关联查询,需要执行的SQL语句大致是:

SELECT
	*
FROM
	answer
LEFT JOIN
	comment
ON
	answer.id=comment.answer_id
WHERE
	question_id=?
ORDER BY
	status_of_accept DESC,
	answer.created_time DESC,
	comment.created_time DESC

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在测试以上SQL语句可以执行之前,还需要对SQL语句做进一步的调整,因为以上SQL语句的查询结果中存在多个名称相同的列,MyBatis框架在处理时,如果存在同名的列,只会处理靠前的列的数据,靠后的列的数据会被无视!所以,必须使用自定义别名的方式,使得查询结果中的每一列的名称都不相同!

则需要将以上SQL语句改为:

SELECT
	answer.*,
	comment.id AS comment_id,
	comment.user_id 		AS comment_user_id,
	comment.user_nick_name 	AS comment_user_nick_name,
	comment.answer_id 		AS comment_answer_id,
	comment.content 		AS comment_content,
	comment.created_time 	AS comment_created_time
FROM
	answer
LEFT JOIN
	comment
ON
	answer.id=comment.answer_id
WHERE
	question_id=?
ORDER BY
	status_of_accept DESC,
	answer.created_time DESC,
	comment.created_time DESC

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

(b)接口与抽象方法

使用AnswerMapper原有的List<AnswerVO> findListByQuestionId(Integer questionId)方法即可,只不过,需要修改AnswerVO类,在这个类中添加private List<Comment> comments;用于表示每一个”回答“数据中有若干个”评论“。

(c)配置SQL语句

<resultMap id="AnswerVO_Map" type="cn.tedu.straw.portal.vo.AnswerVO"> <id column="id" property="id" /> <result column="content" property="content" /> <result column="count_of_likes" property="countOfLikes" /> <result column="user_id" property="userId" /> <result column="user_nick_name" property="userNickName" /> <result column="question_id" property="questionId" /> <result column="created_time" property="createdTime" /> <result column="status_of_accept" property="statusOfAccept" /> <collection property="comments" ofType="cn.tedu.straw.portal.model.Comment"> <id column="comment_id" property="id" /> <result column="comment_user_id" property="userId" /> <result column="comment_user_nick_name" property="userNickName" /> <result column="comment_answer_id" property="answerId" /> <result column="comment_content" property="content" /> <result column="comment_created_time" property="createdTime" /> </collection>
</resultMap>

<select id="findListByQuestionId" resultMap="AnswerVO_Map"> SELECT answer.*, comment.id AS comment_id, comment.user_id 		AS comment_user_id, comment.user_nick_name 	AS comment_user_nick_name, comment.answer_id 		AS comment_answer_id, comment.content 		AS comment_content, comment.created_time 	AS comment_created_time FROM answer LEFT JOIN comment ON answer.id=comment.answer_id WHERE question_id=#{questionId} ORDER BY status_of_accept DESC, answer.created_time DESC, comment.created_time DESC
</select>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

(d)单元测试

直接运行原本已经开发的单元测试即可!

关于”回答列表“的业务层和控制器层都是不需要调整的,直接测试控制器层,可以看到返回的JSON数据就已经包含了List<Comment> comments的数据!

77. 显示评论列表-前端页面

首先,需要调整的是”显示回答列表“中的评论数量:

在这里插入图片描述

然后,遍历”回答“中的”评论列表“:

在这里插入图片描述

经过以上调整后,显示每个”回答“时,都会尝试显示该”回答“匹配的”评论列表“,即读取answer中的comments,但是,新发表”回答“时,插入到顶部的”回答“数据是服务器端响应的,并不包含comments,会导致读取该项的comments失败,为了避免这个问题,同时基于”新的回答肯定还没有被评论“,在将新的”回答“插入到顶部之前,为其补充空的comments属性,即:

let answer = json.data;
answer.createdTimeText = getCreatedTimeText(answer.createdTime);
answer.comments = []; // 在将新的”回答“插入到顶部之前,为其补充空的comments属性
// unshift():在数组顶部添加元素
answersApp.answers.unshift(answer);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

注意:如果Vue对象的answers属性存在模拟数据,这些模拟数据中也必须包含在页面显示时所使用到的属性,例如comments等,否则,在浏览器的控制台会报错,因为这些模拟数据会在页面打开之初就加载!当然,这些报错不影响程序的正常使用,因为打开页面之后,会直接发出异步请求,获取服务器响应的真实数据!所以,这些错只是短暂存在的!删除这些模拟数据就不会存在这些问题,如果要保留模拟数据,必须保证各属性都已经被声明,例如:

answers: [ { userNickName: '成老师', createdTimeText: '37分钟前', content: '说了半天还是没答案啊', comments: [] }, { userNickName: '小刘老师', createdTimeText: '3小时前', content: '你们能不能好好说', comments: [] }, { userNickName: '范老师', createdTimeText: '6小时前', content: '你们说了几天也等于啥也没说', comments: [] }, { userNickName: '王老师', createdTimeText: '2天前', content: '不好说也得好好说', comments: [] }, { userNickName: '大刘老师', createdTimeText: '3天前', content: '这个问题不好说啊', comments: [] }
]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最后,每次发表”评论“之后,新的评论并不会自动显示到页面,所以,在发表成功后,还应该:

if (json.state == 2000) { alert("发表评论成功!"); // 从服务器端返回的数据中获取“评论”数据对象 let comment = json.data; // 由于当前页面的数据answers包含多条“回答” // 需要先找到本次评论对应的“回答” // 则遍历整个answers(即所有“回答”),检查id与参数answerId是否相同 for (let i = 0; i < answersApp.answers.length; i++) { if (answersApp.answers[i].id == answerId) { answersApp.answers[i].comments.unshift(comment); break; } }
} else { alert(json.message);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

文章来源: haiyong.blog.csdn.net,作者:海拥✘,版权归原作者所有,如需转载,请联系作者。

原文链接:haiyong.blog.csdn.net/article/details/107739447

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。