目的

很多网站都有对于点赞功能的限制,例如一个小时内点多少赞,或者一篇文章点多少次赞,今天就来设计一下这个功能如何实现,先基于Java的数据结构进行开发,后面可拓展到Redis中的数据结构。一小时内点在5次(可自行修改),为了方便测试,本人设计的60s内限制点赞五次


结构

文章ID(passageId)

该字段记录点赞的具体文章ID,因为Id具有唯一性,唯一标识一篇文章,所以对该字段进行记录。

用户ID(uid)

该字段记录点赞用户的ID,同上述文章ID,都具有唯一性,所以记录该字段。

点赞次数(count)

初始化时默认为1,因为该数据结构为点赞一次发生所创建的,默认值为1,后面再次点赞需要对该点赞次数变量进行更改和同步

时间轴(time_history)

这也是该数据结构中比较核心的功能

使用 PriorityQueue<Date> 这个优先队列进行存储

由于我们该点赞是基于该点赞时间点前限制一个小时内只能点赞五次,假如我现在1点20分1点22分1点23分1点30分1点40分,这几个时间点 点了5此赞,这时候我们1点45分再去点一次赞就会打印点不了赞(Sout),但是如果现在是2点25又来点赞,我们发现又可以点了

  • 原来的time_history :

    1点20分—–> 1点22分 —-> 1点23分 —> 1点30分 —> 1点40分

  • 1点45点赞的time_history:

    1点20分—–> 1点22分 —-> 1点23分 —> 1点30分 —> 1点40分(count已经为5,打印不能点赞)

  • 2点25点赞的time_history:

    1点30分 —> 1点40分 —> 2点25分

    原因: (count为3,因为1点20,1点22,1点23已经超出2点25前的一个小时了)

哈希表

我们利用HashMap将上述字段进行存储,格式如下图

7sSOr6.png


代码

初始化结构

将点赞次数设置为0次,若要持久化到Redis中(Springboot网络项目),点赞次数设为1,因为该程序为本地调试

public StarObject(int passageId,int uid) {  //初始化结构,点赞次数为0,若要保存到Redis中,则为1

this.passageId = passageId;
this.uid = uid;
this.starCount = 0;
map.put("starCount",this.starCount);
map.put("passageId",this.passageId);
map.put("uid",this.uid);
map.put("time_history",this.time_history);
}

计算当前时间与时间轴最早时间的差值(以秒为单位)

public long tranverseTime(Date head,Date now) {   //计算当前时间与时间轴的最早时间差值(s)
long sub = now.getTime() - head.getTime();
sub = sub / 1000;
return sub;
}

点赞API

步骤:

  • 首先获取当前时间点now

  • 获得时间轴最早时间peek(可能为空或非空)

  • 这时候分两种情况

    • 当前时间now - 时间轴最早时间peek <= 60s —– 即时间轴最早时间仍在当前时间前60s内

      这时候如果count < 5 —-> 可以点赞

      若count >= 5 —–> 不能点赞

    • 当前时间now - 时间轴最早时间peek > 60s —– 即时间轴最早时间不在当前时间前60s内

      • 我们需要将最早时间进行移除,直到我们优先队列中的第一个元素在当前时间的往前推60s内

        注意: 在peek的过程中,可能为空,空了直接退出

      • 在对count进行判断,小于5可以点赞

      • count >= 5则不能点赞

public void star(){    //点赞API设计
PriorityQueue<Date> time_history = (PriorityQueue<Date>)map.get("time_history");
Integer starCount = (int)map.get("starCount");
Date peek = time_history.peek();
Date now = new Date();
if(peek == null ||tranverseTime(peek,now) <= 60){
if(starCount < 5){
time_history.add(now);
starCount++;
}else{
System.out.println("点赞60s超过5次了……");
return;
}
}else{
while(tranverseTime(peek,now) > 60){
time_history.poll();
starCount--;
peek = time_history.peek();
if(peek == null) break;
}
if(starCount < 5){
time_history.add(now);
starCount++;
}else{
System.out.println("(else)点赞60s超过5次了……");
return;
}
}
map.put("time_history",time_history);
map.put("starCount",starCount);
}

