商铺点赞功能实现及问题

商铺点赞功能实现及问题

Scroll Down

场景描述

用户对喜欢的商家进行点赞。这个问题在很久之前就有相似的部分解决(文章),这篇文章主要记录一下可能遇到的问题及对应的解决想法。

实现思路

20210207165837.png

实现代码

controller

 /**
     * 点赞/取消点赞 店铺
     *
     * @param shopId 店铺id
     * @return 结果
     */
    @PostMapping("/like")
    public Result like(@RequestParam("shopId") Long shopId){
        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        //异步到redis中点赞或者取消点赞
        likeShopService.like(shopId,userId);
        return Result.success();
    }

    /**
     * 获取点赞数量
     *
     * @param shopId 店铺id
     * @return 结果
     */
    @GetMapping("/likeSum/{shopId}")
    public Result likeSum(@PathVariable("shopId") Long shopId){
        Integer likeSum = redisCache.getCacheObject(RedisKeyUtil.getShopLikeKey(Math.toIntExact(shopId)));
        return likeSum == null ? Result.success(0) : Result.success(likeSum);
    }


    /**
     * 获取点赞状态
     *
     * @param shopId 店铺id
     * @return 结果
     */
    @GetMapping("/isMember/{shopId}")
    public Result isMember(@PathVariable("shopId") Long shopId){
        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        boolean isMember = redisCache.isMember(RedisKeyUtil.getShopUserLikeKey(Math.toIntExact(shopId)), userId);
        return Result.success(isMember);
    }

serviceImpl

 @Override
    public void like(Long shopId, Long userId) {
        //redis执行器异步
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                //获取是点赞还是取消点赞(是否包含key value都符合的值)
                Boolean isMember = redisCache.isMember(RedisKeyUtil.getShopUserLikeKey(Math.toIntExact(shopId)), userId);
                //开启事务
                redisOperations.multi();
                //点赞的话就新增数据到redis,取消的话就删除数据
                //点赞的话店铺获赞数量自增,取消的话店铺获赞数量自减
                if(isMember){
                    redisOperations.opsForSet().remove(RedisKeyUtil.getShopUserLikeKey(Math.toIntExact(shopId)), userId);
                    redisOperations.opsForValue().decrement(RedisKeyUtil.getShopLikeKey(Math.toIntExact(shopId)));
                }else{
                    redisOperations.opsForSet().add(RedisKeyUtil.getShopUserLikeKey(Math.toIntExact(shopId)),userId);
                    redisOperations.opsForValue().increment(RedisKeyUtil.getShopLikeKey(Math.toIntExact(shopId)));
                }
                //提交事务
                return redisOperations.exec();
            }
        });
    }

存在的问题

1、数据查询不灵活,存储在redis中的数据,在统计分析方面进行复杂查询时不如sql更灵活
2、数据可靠性底,持久半持久化存储也得考虑相应的效率问题。
3、点赞通知应该如何实现?

目前解决思路

1、想通过quartz在闲时定时同步数据到mysql中,这里需要使用增量同步。
2、 Ajax轮询?但我看有人用kafka的,毕竟kafka在处理大吞吐量有优势,这个有时间自己尝试实现下看看再更新。

2021-08-25更新
暂时用websocket推消息吧,后续正好加上聊天。
注册websocket

@Configuration
public class WebSocketConfig {
    /**
     * 注册websocket的endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

Service

@ServerEndpoint("/webSocket/{userId}")
@Component
public class WebSocketService {
    /**
     * 存放每个客户端的websocket对象,线程安全的map存放
     */
    private static ConcurrentHashMap<Long, Session> webSocketPools = new ConcurrentHashMap<>();
    /**
     * 当前在线人数
     */
    private static AtomicInteger onLineNum = new AtomicInteger();

    /**
     * websocket创建连接时调用
     *
     * @param session
     * @param userId
     */
    @OnOpen
    public void onOpen(@PathParam("userId") Long userId, Session session) {
        webSocketPools.put(userId, session);
        //在线总数加1
        addOnLineCount();
    }

    /**
     * 发送消息
     *
     * @param info
     */
    @OnMessage
    public void onMessage(String info) {
        Message msg = JSON.parseObject(info, Message.class);
        sendInfo(msg.getToUserId(), msg.getText());
    }

    /**
     * websocket断开连接
     */
    @OnClose
    public void onClose(@PathParam("userId") Long userId) {
        webSocketPools.remove(userId);
        //在线总数减1
        subOnLineCount();
    }

    /**
     * 错误调用
     *
     * @param throwable
     */
    @OnError
    public void onError(Throwable throwable) {
        throwable.printStackTrace();
    }

    /**
     * 发送给消息 对一
     *
     * @param toUserId
     * @param text
     */
    public void sendInfo(Long toUserId, String text) {
        Session session = webSocketPools.get(toUserId);
        try {
            if (session != null) {
                synchronized (session) {
                    session.getBasicRemote().sendText(text);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

之前的定时同步数据到mysql的话,先暂时开启redis的AOF吧。