正在显示
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 | +} |
-
请 注册 或 登录 后发表评论