正在显示
5 个修改的文件
包含
297 行增加
和
0 行删除
1 | +package com.tianbo.warehouse.controller; | ||
2 | + | ||
3 | +import com.alibaba.fastjson.JSON; | ||
4 | +import com.alibaba.fastjson.JSONObject; | ||
5 | +import com.tianbo.warehouse.controller.response.ResultJson; | ||
6 | +import com.tianbo.warehouse.dao.KakoUserMapper; | ||
7 | +import com.tianbo.warehouse.model.KakoUser; | ||
8 | +import com.tianbo.warehouse.util.RedisUtils; | ||
9 | +import io.swagger.annotations.ApiOperation; | ||
10 | +import lombok.extern.slf4j.Slf4j; | ||
11 | +import org.apache.commons.lang.StringUtils; | ||
12 | +import org.springframework.beans.factory.annotation.Autowired; | ||
13 | +import org.springframework.web.bind.annotation.PostMapping; | ||
14 | +import org.springframework.web.bind.annotation.RestController; | ||
15 | + | ||
16 | +import javax.annotation.Resource; | ||
17 | +import javax.servlet.http.HttpServletRequest; | ||
18 | +import javax.servlet.http.HttpServletResponse; | ||
19 | +import java.util.Date; | ||
20 | + | ||
21 | +@RestController() | ||
22 | +@Slf4j | ||
23 | +public class HeartBeatController { | ||
24 | + | ||
25 | + @Autowired | ||
26 | + private RedisUtils redisUtils; | ||
27 | + | ||
28 | + @Resource | ||
29 | + private KakoUserMapper kakoUserMapper; | ||
30 | + | ||
31 | + //token头部标识类型,Bearer代表Bearer TOKEN | ||
32 | + static final String AUTHORIZATION_HEADER = "Bearer "; | ||
33 | + | ||
34 | + //检查token时效是否低于标准线 | ||
35 | + static final long TOKEN_TTL_CHECK_MIN= 36000L; | ||
36 | + | ||
37 | + //重置token时效为标准线 | ||
38 | + static final long TOKEN_TTL_ADD= 86400L; | ||
39 | + | ||
40 | + //心跳每次续费时长 | ||
41 | + static final long HEARTBEAT_TOKEN_TTL_ADD= 10L; | ||
42 | + | ||
43 | + @ApiOperation(value = "用户心跳接口", notes = "心跳续期") | ||
44 | + @PostMapping("/heartbeat") | ||
45 | + public ResultJson heartbeat(HttpServletRequest request, HttpServletResponse response){ | ||
46 | + try { | ||
47 | + | ||
48 | + //1. 获取客户端IP,因为有反向代理所以要从头部获取代理过来的头部IP | ||
49 | + String clientIP =null; | ||
50 | + clientIP = request.getRemoteAddr(); | ||
51 | + String header_forwarded = request.getHeader("x-forwarded-for"); | ||
52 | + if (StringUtils.isNotBlank(header_forwarded)) { | ||
53 | + clientIP = request.getHeader("x-forwarded-for"); | ||
54 | + // 多次反向代理后会有多个ip值,第一个ip才是真实ip | ||
55 | + if (clientIP.contains(",")) { | ||
56 | + clientIP = clientIP.split(",")[0]; | ||
57 | + } | ||
58 | + } | ||
59 | + | ||
60 | + //2.获取token | ||
61 | + String token = request.getHeader("Authorization"); | ||
62 | + /** | ||
63 | + * key样式 | ||
64 | + * accessToken:token | ||
65 | + */ | ||
66 | + if (token!=null && !token.isEmpty() && token.startsWith(AUTHORIZATION_HEADER)){ | ||
67 | + token = token.substring(AUTHORIZATION_HEADER.length()); | ||
68 | + String accessToken = token; | ||
69 | + String userDetailStr = redisUtils.get(accessToken); | ||
70 | + | ||
71 | + | ||
72 | + //4. 更新用户心跳时间及在线状态IP等资料 | ||
73 | + if (StringUtils.isNotBlank(userDetailStr)){ | ||
74 | + | ||
75 | + JSONObject u = JSON.parseObject(userDetailStr); | ||
76 | + String userId= u.getString("id"); | ||
77 | + String userInfo = u.getString("name"); | ||
78 | + String username = u.getString("username"); | ||
79 | +// userDetailStr = userDetailStr.replace("@",""); | ||
80 | + | ||
81 | + /**3.续期token过期时间 | ||
82 | + * 增加过期时间,考虑到程序及网络传输中间的时间损耗, | ||
83 | + * 每10秒一个的心跳直接续费10秒的话,token的过期时间还是会随着时间逐步减少 | ||
84 | + */ | ||
85 | + long tokenExpireTime= redisUtils.getExpire(accessToken); | ||
86 | + if(tokenExpireTime < TOKEN_TTL_CHECK_MIN){ | ||
87 | + redisUtils.expire(accessToken, TOKEN_TTL_ADD); | ||
88 | + redisUtils.expire(username, TOKEN_TTL_ADD); | ||
89 | + }else{ | ||
90 | + redisUtils.expire(accessToken,tokenExpireTime+HEARTBEAT_TOKEN_TTL_ADD); | ||
91 | + redisUtils.expire(username, tokenExpireTime+HEARTBEAT_TOKEN_TTL_ADD); | ||
92 | + } | ||
93 | + | ||
94 | + /** | ||
95 | + * 多式联运用户表 | ||
96 | + */ | ||
97 | +// Integer dsly_userId = u.getInteger("id"); | ||
98 | +// USER user = new USER(); | ||
99 | +// user.setId(dsly_userId); | ||
100 | +// user.setLoginip(clientIP); | ||
101 | +// user.setLogintime(new Date()); | ||
102 | +// user.setOnline(true); | ||
103 | +// int ii = userMapper.updateByPrimaryKeySelective(user); | ||
104 | + | ||
105 | + KakoUser kakoUser = new KakoUser(); | ||
106 | + kakoUser.setId(userId); | ||
107 | + kakoUser.setLoginIp(clientIP); | ||
108 | + kakoUser.setLoginDate(new Date()); | ||
109 | + kakoUser.setOnline(true); | ||
110 | + int i = kakoUserMapper.updateByPrimaryKeySelective(kakoUser); | ||
111 | + | ||
112 | + return i > 0 ? new ResultJson("200","心跳成功"): new ResultJson("400","心跳失败"); | ||
113 | + } | ||
114 | + | ||
115 | + } | ||
116 | + return new ResultJson("400","心跳失败"); | ||
117 | + | ||
118 | + }catch (Exception e){ | ||
119 | + log.error("[HEART-BEAT-ERROR]-",e); | ||
120 | + return new ResultJson("400","心跳失败"); | ||
121 | + } | ||
122 | + } | ||
123 | +} |
1 | +package com.tianbo.warehouse.heatbeat; | ||
2 | + | ||
3 | + | ||
4 | +import com.tianbo.warehouse.dao.KakoUserMapper; | ||
5 | +import lombok.extern.slf4j.Slf4j; | ||
6 | +import org.springframework.stereotype.Component; | ||
7 | +import com.tianbo.warehouse.model.KakoUser; | ||
8 | + | ||
9 | +import javax.annotation.PostConstruct; | ||
10 | +import javax.annotation.Resource; | ||
11 | +import java.util.Date; | ||
12 | + | ||
13 | +@Component | ||
14 | +@Slf4j | ||
15 | +public class OfflineTheardJob implements Runnable { | ||
16 | + | ||
17 | + private static OfflineTheardJob offlineTheardJob; | ||
18 | + | ||
19 | + private KakoUser user; | ||
20 | + | ||
21 | + //用户掉线判定时间差 | ||
22 | + static final long OFFLINE_= 60L; | ||
23 | + | ||
24 | + @Resource | ||
25 | + private KakoUserMapper userMapper; | ||
26 | + | ||
27 | + OfflineTheardJob() { | ||
28 | + | ||
29 | + } | ||
30 | + OfflineTheardJob(KakoUser user) { | ||
31 | + this.user = user; | ||
32 | + } | ||
33 | + | ||
34 | + @PostConstruct | ||
35 | + public void init(){ | ||
36 | + offlineTheardJob = this; | ||
37 | + } | ||
38 | + | ||
39 | + @Override | ||
40 | + public void run(){ | ||
41 | + Date userLoginTime = user.getLoginDate(); | ||
42 | + if(userLoginTime!=null){ | ||
43 | + long diff= Math.abs(System.currentTimeMillis() - userLoginTime.getTime()); | ||
44 | + long s = diff / 1000; | ||
45 | + | ||
46 | + log.info("[HEAT-BEAT]-用户{}心跳-时间相差{}秒",user.getName(),s); | ||
47 | + | ||
48 | + | ||
49 | + if (s > OFFLINE_){ | ||
50 | + setOffline(); | ||
51 | + } | ||
52 | + }else { | ||
53 | + setOffline(); | ||
54 | + } | ||
55 | + } | ||
56 | + | ||
57 | + private void setOffline(){ | ||
58 | + user.setOnline(false); | ||
59 | + int i = offlineTheardJob.userMapper.updateByPrimaryKeySelective(user); | ||
60 | + if (i>0){ | ||
61 | + log.info("用户id:{},用户名称:{},从IP:{}掉线",user.getId(),user.getName(),user.getLoginIp()); | ||
62 | + } | ||
63 | + } | ||
64 | +} |
1 | +package com.tianbo.warehouse.heatbeat; | ||
2 | + | ||
3 | +import com.tianbo.warehouse.dao.KakoUserMapper; | ||
4 | +import com.tianbo.warehouse.model.KakoUser; | ||
5 | +import lombok.extern.slf4j.Slf4j; | ||
6 | +import org.springframework.scheduling.annotation.Scheduled; | ||
7 | +import org.springframework.stereotype.Component; | ||
8 | + | ||
9 | +import javax.annotation.Resource; | ||
10 | +import java.util.List; | ||
11 | +import java.util.concurrent.ThreadPoolExecutor; | ||
12 | + | ||
13 | + | ||
14 | +/** | ||
15 | + * 清理心跳超时的在线用户,判定为离线 | ||
16 | + * @author xyh | ||
17 | + * @date | ||
18 | + * 记得给用户ID,用户名称,用户心跳时间,用户登录ip,用户在线状态的数据库字段设置索引。 | ||
19 | + */ | ||
20 | +@Slf4j | ||
21 | +@Component | ||
22 | +public class OfflineUserTask { | ||
23 | + | ||
24 | + | ||
25 | + @Resource | ||
26 | + private KakoUserMapper userMapper; | ||
27 | + | ||
28 | + @Scheduled(fixedRate = 60000) | ||
29 | + private void offlineUserHeartBeat(){ | ||
30 | + | ||
31 | + //初始化线程池 | ||
32 | + ThreadPoolExecutor threadPool = XMLThreadPoolFactory.instance(); | ||
33 | + | ||
34 | + List<KakoUser> userList = userMapper.selectOnlineUser(); | ||
35 | + if (userList!=null && !userList.isEmpty()){ | ||
36 | + log.trace("用户掉线判定开始,共需判定{}个在线标识用户",userList.size()); | ||
37 | + for (KakoUser user:userList) { | ||
38 | + OfflineTheardJob offlineTheardJob = new OfflineTheardJob(user); | ||
39 | + threadPool.execute(offlineTheardJob); | ||
40 | + } | ||
41 | + | ||
42 | + } | ||
43 | + | ||
44 | + } | ||
45 | +} | ||
46 | + |
1 | +package com.tianbo.warehouse.heatbeat; | ||
2 | + | ||
3 | +import java.util.ArrayList; | ||
4 | +import java.util.Date; | ||
5 | +import java.util.Iterator; | ||
6 | +import java.util.List; | ||
7 | +import java.util.concurrent.ThreadFactory; | ||
8 | + | ||
9 | +public class XMLThreadFactory implements ThreadFactory { | ||
10 | + | ||
11 | + private int counter; | ||
12 | + private String name; | ||
13 | + private List<String> stats; | ||
14 | + | ||
15 | + public XMLThreadFactory(String name) | ||
16 | + { | ||
17 | + counter = 1; | ||
18 | + this.name = name; | ||
19 | + stats = new ArrayList<String>(); | ||
20 | + } | ||
21 | + | ||
22 | + @Override | ||
23 | + public Thread newThread(Runnable runnable) | ||
24 | + { | ||
25 | + Thread t = new Thread(runnable, name + "-Thread_" + counter); | ||
26 | + counter++; | ||
27 | + stats.add(String.format("Created thread %d with name %s on %s \n", t.getId(), t.getName(), new Date())); | ||
28 | + return t; | ||
29 | + } | ||
30 | + | ||
31 | + public String getStats() | ||
32 | + { | ||
33 | + StringBuffer buffer = new StringBuffer(); | ||
34 | + Iterator<String> it = stats.iterator(); | ||
35 | + while (it.hasNext()) | ||
36 | + { | ||
37 | + buffer.append(it.next()); | ||
38 | + } | ||
39 | + return buffer.toString(); | ||
40 | + } | ||
41 | + | ||
42 | +} |
1 | +package com.tianbo.warehouse.heatbeat; | ||
2 | + | ||
3 | +import java.util.concurrent.LinkedBlockingQueue; | ||
4 | +import java.util.concurrent.ThreadPoolExecutor; | ||
5 | +import java.util.concurrent.TimeUnit; | ||
6 | + | ||
7 | +public class XMLThreadPoolFactory { | ||
8 | + | ||
9 | + private static ThreadPoolExecutor threadPool; | ||
10 | + | ||
11 | + public static ThreadPoolExecutor instance(){ | ||
12 | + if (threadPool==null){ | ||
13 | + XMLThreadFactory xmlThreadFactory = new XMLThreadFactory("heartbeatTask"); | ||
14 | + threadPool = new ThreadPoolExecutor(12, 64, | ||
15 | + 0L, TimeUnit.MILLISECONDS, | ||
16 | + new LinkedBlockingQueue<Runnable>(1024), | ||
17 | + xmlThreadFactory, | ||
18 | + new ThreadPoolExecutor.AbortPolicy()); | ||
19 | + } | ||
20 | + return threadPool; | ||
21 | + } | ||
22 | +} |
-
请 注册 或 登录 后发表评论