Redis 如何實現點贊、取消點贊

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點贊是個頻率比較高的事件,也不是特別重要的記錄,使用緩存來存儲還是比較合理的,另外像排行榜、熱議等都可以使用緩存,用戶發起點贊、取消點贊後先存入 Redis 中,再每隔兩小時從 Redis 讀取點贊數據寫入數據庫中做持久化存儲。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"點贊、取消點贊是高頻次的操作,若每次都讀寫數據庫,大量的操作會影響數據庫性能,所以需要做緩存。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至於多久從 Redis 取一次數據存到數據庫中,根據項目的實際情況定吧,暫時設兩個小時。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"項目需求需要查看都誰點讚了,所以要存儲每個點讚的點贊人、被點贊人,不能簡單的做計數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"文章分四部分介紹:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 緩存設計及實現","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據庫設計","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據庫操作","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開啓定時任務持久化存儲到數據庫","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、Redis 緩存設計及實現","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Redis 與 SpringBoot 項目的整合","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在 pom.xml 中引入依賴","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n org.springframework.boot\n spring-boot-starter-data-redis\n","attrs":{}}]},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"在啓動類上添加註釋 @EnableCaching","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@SpringBootApplication\n@EnableDiscoveryClient\n@EnableSwagger2\n@EnableFeignClients(basePackages = \"com.solo.coderiver.project.client\")\n@EnableCaching\npublic class UserApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(UserApplication.class, args);\n }\n}","attrs":{}}]},{"type":"numberedlist","attrs":{"start":3,"normalizeStart":3},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"編寫 Redis 配置類 RedisConfig","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import com.fasterxml.jackson.annotation.JsonAutoDetect;\nimport com.fasterxml.jackson.annotation.PropertyAccessor;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;\nimport java.net.UnknownHostException;\n\n@Configuration\npublic class RedisConfig {\n\n @Bean\n @ConditionalOnMissingBean(name = \"redisTemplate\")\n public RedisTemplate redisTemplate(\n RedisConnectionFactory redisConnectionFactory)\n throws UnknownHostException {\n\n Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);\n ObjectMapper om = new ObjectMapper();\n om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);\n om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);\n jackson2JsonRedisSerializer.setObjectMapper(om);\n\n RedisTemplate template = new RedisTemplate();\n template.setConnectionFactory(redisConnectionFactory);\n template.setKeySerializer(jackson2JsonRedisSerializer);\n template.setValueSerializer(jackson2JsonRedisSerializer);\n template.setHashKeySerializer(jackson2JsonRedisSerializer);\n template.setHashValueSerializer(jackson2JsonRedisSerializer);\n template.afterPropertiesSet();\n return template;\n }\n\n @Bean\n @ConditionalOnMissingBean(StringRedisTemplate.class)\n public StringRedisTemplate stringRedisTemplate(\n RedisConnectionFactory redisConnectionFactory)\n throws UnknownHostException {\n StringRedisTemplate template = new StringRedisTemplate();\n template.setConnectionFactory(redisConnectionFactory);\n return template;\n }\n \n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Redis 的數據結構類型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 可以存儲鍵與 5 種不同數據結構類型之間的映射,這 5 種數據結構類型分別爲 String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面來對這 5 種數據結構類型作簡單的介紹:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/80/805d960547e6e612dcaa57e8da0949e4.webp","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"點贊數據在 Redis 中的存儲格式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用 Redis 存儲兩種數據,一種是記錄點贊人、被點贊人、點贊狀態的數據,另一種是每個用戶被點讚了多少次,做個簡單的計數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"由於需要記錄點贊人和被點贊人,還有點贊狀態(點贊、取消點贊),還要固定時間間隔取出 Redis 中所有點贊數據,分析了下 Redis 數據格式中 Hash 最合適。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲 Hash 裏的數據都是存在一個鍵裏,可以通過這個鍵很方便的把所有的點贊數據都取出。這個鍵裏面的數據還可以存成鍵值對的形式,方便存入點贊人、被點贊人和點贊狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設點贊人的 id 爲 likedPostId,被點贊人的 id 爲 likedUserId ,點贊時狀態爲 1,取消點贊狀態爲 0。將點贊人 id 和被點贊人 id 作爲鍵,兩個 id 中間用 :: 隔開,點贊狀態作爲值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以如果用戶點贊,存儲的鍵爲:likedUserId::likedPostId,對應的值爲 1 。取消點贊,存儲的鍵爲:likedUserId::likedPostId,對應的值爲 0 。取數據時把鍵用 :: 切開就得到了兩個 id,也很方便。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在可視化工具 RDM 中看到的是這樣子","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/be/bed12a2ba1593748dd53cd7fc126946a.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0e/0e0afd80a4b5c083e7fe4b3bd7bc7b62.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"操作 Redis","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將具體操作方法封裝到了 RedisService 接口裏","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RedisService.java","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import com.solo.coderiver.user.dataobject.UserLike;\nimport com.solo.coderiver.user.dto.LikedCountDTO;\nimport java.util.List;\n\npublic interface RedisService {\n\n /**\n * 點贊。狀態爲1\n * @param likedUserId\n * @param likedPostId\n */\n void saveLiked2Redis(String likedUserId, String likedPostId);\n\n /**\n * 取消點贊。將狀態改變爲0\n * @param likedUserId\n * @param likedPostId\n */\n void unlikeFromRedis(String likedUserId, String likedPostId);\n\n /**\n * 從Redis中刪除一條點贊數據\n * @param likedUserId\n * @param likedPostId\n */\n void deleteLikedFromRedis(String likedUserId, String likedPostId);\n\n /**\n * 該用戶的點贊數加1\n * @param likedUserId\n */\n void incrementLikedCount(String likedUserId);\n\n /**\n * 該用戶的點贊數減1\n * @param likedUserId\n */\n void decrementLikedCount(String likedUserId);\n\n /**\n * 獲取Redis中存儲的所有點贊數據\n * @return\n */\n List getLikedDataFromRedis();\n\n /**\n * 獲取Redis中存儲的所有點贊數量\n * @return\n */\n List getLikedCountFromRedis();\n\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實現類 RedisServiceImpl.java","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import com.solo.coderiver.user.dataobject.UserLike;\nimport com.solo.coderiver.user.dto.LikedCountDTO;\nimport com.solo.coderiver.user.enums.LikedStatusEnum;\nimport com.solo.coderiver.user.service.LikedService;\nimport com.solo.coderiver.user.service.RedisService;\nimport com.solo.coderiver.user.utils.RedisKeyUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.data.redis.core.Cursor;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.core.ScanOptions;\nimport org.springframework.stereotype.Service;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n@Service\n@Slf4j\npublic class RedisServiceImpl implements RedisService {\n\n @Autowired\n RedisTemplate redisTemplate;\n\n @Autowired\n LikedService likedService;\n\n @Override\n public void saveLiked2Redis(String likedUserId, String likedPostId) {\n String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);\n redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.LIKE.getCode());\n }\n\n @Override\n public void unlikeFromRedis(String likedUserId, String likedPostId) {\n String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);\n redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.UNLIKE.getCode());\n }\n\n @Override\n public void deleteLikedFromRedis(String likedUserId, String likedPostId) {\n String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);\n redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);\n }\n\n @Override\n public void incrementLikedCount(String likedUserId) {\n redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, likedUserId, 1);\n }\n\n @Override\n public void decrementLikedCount(String likedUserId) {\n redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, likedUserId, -1);\n }\n\n @Override\n public List getLikedDataFromRedis() {\n Cursor> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);\n List list = new ArrayList<>();\n while (cursor.hasNext()){\n Map.Entry entry = cursor.next();\n String key = (String) entry.getKey();\n //分離出 likedUserId,likedPostId\n String[] split = key.split(\"::\");\n String likedUserId = split[0];\n String likedPostId = split[1];\n Integer value = (Integer) entry.getValue();\n //組裝成 UserLike 對象\n UserLike userLike = new UserLike(likedUserId, likedPostId, value);\n list.add(userLike);\n //存到 list 後從 Redis 中刪除\n redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);\n }\n return list;\n }\n\n @Override\n public List getLikedCountFromRedis() {\n Cursor> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, ScanOptions.NONE);\n List list = new ArrayList<>();\n while (cursor.hasNext()){\n Map.Entry map = cursor.next();\n //將點贊數量存儲在 LikedCountDT\n String key = (String)map.getKey();\n LikedCountDTO dto = new LikedCountDTO(key, (Integer) map.getValue());\n list.add(dto);\n //從Redis中刪除這條記錄\n redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, key);\n }\n return list;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用到的工具類和枚舉類","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RedisKeyUtils, 用於根據一定規則生成 key","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class RedisKeyUtils {\n\n // 保存用戶點贊數據的key\n public static final String MAP_KEY_USER_LIKED = \"MAP_USER_LIKED\";\n // 保存用戶被點贊數量的key\n public static final String MAP_KEY_USER_LIKED_COUNT = \"MAP_USER_LIKED_COUNT\";\n\n /**\n * 拼接被點讚的用戶id和點讚的人的id作爲key。格式 222222::333333\n * @param likedUserId 被點讚的人id\n * @param likedPostId 點讚的人的id\n * @return\n */\n public static String getLikedKey(String likedUserId, String likedPostId){\n StringBuilder builder = new StringBuilder();\n builder.append(likedUserId);\n builder.append(\"::\");\n builder.append(likedPostId);\n return builder.toString();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LikedStatusEnum 用戶點贊狀態的枚舉類","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"package com.solo.coderiver.user.enums;\n\nimport lombok.Getter;\n\n/**\n * 用戶點讚的狀態\n */\n@Getter\npublic enum LikedStatusEnum {\n LIKE(1, \"點贊\"),\n UNLIKE(0, \"取消點贊/未點贊\");\n\n private Integer code;\n\n private String msg;\n\n LikedStatusEnum(Integer code, String msg) {\n this.code = code;\n this.msg = msg;\n }\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、數據庫設計","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據庫表中至少要包含三個字段:被點贊用戶 id,點贊用戶 id,點贊狀態。再加上主鍵 id,創建時間,修改時間就行了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"建表語句","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"create table `user_like`(\n `id` int not null auto_increment,\n `liked_user_id` varchar(32) not null comment '被點讚的用戶id',\n `liked_post_id` varchar(32) not null comment '點讚的用戶id',\n `status` tinyint(1) default '1' comment '點贊狀態,0取消,1點贊',\n `create_time` timestamp not null default current_timestamp comment '創建時間',\n `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改時間',\n primary key(`id`),\n INDEX `liked_user_id`(`liked_user_id`),\n INDEX `liked_post_id`(`liked_post_id`)\n) comment '用戶點贊表';","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"三、數據庫操作","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作數據庫同樣封裝在接口中","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LikedService","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import com.solo.coderiver.user.dataobject.UserLike;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\n\nimport java.util.List;\n\npublic interface LikedService {\n\n /**\n * 保存點贊記錄\n * @param userLike\n * @return\n */\n UserLike save(UserLike userLike);\n\n /**\n * 批量保存或修改\n * @param list\n */\n List saveAll(List list);\n\n\n /**\n * 根據被點贊人的id查詢點贊列表(即查詢都誰給這個人點贊過)\n * @param likedUserId 被點贊人的id\n * @param pageable\n * @return\n */\n Page getLikedListByLikedUserId(String likedUserId, Pageable pageable);\n\n /**\n * 根據點贊人的id查詢點贊列表(即查詢這個人都給誰點贊過)\n * @param likedPostId\n * @param pageable\n * @return\n */\n Page getLikedListByLikedPostId(String likedPostId, Pageable pageable);\n\n /**\n * 通過被點贊人和點贊人id查詢是否存在點贊記錄\n * @param likedUserId\n * @param likedPostId\n * @return\n */\n UserLike getByLikedUserIdAndLikedPostId(String likedUserId, String likedPostId);\n\n /**\n * 將Redis裏的點贊數據存入數據庫中\n */\n void transLikedFromRedis2DB();\n\n /**\n * 將Redis中的點贊數量數據存入數據庫\n */\n void transLikedCountFromRedis2DB();\n\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LikedServiceImpl 實現類","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import com.solo.coderiver.user.dataobject.UserInfo;\nimport com.solo.coderiver.user.dataobject.UserLike;\nimport com.solo.coderiver.user.dto.LikedCountDTO;\nimport com.solo.coderiver.user.enums.LikedStatusEnum;\nimport com.solo.coderiver.user.repository.UserLikeRepository;\nimport com.solo.coderiver.user.service.LikedService;\nimport com.solo.coderiver.user.service.RedisService;\nimport com.solo.coderiver.user.service.UserService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.List;\n\n@Service\n@Slf4j\npublic class LikedServiceImpl implements LikedService {\n\n @Autowired\n UserLikeRepository likeRepository;\n\n @Autowired\n RedisService redisService;\n\n @Autowired\n UserService userService;\n\n @Override\n @Transactional\n public UserLike save(UserLike userLike) {\n return likeRepository.save(userLike);\n }\n\n @Override\n @Transactional\n public List saveAll(List list) {\n return likeRepository.saveAll(list);\n }\n\n @Override\n public Page getLikedListByLikedUserId(String likedUserId, Pageable pageable) {\n return likeRepository.findByLikedUserIdAndStatus(likedUserId, LikedStatusEnum.LIKE.getCode(), pageable);\n }\n\n @Override\n public Page getLikedListByLikedPostId(String likedPostId, Pageable pageable) {\n return likeRepository.findByLikedPostIdAndStatus(likedPostId, LikedStatusEnum.LIKE.getCode(), pageable);\n }\n\n @Override\n public UserLike getByLikedUserIdAndLikedPostId(String likedUserId, String likedPostId) {\n return likeRepository.findByLikedUserIdAndLikedPostId(likedUserId, likedPostId);\n }\n\n @Override\n @Transactional\n public void transLikedFromRedis2DB() {\n List list = redisService.getLikedDataFromRedis();\n for (UserLike like : list) {\n UserLike ul = getByLikedUserIdAndLikedPostId(like.getLikedUserId(), like.getLikedPostId());\n if (ul == null){\n //沒有記錄,直接存入\n save(like);\n }else{\n //有記錄,需要更新\n ul.setStatus(like.getStatus());\n save(ul);\n }\n }\n }\n\n @Override\n @Transactional\n public void transLikedCountFromRedis2DB() {\n List list = redisService.getLikedCountFromRedis();\n for (LikedCountDTO dto : list) {\n UserInfo user = userService.findById(dto.getId());\n //點贊數量屬於無關緊要的操作,出錯無需拋異常\n if (user != null){\n Integer likeNum = user.getLikeNum() + dto.getCount();\n user.setLikeNum(likeNum);\n //更新點贊數量\n userService.updateInfo(user);\n }\n }\n }\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"四、開啓定時任務持久化存儲到數據庫","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定時任務 Quartz 很強大,就用它了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Quartz 使用步驟:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、 添加依賴","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n org.springframework.boot\n spring-boot-starter-quartz\n\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、 編寫配置文件","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"package com.solo.coderiver.user.config;\n\nimport com.solo.coderiver.user.task.LikeTask;\nimport org.quartz.*;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class QuartzConfig {\n\n private static final String LIKE_TASK_IDENTITY = \"LikeTaskQuartz\";\n\n @Bean\n public JobDetail quartzDetail(){\n return JobBuilder.newJob(LikeTask.class).withIdentity(LIKE_TASK_IDENTITY).storeDurably().build();\n }\n\n @Bean\n public Trigger quartzTrigger(){\n SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()\n// .withIntervalInSeconds(10) //設置時間週期單位秒\n .withIntervalInHours(2) //兩個小時執行一次\n .repeatForever();\n return TriggerBuilder.newTrigger().forJob(quartzDetail())\n .withIdentity(LIKE_TASK_IDENTITY)\n .withSchedule(scheduleBuilder)\n .build();\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、 編寫執行任務的類繼承自 QuartzJobBean","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"package com.solo.coderiver.user.task;\n\nimport com.solo.coderiver.user.service.LikedService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang.time.DateUtils;\nimport org.quartz.JobExecutionContext;\nimport org.quartz.JobExecutionException;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.scheduling.quartz.QuartzJobBean;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\n/**\n * 點讚的定時任務\n */\n@Slf4j\npublic class LikeTask extends QuartzJobBean {\n\n @Autowired\n LikedService likedService;\n\n private SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n\n @Override\n protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {\n log.info(\"LikeTask-------- {}\", sdf.format(new Date()));\n //將 Redis 裏的點贊信息同步到數據庫裏\n likedService.transLikedFromRedis2DB();\n likedService.transLikedCountFromRedis2DB();\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點贊 / 取消點贊 跟 點贊數 +1/ -1 應該保證是原子操作 , 不然出現併發問題就會有兩條重複的點贊記錄 , 所以要給整個原子操作加鎖 . 同時需要在 Spring Boot 的系統關閉鉤子函數中補充同步 redis 中點贊數據到 mysql 中的過程 . 不然有可能出現距離上一次同步 1 小時 59 分的時候服務器更新 , 把整整兩小時的點贊數據都給清空了 . 如果點贊設計到比較重要活動業務的話這就很尷尬了 .","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章