前端对应页面,从左至右依次为:评论数、收藏数、分享数、置顶/点赞数:

攻略浏览量

打开模块:trip-article-server,找到:StrategyController,修改其中的/detail接口,添加增加浏览量的方法

StrategyController
@GetMapping("/detail")
public R<Strategy> detail(Long id) {
strategyService.viewnumIncr(id);
return R.ok(strategyService.getById(id));
}

在 StrategyService 接口中定义该方法

StrategyService
/**
* 增加阅读量
*
* @param id 攻略id
*/
void viewnumIncr(Long id);

在 StrategyServiceImpl 类中实现该方法

这里需要在RedisService中添加hash的值增加方法

/**
* 针对 hash key 进行 increment
*
* @param prefix 前缀
* @param hashKey hash key
* @param increment 自增量
* @param suffix 后缀
*/
public void hashIncrement(KeyPrefix prefix, String hashKey, int increment, String... suffix) {
redisTemplate.opsForHash().increment(prefix.fullKey(suffix), hashKey, 1);
}
StrategyServiceImpl
@Service
public class StrategyServiceImpl extends ServiceImpl<StrategyMapper, Strategy> implements StrategyService {

private final RedisService redisService;

public StrategyServiceImpl(RedisService redisService) {
this.redisService = redisService;
}
/**
* 增加阅读量
*
* @param id 攻略id
*/
@Override
public void viewnumIncr(Long id) {
this.statDataIncr("viewnum", id);
}

/**
* hashKey 对应的 value 自增
* @param hashKey hash key
* @param sid 攻略id
*/
private void statDataIncr(String hashKey, Long sid) {
redisService.hashIncrement(StrategyRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP, hashKey, 1, sid + "");
}
}

攻略点赞/置顶

对于一篇攻略文章来说,每个用户每天只能顶一次,且无法取消置顶,当时间过了23:59:59,用户可再次顶。

为此需要维护一个状态,用来判断用户当天是否顶过,同时维护过期时间,实现方案:

  • 使用Redis 的 Map 来存储,以文章ID为 Map 的 key ,其中 hash key 为顶过攻略的用户的ID。

  • Redis Key:STRATEGIES:TOP:sid,sid为该篇攻略的ID,hash key:用户的ID。

  • 当用户顶该篇攻略时,刷新 map 的过期时间(当前时间到23:59:59的时间间隔)。

  • 修改 RedisService 的 hashIncrement 方法

    RedisService
    /**
    * 针对 hash key 进行 increment
    *
    * @param prefix 前缀
    * @param hashKey hash key
    * @param increment 自增量
    * @param suffix 后缀
    */
    public void hashIncrement(KeyPrefix prefix, String hashKey, int increment, String... suffix) {
    Long ret = redisTemplate.opsForHash().increment(prefix.fullKey(suffix), hashKey, 1);
    if (prefix.getTimeout() != null && prefix.getTimeout() > 0 && ret == 1) {
    // 有过期时间,且第一次则设置过期时间,给map设置过期时间
    expire(prefix.fullKey(suffix), prefix.getTimeout(), prefix.getUnit());
    }
    }

    注意,这里当一个用户第一次置顶时 ret 为1,可以设置过期时间, 但是每个用户第一次置顶时都会更新该篇攻略 map 的过期时间,无所谓,只要时间没到23:59:59,map 就不会过期,顶过的用户就不能再顶。

接口信息

路径地址 http://localhost:9000/article/strategies/thumbnumIncr
请求方式 GET
请求参数 sid
返回结果 R { code:””, msg: “”, data: Boolean }

Controller

打开模块:trip-article-server,找到:StrategyController,添加点赞/置顶方法

StrategyController
@RequireLogin
@GetMapping("/thumbnumIncr")
public R<Boolean> thumbnumIncr(Long sid) {
return R.ok(strategyService.thumbnumIncr(sid));
}

Service

找到:StrategyService 接口,定义点赞/置顶方法

StrategyService
/**
* 攻略置顶,一个用户一天只能置顶一篇攻略
*
* @param sid 攻略ID
* @return 置顶状态
*/
Boolean thumbnumIncr(Long sid);

找到:StrategyServiceImpl 类,实现点赞/置顶方法

获取当前时间到23:59:59时间间隔的方法,

DateUtils
public class DateUtils {

public static long getLastMillisSeconds() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime lastSeconds = LocalDateTime.of(now.getYear(), now.getMonth(), now.getDayOfMonth(), 23, 59, 59);
return Duration.between(now, lastSeconds).toMillis();
}

