现实场景有这样一个需求:
某工作大厅,有几百个工作人员,当有某个工作人员向离岗出去,要点击自己工位上的暂离按钮,回岗的时候点击工位上的回岗按钮,如果离岗超过20分钟还没有回岗则短信提醒该工作人员离岗超时,该回岗了!
我们需要怎么处理这个业务呢?
首先要有一个缓存Map,存放工作人员的userid和超时时间。
然后针对该Map提供数据删除和添加的方法,回岗就删除,离岗就添加。
最后再起一个线程一直轮询该Map,当前时间和Map中的超时时间做对比,如果当前时间在超时时间之后,那这个人就是超时了,然后进行短信提醒。
先看一下这个缓存的Map,我这里写了一个工具类:
/**
* 检测用户离岗是否超时工具类
* @author Run the ant
* @date 2018年8月2日
* @version 1.0
*/
@Service
public class BackJobCheckUtil {
private final static Logger logger = LoggerFactory.getLogger(BackJobCheckUtil.class);
public static final String NAMESPACE = "backjobcheck";
public static final String DATAKEY = "datakey";
@Autowired
private BackJobCheckTask backJobCheckTask;
@Resource(name = "cache")
private BaseCache<HashMap<String, Date>> datecache;
/**
* 确认是否有人员暂离超时(超时发送短信,但是只会发送一次)
*
* @author Run the ant
* @data 2018年8月2日
*/
public void check () {
HashMap<String, Date> data = getDataCache();
if (data.size() == IConstant.ZERO) {
//当查询到缓存中没有数据的时候则中断该任务
backJobCheckTask.interrupt();
}
Date now = DateUtil.getCurrentDate();
List<String> uids = new ArrayList<String>();
for (Entry<String, Date> entry : data.entrySet()) {
Date date = entry.getValue();
//如果离岗时间超出限制则短信提醒
if (now.after(date)) {
//短信提醒业务
}
}
if (uids.size() > 0) {
for (String uid : uids) {
data.remove(uid);
}
datecache.put(NAMESPACE, DATAKEY, data);
}
}
/**
* 添加一条人员离岗记录
* @param uid
* @author Run the ant
* @data 2018年8月2日
*/
public synchronized void addUserOverTime (String uid) {
Integer overmin = 20;
Date overtime = new Date(new Date().getTime() + overmin * 60 * 1000);
HashMap<String, Date> data = getDataCache();
data.put(uid, overtime);
datecache.put(NAMESPACE, DATAKEY, data);
//唤醒
backJobCheckTask.build();
}
/**
* 删除一条人员离岗记录
* @param uid
* @author Run the ant
* @data 2018年8月2日
*/
public synchronized void delUserOverTime (String uid) {
HashMap<String, Date> data = getDataCache();
data.remove(uid);
datecache.put(NAMESPACE, DATAKEY, data);
}
/**
* 获取缓存中的人员超时时间数据
* @return
* @author Run the ant
* @data 2018年8月2日
*/
private HashMap<String, Date> getDataCache () {
//针对自己的缓存修改代码,业务都是这样,从缓存取出我的Map数据
HashMap<String, Date> data = datecache.get(NAMESPACE, DATAKEY);
if (data == null) {
data = new HashMap<String, Date>();
datecache.put(NAMESPACE, DATAKEY, data);
}
return data;
}
}
这里有BackJobCheckTask这个类,这个是线程执行的类,贴代码:
/**
* 检测用户离岗是否超时任务
* @author Run the ant
* @date 2018年7月31日
* @version 1.0
*/
@Service
public class BackJobCheckTask implements Runnable {
@Autowired
private BackJobCheckUtil backJobCheckUtil;
private ReentrantLock lock = new ReentrantLock();
private final static Logger logger = LoggerFactory.getLogger(BackJobCheckTask.class);
private Thread tempThread;
private final static String name = "检测用户离岗是否超时任务";
@Override
public void run() {
logger.info("线程【{}】开始运行", tempThread.getName());
try {
while (true) {
backJobCheckUtil.check();
tempThread.sleep(3000);
}
} catch (Exception e) {
logger.error(name + "异常",e);
}
logger.info("线程【{}】停止运行", tempThread.getName());
}
/**
* 检测线程是否启动,未启动则启动线程
*
* @author Run the ant
* @data 2018年7月31日
*/
private void runThread(){
if(tempThread == null || tempThread.isAlive() == false){
try {
lock.lock();
if(tempThread == null || tempThread.isAlive() == false){
tempThread = new Thread(this,name);
tempThread.start();
}
} catch (Exception e) {
logger.error("发布者{}线程启动失败", name,e);
}finally{
lock.unlock();
}
}
}
/**
* 线程中断
*
* @author Run the ant
* @data 2018年7月31日
*/
public void interrupt(){
if(tempThread != null){
tempThread.interrupt();
}
}
/**
* 线程唤醒
*
* @author Run the ant
* @data 2018年7月31日
*/
public void build(){
runThread();
}
}
然后还需要1个接口,来接收工作人员点击暂离和回岗的消息:
@Controller
@RequestMapping(value={"/servlet"})
public class Servlet {
private final static Logger logger = LoggerFactory.getLogger(Servlet .class);
@Autowired
private BackJobCheckUtil backJobCheckUtil;
/**
* 暂离
* @param request
* @param response
* @param uid 窗口人员uid
* @author Run the ant
* @data
*/
@ResponseBody
@RequestMapping(value = "/windowpause")
public void windowPause (HttpServletRequest request, HttpServletResponse response, String uid) {
//接口认证等等其他业务,自行修改
backJobCheckUtil.addUserOverTime(uid);
}
/**
* 回岗
* @param request
* @param response
* @param uid 窗口人员uid
* @author Run the ant
* @data
@ResponseBody
@RequestMapping(value = "/windownormal")
public void windowNormal (HttpServletRequest request, HttpServletResponse response,
String uid) {
//接口认证等等其他业务,自行修改
backJobCheckUtil.delUserOverTime(uid);
}
}
OK,这样就实现了,当没有人暂离的时候,该线程就自行销毁,如果某个时间点突然有数据加入,线程就重新启动。