当前位置:网站首页>16、热帖排行
16、热帖排行
2022-07-31 02:36:00 【A snicker】
1、帖子分数与时间有关,而且点赞,收藏等操作是非常高频的,一般是通过定时任务计算帖子分数(分布式定时任务 Spring Quartz)
2、把分数变化的帖子放进缓存里,定时时间到对缓存里的帖子进行计算即可(Redis)
1、将需要计算分数的帖子放入Redis中
1、生成RedisKey
2、影响帖子分数操作发生时,将帖子放入Redis
RedisKeyUtil.java
// 帖子分数
public static String getPostScoreKey() {
return PREFIX_POST + SPLIT + "score";
}
DiscussPostController.java
A)发新帖时,将帖子id放入Redis(给帖子一个初始分数):RedisKey:postId 存放在Set里。
@PostMapping("/add")
@ResponseBody
public String addDiscussPost(String title, String content) {
...
// 计算帖子分数
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, post.getId());
}
B)加精、评论、点赞操作时,也将帖子放入Redis
// 加精 HomeController.java
@PostMapping("/wonderful")
@ResponseBody
public String setWonderful(int id) {
discussPostService.updateStatus(id, 1);
...
// 计算帖子分数
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, id);
}
// 评论 CommentController.java
@PostMapping("/add/{discussPostId}")
public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
if (comment.getEntityType() == ENTITY_TYPE_POST) {
...
// 计算帖子分数
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, discussPostId);
}
2、使用Quartz实现定时任务
1、Job接口实现
quartz包下 PostScoreRefreshJob 类
public class PostScoreRefreshJob implements Job, CommunityConstant {
private static final Logger LOGGER = LoggerFactory.getLogger(PostScoreRefreshJob.class);
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private DiscussPostService discussPostService;
@Autowired
private LikeService likeService;
@Autowired
private ElasticsearchService elasticsearchService;
private static final Date EPOCH;
static {
try {
EPOCH = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2014-08-01 00:00:00");
} catch (ParseException e) {
throw new RuntimeException("初始化牛客纪元失败!", e);
}
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String redisKey = RedisKeyUtil.getPostScoreKey();
BoundSetOperations operations = redisTemplate.boundSetOps(redisKey);
if (operations.size() == 0) {
LOGGER.info("任务取消,没有需要刷新的帖子!");
return;
}
LOGGER.info("[任务开始] 正在刷新帖子分数:" + operations.size());
while (operations.size() > 0) {
this.refresh((Integer) operations.pop());
}
LOGGER.info("[任务结束] 帖子分数刷新完毕!");
}
private void refresh(int postId) {
DiscussPost post = discussPostService.findDiscussPostById(postId);
if (post == null) {
LOGGER.error("该帖子不存在:id = " + postId);
return;
}
// 是否精华
boolean wonderful = post.getStatus() == 1;
// 评论数量
int commentCount = post.getCommentCount();
// 点赞数量
long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, postId);
// 计算权重
double w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2;
// 分数 = 帖子权重 + 距离天数
double score =
Math.log10(Math.max(w, 1))
+ (post.getCreateTime().getTime() - EPOCH.getTime()) / (1000 * 3600 * 24);
// 更新帖子分数
discussPostService.updateScore(postId, score);
// 同步搜索数据
post.setScore(score);
elasticsearchService.saveDiscussPost(post);
}
}
- 牛客纪元域:private static final Date epoch,由于epoch只需要初始化一次,在静态代码块里面进行初始化
- 使用BoundSetOperations 绑定 Key,BoundSetOperations就是一个绑定key的对象,我们可以通过这个对象来进行与key相关的操作。
- 从Redis里取数据,若没有数据,记录日志后直接返回。若Set为空,对每个postId执行 refresh 方法。
refresh刷新一个帖子的方法:
1、根据帖子id查找post实体
2、判断是否加精、评论数量、点赞数量,计算分数(getTime() 得到毫秒值)
3、更新帖子分数
4、同步搜索数据
5、对Quartz进行配置:时间间隔单位毫秒
对Quartz进行配置
// 配置 -> 数据库 -> 调用
@Configuration
public class QuartzConfig {
// 刷新帖子分数任务
@Bean
public JobDetailFactoryBean postScoreRefreshJobDetail() {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(PostScoreRefreshJob.class);
factoryBean.setName("postScoreRefreshJob");
factoryBean.setGroup("communityJobGroup");
factoryBean.setDurability(true);
factoryBean.setRequestsRecovery(true);
return factoryBean;
}
// 配置Trigger(SimpleTriggerFactoryBean, CronTriggerFactoryBean)
@Bean
public SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail) {
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
factoryBean.setJobDetail(postScoreRefreshJobDetail);
factoryBean.setName("postScoreRefreshTrigger");
factoryBean.setGroup("communityTriggerGroup");
factoryBean.setRepeatInterval(1000 * 60 * 5);
factoryBean.setJobDataMap(new JobDataMap());
return factoryBean;
}
}
3、首页界面处理
1、按照最热对帖子进行排序
对 selectDiscussPosts方法进行重构即可
@Mapper
public interface DiscussPostMapper {
List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit, int orderMode);
// 增加了一个orderMode参数: orderMode=0 时按照“最新” 排序,orderMode=1 时按照“最热” 排序
}
discusspost-mapper.xml
<select id="selectDiscussPosts" resultType="DiscussPost">
select <include refid="selectFields"></include>
from discuss_post
where status != 2
<if test="userId!=0">
and user_id = #{
userId}
</if>
<if test="orderMode==0">
order by type desc, create_time desc
</if>
<if test="orderMode==1">
order by type desc, score desc, create_time desc
</if>
limit #{
offset}, #{
limit}
</select>
2、所有调用 selectDiscussPosts 的地方进行修改
3、HomeController:
从前端获取ordermode参数,并拼入 path 里返回给模板
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model, Page page,
@RequestParam(name = "orderMode", defaultValue = "0") int orderMode) {
// 1.默认orderMode=0,比如访问首页时
// 方法调用钱,SpringMVC会自动实例化Model和Page,并将Page注入Model.
// 所以,在thymeleaf中可以直接访问Page对象中的数据.
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index?orderMode=" + orderMode); // 2.修改Page的路径,加上orderMode参数
// 3.使用重构后的discussPostService
List<DiscussPost> list = discussPostService
.findDiscussPosts(0, page.getOffset(), page.getLimit(), orderMode);
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null) {
for (DiscussPost post : list) {
Map<String, Object> map = new HashMap<>();
map.put("post", post);
User user = userService.findUserById(post.getUserId());
map.put("user", user);
long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());
map.put("likeCount", likeCount);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
model.addAttribute("orderMode", orderMode);
return "/index";
}
4、index.html
1)th:href 超链接修改,传入orderMode 参数
2)由orderMode 参数决定是否有 active 参数
<!-- 筛选条件 -->
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a th:class="|nav-link ${orderMode==0?'active':''}|" th:href="@{/index(orderMode=0)}">最新</a>
</li>
<li class="nav-item">
<a th:class="|nav-link ${orderMode==1?'active':''}|" th:href="@{/index(orderMode=1)}">最热</a>
</li>
</ul>
边栏推荐
- 【Android】Room —— SQLite的替代品
- CV-Model [3]: MobileNet v2
- Brute Force/Adjacency Matrix Breadth First Directed Weighted Graph Undirected Weighted Graph
- Problems that need to be solved by the tcp framework
- 局域网电脑硬件信息收集工具
- LeetCode 1161 最大层内元素和[BFS 二叉树] HERODING的LeetCode之路
- Observer mode (1)
- SQL注入 Less47(报错注入) 和Less49(时间盲注)
- 19. Support Vector Machines - Intuitive Understanding of Optimization Objectives and Large Spacing
- User interaction + formatted output
猜你喜欢
MPPT太阳能充放电控制器数据采集-通过网关采集电池电压容量电量SOC,wifi传输
CMOS和TTL的区别?
There is a problem with the multiplayer-hlap package and the solution cannot be upgraded
StringJoiner详解
Basic learning about Redis related content
The comprehensive result of the case statement, do you know it?[Verilog Advanced Tutorial]
The effective square of the test (one question of the day 7/29)
To write good test cases, you must first learn test design
String为什么不可变?
General introduction to the Unity interface
随机推荐
JS 函数 this上下文 运行时点语法 圆括号 数组 IIFE 定时器 延时器 self.备份上下文 call apply
First acquaintance with C language -- array
leetcode-1161: Maximum in-layer element sum
完整复制虚拟机原理(云计算)
Hanyuan Hi-Tech 8-channel HDMI integrated multi-service high-definition video optical transceiver 8-channel HDMI video + 8-channel two-way audio + 8-channel 485 data + 8-channel E1 + 32-channel teleph
FPGA-based vending machine
MPPT solar charge controller data collection - through the gateway acquisition capacity battery SOC battery voltage, wi-fi
ShardingJDBC基本介绍
Force buckled brush the stairs (7/30)
Introduction to flask series 】 【 flask - using SQLAlchemy
multiplayer-hlap 包有问题,无法升级的解决方案
Android's webview cache related knowledge collection
[1153] The boundary range of between in mysql
基于FPGA的图像实时采集
f.grid_sample
AtCoder Beginner Contest 261 部分题解
BAT can't sell "Medical Cloud": Hospitals flee, mountains stand, and there are rules
try-catch中含return
知识蒸馏7:知识蒸馏代码详解
Nacos