public static Long getLastMillisSecondsOld() {
Calendar instance = Calendar.getInstance();
instance.set(Calendar.HOUR_OF_DAY, 23);
instance.set(Calendar.MINUTE, 59);
instance.set(Calendar.SECOND, 29);
long lastMillis = instance.getTimeInMillis();
return lastMillis - System.currentTimeMillis();

}
}
StrategyServiceImpl
@Service
public class StrategyServiceImpl extends ServiceImpl<StrategyMapper, Strategy> implements StrategyService {

private final RedisService redisService;

public StrategyServiceImpl(RedisService redisService) {
this.redisService = redisService;
}
/**
* 攻略置顶,一个用户一天只能置顶一篇攻略
*
* @param sid 攻略ID
* @return 置顶状态
*/
@Override
public Boolean thumbnumIncr(Long sid) {
LoginUser loginUser = AuthenticationUtil.getLoginUser();
StrategyRedisKeyPrefix keyPrefix = StrategyRedisKeyPrefix.STRATEGIES_TOP_MAP;
String key = keyPrefix.fullKey(sid + "");
String hashKey = loginUser.getId() + "";
// TODO: 并发问题,查询和增加不是原子操作,锁 或者 lua 脚本
Integer count = redisService.getCacheMapValue(key, hashKey);
if (count != null && count > 0) {
return false;
}
// 记录用户的置顶,向攻略map中添加该用户的置顶,并设置攻略置顶map的过期时间
keyPrefix.setTimeout(DateUtils.getLastMillisSeconds());
keyPrefix.setUnit(TimeUnit.MILLISECONDS);
redisService.hashIncrement(keyPrefix, hashKey, 1, sid + "");
// 置顶数+1
this.statDataIncr("thumbsupnum", sid);
return true;
}

/**
* hashKey 对应的 value 自增
* @param hashKey hash key
* @param sid 攻略id
*/
private void statDataIncr(String hashKey, Long sid) {
redisService.hashIncrement(StrategyRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP, hashKey, 1, sid + "");
}
}

攻略收藏量

数据库

攻略的收藏属于用户模块的功能,用户可以查看自己收藏的攻略,因此在trip-user数据库中增加表user_favor_strategies

user_favor_strategies
CREATE TABLE `user_favor_strategies` (
`user_id` bigint NOT NULL,
`strategy_id` bigint NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

Redis Key

找到模块:trip-user-api 中的com.swx.user.redis.key包下的 UserRedisKeyPrefix,添加一个 key

public static final UserRedisKeyPrefix STRATEGIES_STAT_DATA_MAP = new UserRedisKeyPrefix("STRATEGIES:STAT:DATA");

接口信息

获取用户收藏的攻略

路径地址 http://localhost:9000/u/users/favor/strategies
请求方式 GET
请求参数 userId
返回结果 R { code:””, msg: “”, data: List }

添加或者删除攻略收藏

路径地址 http://localhost:9000/u/users/favor/strategies
请求方式 GET
请求参数 sid
返回结果 R { code:””, msg: “”, data: Boolean }

Controller

打开模块:trip-user-server,找到:UserInfoController,添加收藏方法

UserInfoController
@GetMapping("/favor/strategies")
R<List<Long>> getFavorStrategyIdList(@RequestParam Long userId) {
return R.ok(userInfoService.getFavorStrategyIdList(userId));
}

@RequireLogin
@PostMapping("/favor/strategies")
public R<Boolean> favoriteStrategy(Long sid) {
return R.ok(userInfoService.favoriteStrategy(sid));
}

Service

找到:StrategyService 接口,定义获取和添加收藏攻略方法

StrategyService
/**
* 获取用户收藏攻略
*
* @param userId 用户ID
* @return 收藏攻略
*/
List<Long> getFavorStrategyIdList(Long userId);

/**
* 收藏攻略
*
* @param sid 攻略ID
* @return 收藏状态
*/
Boolean favoriteStrategy(Long sid);

找到:StrategyServiceImpl 类,实现获取和添加收藏攻略方法

UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

private final RedisService redisService;

public UserServiceImpl(RedisService redisService, TokenService tokenService) {
this.redisService = redisService;
}

/**
* 获取用户收藏攻略
*
* @param userId 用户ID
* @return 收藏攻略
*/
@Override
public List<Long> getFavorStrategyIdList(Long userId) {
return getBaseMapper().getFavorStrategyIdList(userId);
}

/**
* 收藏攻略
*
* @param sid 攻略ID
* @return 收藏状态
*/
@Override
public Boolean favoriteStrategy(Long sid) {
LoginUser loginUser = AuthenticationUtil.getLoginUser();
List<Long> ids = this.getFavorStrategyIdList(loginUser.getId());
if (ids.contains(sid)) {
redisService.hashIncrement(UserRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP, "favornum", -1, sid + "");
return getBaseMapper().deleteFavorStrategy(loginUser.getId(), sid);
}
redisService.hashIncrement(UserRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP, "favornum", 1, sid + "");
return getBaseMapper().insertFavorStrategy(loginUser.getId(), sid);
}
}

攻略评论数量

Controller

打开模块:trip-comment-server,找到:StrategyCommentController,修改/save方法,添加增加评论数量方法

StrategyCommentController
@RequireLogin
@PostMapping("/save")
public R<?> saveComment(Long strategyId, String strategyTitle) {
strategyCommentService.save(strategyId, strategyTitle);
// 评论数+1
strategyCommentService.replyNumIncr(strategyId);
return R.ok();
}

Service

找到:StrategyCommentService 接口,定义增加评论数量方法

StrategyCommentService
/**
* 评论数+1
*
* @param strategyId 攻略ID
*/
void replyNumIncr(Long strategyId);

找到:StrategyCommentServiceImpl 类,实现增加评论数量方法

StrategyCommentServiceImpl
@Slf4j
@Service
public class StrategyCommentServiceImpl implements StrategyCommentService {

private final RedisService redisService;

public StrategyCommentServiceImpl(RedisService redisService) {
this.redisService = redisService;
}

/**
* 评论数+1
*
* @param strategyId 攻略ID
*/
@Override
public void replyNumIncr(Long strategyId) {
redisService.hashIncrement(CommentRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP, "replynum", 1, strategyId + "");
}
}