写在开始 : 主要是评论服务相关, 有兴趣的可以查看之前的连载文章,美食社交项目;
在餐厅详情页面, 展示餐厅的最近十条最新评论
CREATE TABLE `t_reviews` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`fk_restaurant_id` int(11) NULL DEFAULT NULL COMMENT '餐厅外键ID' ,
`content` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL
DEFAULT NULL COMMENT '评论内容' ,
`fk_diner_id` int(11) NULL DEFAULT NULL COMMENT '食客外键ID' ,
`like_it` tinyint(11) NULL DEFAULT NULL ,
`is_valid` tinyint(1) NULL DEFAULT NULL ,
`create_date` datetime NULL DEFAULT NULL ,
`update_date` datetime NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci
AUTO_INCREMENT=1
ROW_FORMAT=COMPACT
;
select
id, fk_restaurant_id, content, fk_diner_id, like_it, create_date,update_date
from
t_reviews
where
is_valid = 1
order by create_date desc limit 10
从数据库中直接查询结果,简单,在数据量较小的情况下,速度应该都能接受,使用Redis保存最新数据,会提高维护成本,但随着评论数的增加,Redis查询的性能肯定会更高,速度更快,数据库压力更小
package com.itkaka.restaurants.model.pojo;
import com.itkaka.commons.model.base.BaseModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@ApiModel(description = "餐厅评论实体类")
public class Reviews extends BaseModel {
@ApiModelProperty("餐厅外键")
private Integer fkRestaurantId;
@ApiModelProperty("评论内容")
private String content;
@ApiModelProperty("食客外键")
private Integer fkDinerId;
@ApiModelProperty(value = "是否喜欢", example = "0=不喜欢,1=喜欢")
private int likeIt;
}
package com.itkaka.restaurants.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.itkaka.commons.model.vo.ShortDinerInfo;
import com.itkaka.restaurants.model.pojo.Reviews;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
@Getter
@Setter
@ApiModel(description = "餐厅评论VO")
public class ReviewVO extends Reviews {
@ApiModelProperty("食客信息")
private ShortDinerInfo dinerInfo;
@ApiModelProperty("创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "GMT+8")
private Date createDate;
}
restaurant_new_reviews("restaurant:new:reviews:", "餐厅评论的Key"),
package com.itkaka.restaurants.mapper;
import com.itkaka.restaurants.model.pojo.Reviews;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
/**
* 评论服务 Mapper
*/
public interface ReviewsMapper {
// 新增餐厅评论
@Insert("INSERT INTO t_reviews (fk_restaurant_id, fk_diner_id, content, like_it, is_valid, create_date, update_date) " +
" VALUES (#{fkRestaurantId}, #{fkDinerId}, #{content}, #{likeIt}, 1, NOW(), NOW())")
@Options(useGeneratedKeys = true, keyProperty = "id")
int save(Reviews reviews);
}
/**
* 评论服务 Service
*/
@Service
public class ReviewsService {
@Value("${service.name.fs_oauth-server}")
private String oauthServerName;
@Value("${service.name.fs_diners-server}")
private String dinersServerName;
@Resource
private RestTemplate restTemplate;
@Resource
private RedisTemplate redisTemplate;
@Resource
private RestaurantsService restaurantsService;
@Resource
private ReviewsMapper reviewsMapper;
private static final int START = 0;
private static final int END = 9;
//新增餐厅评论 likeId 0不喜欢 1喜欢
public void addReview(Integer restaurantId,String accessToken,
String content,int likeIt){
// 参数校验
AssertUtil.isTrue(restaurantId == null || restaurantId < 1,
"请选择评论的餐厅");
AssertUtil.isNotEmpty(content, "请输入评论内容");
AssertUtil.isTrue(content.length() > 800, "评论内容过长,请重新输入");
// 判断餐厅是否存在
Restaurants restaurants = restaurantsService.findById(restaurantId);
AssertUtil.isTrue(restaurantId == null, "该餐厅不存在");
// 获取登录用户信息
SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
// 插入数据库
Reviews reviews = new Reviews();
reviews.setContent(content);
reviews.setFkRestaurantId(restaurantId);
reviews.setFkDinerId(dinerInfo.getId());
// 这里需要后台操作处理餐厅数据(喜欢/不喜欢)做自增处理
reviews.setLikeIt(likeIt);
int count = reviewsMapper.save(reviews);
if (count == 0){
return;
}
// 写入餐厅最新缓存评论信息
String key = RedisKeyConstant.restaurant_new_reviews.getKey() + restaurantId;
redisTemplate.opsForList().leftPush(key, reviews);
// 修剪餐厅最新缓存评论信息
redisTemplate.opsForList().trim(key, START, END);
}
// 获取登录用户信息
private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
// 是否有 accessToken
AssertUtil.mustLogin(accessToken);
// 拼接远程请求 url
String url = oauthServerName + "user/me?access_token={accessToken}";
// 发送请求
ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
}
SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(), new SignInDinerInfo(), false);
return dinerInfo;
}
}
package com.itkaka.restaurants.controller;
import com.itkaka.commons.model.domain.ResultInfo;
import com.itkaka.commons.utils.ResultInfoUtil;
import com.itkaka.restaurants.model.vo.ReviewVO;
import com.itkaka.restaurants.service.ReviewsService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 评论服务 Controller
*/
@RestController
@RequestMapping("reviews")
public class ReviewsController {
@Resource
private ReviewsService reviewsService;
@Resource
private HttpServletRequest request;
// 新增餐厅评论
@PostMapping("{restaurantId}")
public ResultInfo<String> addReview(@PathVariable Integer restaurantId,
String access_token,
@RequestParam("content") String content,
@RequestParam("likeIt") int likeIt) {
reviewsService.addReview(restaurantId, access_token, content, likeIt);
return ResultInfoUtil.buildSuccess(request.getServletPath(), "添加成功");
}
}
测试
Postman测试
访问:http://localhost/restaurants/reviews/1?access_token=116af711-645c-4ce9-bc32-cd304bdda020
package com.itkaka.restaurants.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.itkaka.commons.constant.ApiConstant;
import com.itkaka.commons.constant.RedisKeyConstant;
import com.itkaka.commons.exception.ParameterException;
import com.itkaka.commons.model.domain.ResultInfo;
import com.itkaka.commons.model.vo.ShortDinerInfo;
import com.itkaka.commons.model.vo.SignInDinerInfo;
import com.itkaka.commons.utils.AssertUtil;
import com.itkaka.restaurants.mapper.ReviewsMapper;
import com.itkaka.restaurants.model.pojo.Restaurants;
import com.itkaka.restaurants.model.pojo.Reviews;
import com.itkaka.restaurants.model.vo.ReviewVO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 评论服务 Service
*/
@Service
public class ReviewsService {
@Value("${service.name.fs_oauth-server}")
private String oauthServerName;
@Value("${service.name.fs_diners-server}")
private String dinersServerName;
@Resource
private RestTemplate restTemplate;
@Resource
private RedisTemplate redisTemplate;
@Resource
private RestaurantsService restaurantsService;
@Resource
private ReviewsMapper reviewsMapper;
private static final int START = 0;
private static final int END = 9;
//新增餐厅评论 likeId 0不喜欢 1喜欢
public void addReview(Integer restaurantId,String accessToken,
String content,int likeIt){
// 参数校验
AssertUtil.isTrue(restaurantId == null || restaurantId < 1,
"请选择评论的餐厅");
AssertUtil.isNotEmpty(content, "请输入评论内容");
AssertUtil.isTrue(content.length() > 800, "评论内容过长,请重新输入");
// 判断餐厅是否存在
Restaurants restaurants = restaurantsService.findById(restaurantId);
AssertUtil.isTrue(restaurantId == null, "该餐厅不存在");
// 获取登录用户信息
SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
// 插入数据库
Reviews reviews = new Reviews();
reviews.setContent(content);
reviews.setFkRestaurantId(restaurantId);
reviews.setFkDinerId(dinerInfo.getId());
// 这里需要后台操作处理餐厅数据(喜欢/不喜欢)做自增处理
reviews.setLikeIt(likeIt);
int count = reviewsMapper.save(reviews);
if (count == 0){
return;
}
// 写入餐厅最新缓存评论信息
String key = RedisKeyConstant.restaurant_new_reviews.getKey() + restaurantId;
redisTemplate.opsForList().leftPush(key, reviews);
// 修剪餐厅最新缓存评论信息
redisTemplate.opsForList().trim(key, START, END);
}
// 获取登录用户信息
private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
// 是否有 accessToken
AssertUtil.mustLogin(accessToken);
// 拼接远程请求 url
String url = oauthServerName + "user/me?access_token={accessToken}";
// 发送请求
ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
}
SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(), new SignInDinerInfo(), false);
return dinerInfo;
}
// 查询餐厅最新评论
public List<ReviewVO> findNewReviews(Integer restaurantId,String accessToken){
// 参数校验
AssertUtil.isTrue(restaurantId == null || restaurantId < 1,
"请选择餐厅进行查看");
// 构建 Redis Key
String key = RedisKeyConstant.restaurant_new_reviews.getKey() + restaurantId;
// 拿前十条
List<LinkedHashMap> reviews = redisTemplate.opsForList().range(key,START,END);
// 初始化 VO 集合
List<ReviewVO> reviewsVOS = Lists.newArrayList();
// 初始化食客 ID 集合
List<Integer> dinerIds = Lists.newArrayList();
// 循环处理评论集合
reviews.forEach(review -> {
ReviewVO reviewsVO = BeanUtil.fillBeanWithMap(review, new ReviewVO(), false);
reviewsVOS.add(reviewsVO);
dinerIds.add(reviewsVO.getFkDinerId());
});
// 完善食客昵称和头像
ResultInfo resultInfo = restTemplate.getForObject(dinersServerName +
"findByIds?access_token={accessToken}&ids={ids}", ResultInfo.class,
accessToken, StrUtil.join(",", dinerIds));
if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
}
List<LinkedHashMap> dinerInfoMaps = (List<LinkedHashMap>) resultInfo.getData();
Map<Integer, ShortDinerInfo> dinerInfos = dinerInfoMaps.stream()
.collect(Collectors.toMap(
diner -> (Integer) diner.get("id"),
diner -> BeanUtil.fillBeanWithMap(diner, new ShortDinerInfo(), false)
));
// 循环处理 VO 集合插入食客信息
reviewsVOS.forEach(review -> {
ShortDinerInfo dinerInfo = dinerInfos.get(review.getFkDinerId());
if (dinerInfo != null) {
review.setDinerInfo(dinerInfo);
}
});
return reviewsVOS;
}
}
package com.itkaka.restaurants.controller;
import com.itkaka.commons.model.domain.ResultInfo;
import com.itkaka.commons.utils.ResultInfoUtil;
import com.itkaka.restaurants.model.vo.ReviewVO;
import com.itkaka.restaurants.service.ReviewsService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 评论服务 Controller
*/
@RestController
@RequestMapping("reviews")
public class ReviewsController {
@Resource
private ReviewsService reviewsService;
@Resource
private HttpServletRequest request;
// 新增餐厅评论
@PostMapping("{restaurantId}")
public ResultInfo<String> addReview(@PathVariable Integer restaurantId,
String access_token,
@RequestParam("content") String content,
@RequestParam("likeIt") int likeIt) {
reviewsService.addReview(restaurantId, access_token, content, likeIt);
return ResultInfoUtil.buildSuccess(request.getServletPath(), "添加成功");
}
// 获取餐厅最新评论
@GetMapping("{restaurantId}/news")
public ResultInfo<List<ReviewVO>> findNewReviews(@PathVariable Integer restaurantId,
String access_token) {
List<ReviewVO> reviewsVOList = reviewsService.findNewReviews(restaurantId, access_token);
return ResultInfoUtil.buildSuccess(request.getServletPath(), reviewsVOList);
}
}
测试:
访问:http://localhost/restaurants/reviews/1/news?access_token=116af711-645c-4ce9-bc32-cd304bdda020
:::info
最新餐厅评论 这个功能中我们实现了添加餐厅评论、获取餐厅评论功能。 这个功能中 Redis 主要用于存储餐厅评论信息,使用了 List 数据类型
:::