总代码

public class StarObject {
int passageId; //文章ID
int uid; //用户ID
PriorityQueue<Date> time_history = new PriorityQueue<>(); //时间轴
int starCount; //点赞次数(与time_history容量一致)
HashMap map = new HashMap<String,Object>(); //存储上述字段

public StarObject(int passageId,int uid) { //初始化结构,点赞次数为0,若要保存到Redis中,则为1

this.passageId = passageId;
this.uid = uid;
this.starCount = 0;
map.put("starCount",this.starCount);
map.put("passageId",this.passageId);
map.put("uid",this.uid);
map.put("time_history",this.time_history);
}

public long tranverseTime(Date head,Date now){ //计算当前时间与时间轴的最早时间差值(s)
long sub = now.getTime() - head.getTime();
sub = sub / 1000;
return sub;
}


public void star(){ //点赞API设计
PriorityQueue<Date> time_history = (PriorityQueue<Date>)map.get("time_history");
Integer starCount = (int)map.get("starCount");
Date peek = time_history.peek();
Date now = new Date();
if(peek == null ||tranverseTime(peek,now) <= 60){
if(starCount < 5){
time_history.add(now);
starCount++;
}else{
System.out.println("点赞60s超过5次了……");
return;
}
}else{
while(tranverseTime(peek,now) > 60){
time_history.poll();
starCount--;
peek = time_history.peek();
if(peek == null) break;
}
if(starCount < 5){
time_history.add(now);
starCount++;
}else{
System.out.println("(else)点赞60s超过5次了……");
return;
}
}
map.put("time_history",time_history);
map.put("starCount",starCount);
}

}

测试代码

import java.util.Date;
import java.util.PriorityQueue;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

/**
* @author Steven0516
* @create 2022-01-18
*/
public class StarObject {
int passageId;
int uid;
PriorityQueue<Date> time_history = new PriorityQueue<>();
int starCount;
HashMap map = new HashMap<String,Object>();

public StarObject(int passageId,int uid) {
this.passageId = passageId;
this.uid = uid;
this.starCount = 0;
map.put("starCount",this.starCount);
map.put("passageId",this.passageId);
map.put("uid",this.uid);
map.put("time_history",this.time_history);
}

public long tranverseTime(Date head,Date now){
long sub = now.getTime() - head.getTime();
sub = sub / 1000;
return sub;
}


public void star(){
PriorityQueue<Date> time_history = (PriorityQueue<Date>)map.get("time_history");
Integer starCount = (int)map.get("starCount");
Date peek = time_history.peek();
Date now = new Date();
if(peek == null ||tranverseTime(peek,now) <= 60){
if(starCount < 5){
time_history.add(now);
starCount++;
}else{
System.out.println("点赞60s超过5次了……");
return;
}
}else{
while(tranverseTime(peek,now) > 60){
time_history.poll();
starCount--;
peek = time_history.peek();
if(peek == null) break;
}
if(starCount < 5){
time_history.add(now);
starCount++;
}else{
System.out.println("(else)点赞60s超过5次了……");
return;
}
}
map.put("time_history",time_history);
map.put("starCount",starCount);
}

public static void main(String[] args) throws InterruptedException {
StarObject starObject = new StarObject(123,111);
for (int i = 0; i < 6; i++) {
starObject.star();
if(i == 2){
System.out.println("第三次点赞……开始60s⏲");

}
TimeUnit.SECONDS.sleep(1);
}
System.out.println("starCount: " + starObject.map.get("starCount"));
System.out.println("time_history: " + starObject.map.get("time_history"));
new Thread(new TestThread(starObject)).start();



}

}

class TestThread implements Runnable{
StarObject starObject;

TestThread(StarObject starObject){
this.starObject = starObject;
}

@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(60);
starObject.star();
System.out.println("(60s后)starCount: " + starObject.map.get("starCount"));
System.out.println("(60s后)time_history: " + starObject.map.get("time_history"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

运行结果:

7sPhl9.png