目录
一、服务器端向客户端响应“问题”的标签列表数据【续】
当已经把各标签数据存入到Redis中,则在需要获取标签数据时,直接从Redis中获取即可!
目前,服务器端响应到客户端的数据中交不包含“标签列表”,只有各“问题”对应的“标签的id的列表”:
所以,首先,需要在QuestionListItemVO
中添加新的属性,以表示“标签列表”:
由于设计为“从Redis中获取标签列表数据”,所以,获取用户的问题列表的持久层的代码是不需要改动的!在处理“查询某用户的问题列表”的业务层中补充代码,则在QuestionServiceImpl
中,先声明Redis工具类对象:
然后,在getMyQuestions()
方法中:
直接执行原有的业务层单元测试,测试的输出结果例如:
PageInfo{
pageNum=1,
pageSize=3,
size=3,
startRow=1,
endRow=3,
total=15,
pages=5,
list=Page{
count=true,
pageNum=1,
pageSize=3,
startRow=0,
endRow=3,
total=15,
pages=5,
reasonable=false,
pageSizeZero=false
}[....],
prePage=0,
nextPage=2,
isFirstPage=true,
isLastPage=false,
hasPreviousPage=false,
hasNextPage=true,
navigatePages=8,
navigateFirstPage=1,
navigateLastPage=5,
navigatepageNums=[1, 2, 3, 4, 5]
}
并且,再次通过浏览器直接访问控制器层,可以看到响应的结果中已经包含标签列表:
最后,在处理页面时,将原本临时添加的设置模拟数据的标签列表删除即可:
二、显示“我的问答”分页链接
先在index.js
的Vue对象中,添加模拟的分页数据:
然后,在index.html
中使用以上属性进行遍历并显示:
完成后,重新启动straw-gateway
项目,在“我的问答”列表下方可以看到分页链接:
为了完善页面显示效果及功能,还应该在index.js
中添加属性:
在index.html
页面中配置:
然后,在index.js
中,当从服务器端获取到数据后,更新以上Vue属性的值:
全部完成后,重启straw-gateway
项目,分页功能是可以正常使用的!
三、显示问题详情--显示页面
目前,点击主页的各“问题”的标题,应该显示“问题详情”页,但目前显示的结果是404,因为在static
下并没有对应的页面,页面文件已经移动到templates
文件夹下:
可以在SystemController
中添加对/question/detail.html
路径请求的处理,以显示该页面:
完成后,重启straw-gateway
项目,在主页中点击任意“问题”的标题,即可打开“问题详情”页面(页面中显示的数据都是在HTML中设计的模拟数据)。
四、显示问题详情--持久层
显示某问题的详情,需要执行的SQL语句大致是:
select * from question where id=? and is_delete=0
以上查询结果中并不包含“问题”的“标签列表”,而最终响应到客户端的数据是需要包含“标签列表”的!则先在straw-commons
的cn.tedu.straw.commons.vo
包中创建QuestionDetailVO
类,该类的属性与Question
的相同,并在其基础上添加List<TagVO> tags
属性用于表示“标签列表”:
@Data
@Accessors(chain = true)
public class QuestionDetailVO implements Serializable {
private Integer id;
private String title;
private String content;
private Integer userId;
private String userNickName;
private Integer status;
private Integer hits;
private Integer isDelete;
private String tagIds;
private LocalDateTime gmtCreate;
private LocalDateTime gmtModified;
private List<TagVO> tags;}
在QuestionMapper.java
接口中添加抽象方法:
/**
* 根据id查询某“问题”的详情
*
* @param id “问题”的id
* @return 匹配“问题”的详情,如果没有匹配的数据,则返回null
*/
QuestionDetailVO findById(Integer id);
在QuestionMapper.xml
中配置以上抽象方法映射的SQL语句:
<resultMap id="QuestionDetailMap" type="cn.tedu.straw.commons.vo.QuestionDetailVO">
<id column="id" property="id" />
<result column="title" property="title" />
<result column="content" property="content" />
<result column="user_id" property="userId" />
<result column="user_nick_name" property="userNickName" />
<result column="status" property="status" />
<result column="hits" property="hits" />
<result column="is_delete" property="isDelete" />
<result column="tag_ids" property="tagIds" />
<result column="gmt_create" property="gmtCreate" />
<result column="gmt_modified" property="gmtModified" />
</resultMap>
<select id="findById" resultMap="QuestionDetailMap">
SELECT * FROM question WHERE id=#{id}
</select>
在QuestionMapperTests
中编写并执行单元测试:
@Test
void findById() {
Integer id = 10;
QuestionDetailVO question = mapper.findById(id);
log.debug("question >>> {}", question);
}
五、显示问题详情--业务层
先创建“问题数据不存在”的异常:
public class QuestionNotFoundException extends ServiceException {}
在IQuestionService
接口中添加抽象方法:
/**
* 根据id查询某“问题”的详情
*
* @param id “问题”的id
* @return 匹配“问题”的详情
*/
QuestionDetailVO getQuestionDetail(Integer id);
在QuestionServiceImpl
类中实现以上抽象方法:
@Override
public QuestionDetailVO getQuestionDetail(Integer id) {
// 调用持久层的功能查询数据
QuestionDetailVO question = questionMapper.findById(id);
// 判断查询结果是否为null
if (question == null) {
// 是:抛出QuestionNotFoundException
throw new QuestionNotFoundException("获取问题详情失败!尝试访问的数据不存在!");
}
// 补全tags属性的值
String tagIdsString = question.getTagIds(); // 6, 10, 15
String[] tagIdsArray = tagIdsString.split(", "); // {6, 10, 15}
List<TagVO> tags = new ArrayList<>();
for (int i = 0; i < tagIdsArray.length; i++) {
String key = "tag" + tagIdsArray[i]; // tag6
TagVO tag = (TagVO) redisUtils.get(key); // { id:6, name: 'WEB' }
tags.add(tag);
}
question.setTags(tags);
// 返回查询结果
return question;
}
完成后,在QuestionServiceTests
中测试:
@Test
void getQuestionDetail() {
try {
Integer id = 10000;
QuestionDetailVO question = service.getQuestionDetail(id);
log.debug("question >>> {}", question);
} catch (ServiceException e) {
log.debug("查询问题详情失败,错误类型:{}", e.getClass().getName());
log.debug("错误原因:{}", e.getMessage());
}
}
六、显示问题详情--控制器层
由于在业务层创建并抛出了新的异常,需要在R.State
中补充对应的状态码,并在GlobalExceptionHandler
中进行处理。
在QuestionController
中添加处理请求的方法:
// http://localhost:8081/v1/questions/15
// http://localhost/api-question/v1/questions/15
@GetMapping("/{id}")
public R<QuestionDetailVO> showQuestionDetail(@PathVariable("id") Integer id) {
return R.ok(questionService.getQuestionDetail(id));
}
完成后,重启straw-api-question
项目,在浏览器中直接访问以上测试路径,观察是否得到正确的结果。
七、显示问题详情--前端页面
在detail.html
中,先确定“显示问题详情”的区域,配置id
:
在static/js/question
文件夹下创建detail.js
文件,在该文件中创建Vue对象:
并在detail.html
中引用该文件:
然后,在Vue对象中添加测试的模拟数据:
并在页面中绑定这些数据属性以显示模拟数据:
接下来,需要将以上测试数据改为由服务器端响应的数据,由于服务器端响应的时间不是例如“3天前”这样的格式,所以,需要事先引用此前的get_time_text.js
文件:
为了保证在detail.html
中能够正确的显示此前所点击的问题的详情,需要在此前点击问题时把问题的id
作为参数传递到detail.html
页面,推荐将id
放在URL中,便于实现URL分享(将URL复制发送给他人,也可以看到相同的页面),所以,在index.html
中:
然后,在detail.js
中,加载“问题”详情时,先从URL中获取id
值,并简单的判断id
值的格式是否有误,然后,基于该id
从服务器端获取数据,获取的数据中并不包含“发表时间”的字符串文本,所以,还需要处理时间文本,最后,更新Vue属性即可:
loadQuestionDetail: function () {
// alert('准备加载问题详情……');
let id = location.search.substring(1);
if (id == "" || isNaN(id) || id < 1) {
alert('参数错误!');
location.href = '/index.html';
return;
}
$.ajax({
url: '/api-question/v1/questions/' + id,
success: function (json) {
if (json.state == 2000) {
let question = json.data;
question.gmtCreateText = getTimeText(question.gmtCreate);
questionDetailApp.question = question;
} else {
alert(json.message);
location.href = '/index.html';
}
}
});
}
完成后,重启straw-gateway
项目,刷新主页index.html
,点击“我的问答”中任何问题的标题,都可以打开详情页面显示该“问题”。