8-1 保存留言功能开发
8-2 查询留言列表分页接口
8-3 留言列表分页前后端联调
8-4 评论回复sql设计与查询
8-5 页显示回复评论
8-6 评论回复功能开发
1、在VideoController.java中编写保存留言的接口saveComment
@PostMapping("/saveComment")
public IMoocJSONResult saveComment(@RequestBody Comments comment,
String fatherCommentId, String toUserId) throws Exception {
comment.setFatherCommentId(fatherCommentId);
comment.setToUserId(toUserId);
videoService.saveComment(comment);
return IMoocJSONResult.ok();
}
2、添加拦截器
3、在VideoService.java中定义saveComment的方法:
/**
* @Description: 用户留言
*/
public void saveComment(Comments comment);
4、在VideoServiceImpl.java中对上面的方法进行实现:
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void saveComment(Comments comment) {
String id = sid.nextShort();
comment.setId(id);
comment.setCreateTime(new Date());
commentMapper.insert(comment);
}
5、前端:在videoinfo.wxml中编写前端页面代码:
<view class="saySthView">
<input name="commentContent" class="saySth" placeholder="说点什么吧。。。" confirm-type="send" bindconfirm="saveComment" focus='{
{commentFocus}}' value='{
{contentValue}}'
data-replyFatherCommentId='{
{replyFatherCommentId}}'
data-replyToUserId='{
{replyToUserId}}'
/>
</view>
6、在videoinfo.js中编写前端逻辑代码:
使得点击留言按钮,可以跳出最下方的弹框,进行编写留言:
leaveComment:function(){
this.setData({
commentFocus:true
});
}
保存留言到数据库的代码:
saveComment:function(e){
var me = this;
var content = e.detail.value;
var user = app.getGlobalUserInfo();
var videoInfo = JSON.stringify(me.data.videoInfo);
var realUrl = '../videoinfo/videoinfo#videoInfo@' + videoInfo;
if (user == null || user == undefined || user == '') {
wx.navigateTo({
url: '../userLogin/login?redirectUrl=' + realUrl,
})
} else {
wx.showLoading({
title: '请稍后...',
})
wx.request({
url: app.serverUrl + '/video/saveComment',
method: 'POST',
header: {
'content-type': 'application/json', // 默认值
'userId':user.id,//用于安全验证的信息
'userToken':user.userToken
},
data: {
fromUserId: user.id,
videoId: me.data.videoInfo.id,
comment: content
},
success: function(res) {
console.log(res.data)
wx.hideLoading();
me.setData({
contentValue: ""
});
}
})
}
}
1、在VideoController.java中编写保存留言的接口getVideoComments
@PostMapping("/getVideoComments")
public IMoocJSONResult getVideoComments(String videoId, Integer page, Integer pageSize) throws Exception {
if (StringUtils.isBlank(videoId)) {
return IMoocJSONResult.ok();
}
// 分页查询视频列表,时间顺序倒序排序
if (page == null) {
page = 1;
}
if (pageSize == null) {
pageSize = 10;
}
PagedResult list = videoService.getAllComments(videoId, page, pageSize);
return IMoocJSONResult.ok(list);
}
2、添加拦截器
这个接口是不需要被拦截的,所以不用加;
3、在VideoService.java中定义getAllComments的方法:
/**
* @Description: 留言分页
*/
public PagedResult getAllComments(String videoId, Integer page, Integer pageSize);
4、在VideoServiceImpl.java中对上面的方法进行实现:
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public PagedResult getAllComments(String videoId, Integer page, Integer pageSize) {
PageHelper.startPage(page, pageSize);
List<CommentsVO> list = commentMapperCustom.queryComments(videoId);
for (CommentsVO c : list) {
String timeAgo = TimeAgoUtils.format(c.getCreateTime());
c.setTimeAgoStr(timeAgo);
}
PageInfo<CommentsVO> pageList = new PageInfo<>(list);
PagedResult grid = new PagedResult();
grid.setTotal(pageList.getPages());
grid.setRows(list);
grid.setPage(page);
grid.setRecords(pageList.getTotal());
return grid;
}
5、新建自定义mapper方法:
1)首先新建pojo类:在com.asayi.pojo.vo包中新建CommentsVO.java自定义pojo对象:
package com.asayi.pojo.vo;
import java.util.Date;
import javax.persistence.*;
import org.springframework.format.annotation.DateTimeFormat;
public class CommentsVO {
private String id;
/**
* 视频id
*/
private String videoId;
/**
* 留言者,评论的用户id
*/
private String fromUserId;
private Date createTime;
/**
* 评论内容
*/
private String comment;
private String faceImage;
private String nickname;
private String toNickname;
private String timeAgoStr;
public String getTimeAgoStr() {
return timeAgoStr;
}
public void setTimeAgoStr(String timeAgoStr) {
this.timeAgoStr = timeAgoStr;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getFaceImage() {
return faceImage;
}
public void setFaceImage(String faceImage) {
this.faceImage = faceImage;
}
/**
* @return id
*/
public String getId() {
return id;
}
/**
* @param id
*/
public void setId(String id) {
this.id = id;
}
/**
* 获取视频id
*
* @return video_id - 视频id
*/
public String getVideoId() {
return videoId;
}
/**
* 设置视频id
*
* @param videoId 视频id
*/
public void setVideoId(String videoId) {
this.videoId = videoId;
}
/**
* 获取留言者,评论的用户id
*
* @return from_user_id - 留言者,评论的用户id
*/
public String getFromUserId() {
return fromUserId;
}
/**
* 设置留言者,评论的用户id
*
* @param fromUserId 留言者,评论的用户id
*/
public void setFromUserId(String fromUserId) {
this.fromUserId = fromUserId;
}
/**
* @return create_time
*/
public Date getCreateTime() {
return createTime;
}
/**
* @param createTime
*/
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
/**
* 获取评论内容
*
* @return comment - 评论内容
*/
public String getComment() {
return comment;
}
/**
* 设置评论内容
*
* @param comment 评论内容
*/
public void setComment(String comment) {
this.comment = comment;
}
public String getToNickname() {
return toNickname;
}
public void setToNickname(String toNickname) {
this.toNickname = toNickname;
}
}
2)新建VideosMapperCustom.java的mapper对象:
package com.asayi.mapper;
import java.util.List;
import com.asayi.pojo.Comments;
import com.asayi.pojo.vo.CommentsVO;
import com.asayi.utils.MyMapper;
public interface CommentsMapperCustom extends MyMapper<Comments> {
public List<CommentsVO> queryComments(String videoId);
}
3)在VideosMapperCustom.xml中实现mapper的sql语句编写:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.asayi.mapper.CommentsMapperCustom" >
<resultMap id="BaseResultMap" type="com.asayi.pojo.vo.CommentsVO" >
<!--
WARNING - @mbg.generated
-->
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="video_id" property="videoId" jdbcType="VARCHAR" />
<result column="from_user_id" property="fromUserId" jdbcType="VARCHAR" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
<result column="comment" property="comment" jdbcType="LONGVARCHAR" />
<result column="face_image" property="faceImage" jdbcType="VARCHAR" />
<result column="nickname" property="nickname" jdbcType="VARCHAR" />
<result column="toNickname" property="toNickname" jdbcType="VARCHAR" />
</resultMap>
select c.*,u.face_image as face_image,u.nickname from comments c left join asayi_users u on c.from_user_id = u.id
where c.video_id = #{
videoId} order by c.create_time desc
</mapper>
6、开源工具类:
package com.asayi.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeAgoUtils {
private static final long ONE_MINUTE = 60000L;
private static final long ONE_HOUR = 3600000L;
private static final long ONE_DAY = 86400000L;
private static final long ONE_WEEK = 604800000L;
private static final String ONE_SECOND_AGO = "秒前";
private static final String ONE_MINUTE_AGO = "分钟前";
private static final String ONE_HOUR_AGO = "小时前";
private static final String ONE_DAY_AGO = "天前";
private static final String ONE_MONTH_AGO = "月前";
private static final String ONE_YEAR_AGO = "年前";
public static String format(Date date) {
long delta = new Date().getTime() - date.getTime();
if (delta < 1L * ONE_MINUTE) {
long seconds = toSeconds(delta);
return (seconds <= 0 ? 1 : seconds) + ONE_SECOND_AGO;
}
if (delta < 45L * ONE_MINUTE) {
long minutes = toMinutes(delta);
return (minutes <= 0 ? 1 : minutes) + ONE_MINUTE_AGO;
}
if (delta < 24L * ONE_HOUR) {
long hours = toHours(delta);
return (hours <= 0 ? 1 : hours) + ONE_HOUR_AGO;
}
if (delta < 48L * ONE_HOUR) {
return "昨天";
}
if (delta < 30L * ONE_DAY) {
long days = toDays(delta);
return (days <= 0 ? 1 : days) + ONE_DAY_AGO;
}
if (delta < 12L * 4L * ONE_WEEK) {
long months = toMonths(delta);
return (months <= 0 ? 1 : months) + ONE_MONTH_AGO;
} else {
long years = toYears(delta);
return (years <= 0 ? 1 : years) + ONE_YEAR_AGO;
}
}
private static long toSeconds(long date) {
return date / 1000L;
}
private static long toMinutes(long date) {
return toSeconds(date) / 60L;
}
private static long toHours(long date) {
return toMinutes(date) / 60L;
}
private static long toDays(long date) {
return toHours(date) / 24L;
}
private static long toMonths(long date) {
return toDays(date) / 30L;
}
private static long toYears(long date) {
return toMonths(date) / 365L;
}
public static void main(String[] args) throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:m:s");
Date date = format.parse("2018-05-01 18:35:35");
System.out.println(format(date));
}
}
7、前端:wxml
<block wx:for="{
{commentsList}}">
<view class='comments-all' bindtap='replyFocus'
data-fatherCommentId='{
{item.id}}'
data-toUserId='{
{item.fromUserId}}'
data-toNickname='{
{item.nickname}}'
>
<view class='container-comments'>
<image class="face-comments" src='{
{serverUrl}}{
{item.faceImage}}' ></image>
<view class='nickname-comments'>
<label class='nickname-lbl'>@{
{
item.nickname}}</label>
于
<label class='date-lbl'>{
{
item.timeAgoStr}}</label>
<!-- 留言: -->
<block wx:if="{
{item.toNickname != null}}">
回复
<label class='nickname-lbl'>@{
{
item.toNickname}}</label>
</block>
<block wx:else>
留言:
</block>
</view>
</view>
<view class='comments-content'>{
{
item.comment}}</view>
</view>
</block>
8、在videoinfo.js中编写前端逻辑代码:
从数据库中查询留言:查到后显示到小程序端:
getCommentsList: function(page) {
var me = this;
var videoId = me.data.videoInfo.id;
wx.request({
url: app.serverUrl + '/video/getVideoComments?videoId=' + videoId + "&page=" + page + "&pageSize=5",
method: "POST",
success: function(res) {
console.log(res.data);
}
})
}
9、完善getCommentsList代码:
getCommentsList: function(page) {
var me = this;
var videoId = me.data.videoInfo.id;
wx.request({
url: app.serverUrl + '/video/getVideoComments?videoId=' + videoId + "&page=" + page + "&pageSize=5",
method: "POST",
success: function(res) {
console.log(res.data);
var commentsList = res.data.data.rows;
var newCommentsList = me.data.commentsList;
me.setData({
commentsList: newCommentsList.concat(commentsList),//从后面进行拼接
commentsPage: page,
commentsTotalPage: res.data.data.total
});
}
})
},
//触底响应函数
onReachBottom: function() {
var me = this;
var currentPage = me.data.commentsPage;
var totalPage = me.data.commentsTotalPage;
if (currentPage === totalPage) {
return;
}
var page = currentPage + 1;
me.getCommentsList(page);
}
1、在数据库comments中新建两个数据项:并重新用逆向生成工具生成。
2、修改CommentsMapperCustom.xml中的sql语句:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.asayi.mapper.CommentsMapperCustom" >
<resultMap id="BaseResultMap" type="com.asayi.pojo.vo.CommentsVO" >
<!--
WARNING - @mbg.generated
-->
<id column="id" property="id" jdbcType="VARCHAR" />
<result column="video_id" property="videoId" jdbcType="VARCHAR" />
<result column="from_user_id" property="fromUserId" jdbcType="VARCHAR" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
<result column="comment" property="comment" jdbcType="LONGVARCHAR" />
<result column="face_image" property="faceImage" jdbcType="VARCHAR" />
<result column="nickname" property="nickname" jdbcType="VARCHAR" />
<result column="toNickname" property="toNickname" jdbcType="VARCHAR" />
</resultMap>
<!-- <select id="queryComments" resultMap="BaseResultMap" parameterType="String">
select c.*,u.face_image as face_image,u.nickname from comments c left join asayi_users u on c.from_user_id = u.id
where c.video_id = #{
videoId} order by c.create_time desc
</select>-->
<select id="queryComments" resultMap="BaseResultMap" parameterType="String">
select c.*,u.face_image as face_image,u.nickname,tu.nickname as toNickname
from comments c
left join asayi_users u on c.from_user_id = u.id
left join asayi_users tu on c.to_user_id = tu.id
where c.video_id = #{
videoId} order by c.create_time desc
</select>
</mapper>
修改前端显示代码进行页显示回复评论:
<view class='container-comments'>
<image class="face-comments" src='{
{serverUrl}}{
{item.faceImage}}' ></image>
<view class='nickname-comments'>
<label class='nickname-lbl'>@{
{
item.nickname}}</label>
于
<label class='date-lbl'>{
{
item.timeAgoStr}}</label>
<!-- 留言: -->
<block wx:if="{
{item.toNickname != null}}">
回复
<label class='nickname-lbl'>@{
{
item.toNickname}}</label>
</block>
<block wx:else>
留言:
</block>
</view>
</view>
1、前端代码:
replyFocus: function(e) {
var fatherCommentId = e.currentTarget.dataset.fathercommentid;
var toUserId = e.currentTarget.dataset.touserid;
var toNickname = e.currentTarget.dataset.tonickname;
this.setData({
placeholder: "回复 " + toNickname,
replyFatherCommentId: fatherCommentId,
replyToUserId: toUserId,
commentFocus: true
});
},
saveComment:function(e){
var me = this;
var content = e.detail.value;
// 获取评论回复的fatherCommentId和toUserId
var fatherCommentId = e.currentTarget.dataset.replyfathercommentid;
var toUserId = e.currentTarget.dataset.replytouserid;
var user = app.getGlobalUserInfo();
var videoInfo = JSON.stringify(me.data.videoInfo);
var realUrl = '../videoinfo/videoinfo#videoInfo@' + videoInfo;
if (user == null || user == undefined || user == '') {
wx.navigateTo({
url: '../userLogin/login?redirectUrl=' + realUrl,
})
} else {
wx.showLoading({
title: '请稍后...',
})
wx.request({
url: app.serverUrl + '/video/saveComment?fatherCommentId=' + fatherCommentId + "&toUserId=" + toUserId,
method: 'POST',
header: {
'content-type': 'application/json', // 默认值
'userId':user.id,//用于安全验证的信息
'userToken':user.userToken
},
data: {
fromUserId: user.id,
videoId: me.data.videoInfo.id,
comment: content
},
success: function(res) {
console.log(res.data)
wx.hideLoading();
me.setData({
contentValue: "",
commentsList: [],
placeholder: "",
replyFatherCommentId: "",
replyToUserId: "",
commentFocus: false
});
me.getCommentsList(1);
}
})
}
},
3、修改后端代码:
@PostMapping("/saveComment")
public IMoocJSONResult saveComment(@RequestBody Comments comment,
String fatherCommentId, String toUserId) throws Exception {
comment.setFatherCommentId(fatherCommentId);
comment.setToUserId(toUserId);
videoService.saveComment(comment);
return IMoocJSONResult.ok();
}