前段时间被领导警告,离开工位(摸鱼)时间太长。于是做了一个能查看自己离开工位时间的项目。
基本技术栈及设施
云服务器一台:用于提供公网服务,方便手机微信访问
编程技术栈:Spring + Boot + Web + thymeleaf 、HTML 、 MySql +、ORM
一、思路
1、PC机器上需要一个APP,检测PC锁屏、解锁。
2、PC锁屏、解锁后储存记录到MySql
3、云服务器部署另一个APP,微信(网页)访问时,输出休息时间。
4、将网页添加到微信收藏、并放到左侧快捷栏
5、休息时,打开快捷页面,访问即可查看当前休息时间
二、实现具体步骤
说明: 编程技术栈不再过多阐述,只讲述关键功能方法
1、检测PC锁屏、解锁
pom文件引用包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>provided</scope>
</dependency>
<!-- jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.12.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.12.1</version>
</dependency>
windows系统锁屏、解锁监听类
package org.dlg.win.system;
import com.sun.jna.platform.win32.*;
import com.sun.jna.platform.win32.DBT.*;
import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.platform.win32.WinUser.HDEVNOTIFY;
import com.sun.jna.platform.win32.WinUser.MSG;
import com.sun.jna.platform.win32.WinUser.WNDCLASSEX;
import com.sun.jna.platform.win32.WinUser.WindowProc;
import lombok.Setter;
/**
* 监听windows用户状态
* OnWindowsLockListener 是个人实现的锁定解锁监听接口
* 需要监听其它状态可以参照实现
*
* The Class Win32WindowTest.
* @author lingui
*/
public class WinState implements WindowProc,Runnable {
/**
* 锁定监听
*/
public interface OnWindowsLockListener{
/**
* 锁定
*/
void onLocked();
/**
* 解锁
*/
void onUnLocked();
}
@Setter
private OnWindowsLockListener listener;
@Override
public void run(){
// define new window class
String windowClass = "MyWindowClass";
HMODULE hInst = Kernel32.INSTANCE.GetModuleHandle("");
WNDCLASSEX wClass = new WNDCLASSEX();
wClass.hInstance = hInst;
wClass.lpfnWndProc = WinState.this;
wClass.lpszClassName = windowClass;
// register window class
User32.INSTANCE.RegisterClassEx(wClass);
getLastError();
// create new window
HWND hWnd = User32.INSTANCE
.CreateWindowEx(
User32.WS_EX_TOPMOST,
windowClass,
"My hidden helper window, used only to catch the windows events",
0, 0, 0, 0, 0,
null, // WM_DEVICECHANGE contradicts parent=WinUser.HWND_MESSAGE
null, hInst, null);
getLastError();
// System.out.println("window sucessfully created! window hwnd: "
// + hWnd.getPointer().toString());
Wtsapi32.INSTANCE.WTSRegisterSessionNotification(hWnd,
Wtsapi32.NOTIFY_FOR_THIS_SESSION);
/* this filters for all device classes */
// DEV_BROADCAST_HDR notificationFilter = new DEV_BROADCAST_HDR();
// notificationFilter.dbch_devicetype = DBT.DBT_DEVTYP_DEVICEINTERFACE;
/* this filters for all usb device classes */
DEV_BROADCAST_DEVICEINTERFACE notificationFilter = new DEV_BROADCAST_DEVICEINTERFACE();
notificationFilter.dbcc_size = notificationFilter.size();
notificationFilter.dbcc_devicetype = DBT.DBT_DEVTYP_DEVICEINTERFACE;
notificationFilter.dbcc_classguid = DBT.GUID_DEVINTERFACE_USB_DEVICE;
/*
* use User32.DEVICE_NOTIFY_ALL_INTERFACE_CLASSES instead of
* DEVICE_NOTIFY_WINDOW_HANDLE to ignore the dbcc_classguid value
*/
HDEVNOTIFY hDevNotify = User32.INSTANCE.RegisterDeviceNotification(
hWnd, notificationFilter, User32.DEVICE_NOTIFY_WINDOW_HANDLE);
getLastError();
if (hDevNotify != null) {
System.out.println("RegisterDeviceNotification was sucessfully!");
}
MSG msg = new MSG();
while (User32.INSTANCE.GetMessage(msg, hWnd, 0, 0) != 0) {
User32.INSTANCE.TranslateMessage(msg);
User32.INSTANCE.DispatchMessage(msg);
}
User32.INSTANCE.UnregisterDeviceNotification(hDevNotify);
Wtsapi32.INSTANCE.WTSUnRegisterSessionNotification(hWnd);
User32.INSTANCE.UnregisterClass(windowClass, hInst);
User32.INSTANCE.DestroyWindow(hWnd);
System.out.println("program exit!");
}
/**
* Instantiates a new win32 window test.
*/
public WinState() {
}
/*
* (non-Javadoc)
*
* @see
* com.sun.jna.platform.win32.User32.WindowProc#callback(com.sun.jna.platform
* .win32.WinDef.HWND, int, com.sun.jna.platform.win32.WinDef.WPARAM,
* com.sun.jna.platform.win32.WinDef.LPARAM)
*/
@Override
public LRESULT callback(HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WinUser.WM_CREATE: {
onCreate(wParam, lParam);
return new LRESULT(0);
}
case WinUser.WM_DESTROY: {
User32.INSTANCE.PostQuitMessage(0);
return new LRESULT(0);
}
case WinUser.WM_SESSION_CHANGE: {
this.onSessionChange(wParam, lParam);
return new LRESULT(0);
}
case WinUser.WM_DEVICECHANGE: {
LRESULT lResult = this.onDeviceChange(wParam, lParam);
return lResult != null ? lResult :
User32.INSTANCE.DefWindowProc(hwnd, uMsg, wParam, lParam);
}
default:
return User32.INSTANCE.DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/**
* Gets the last error.
*
* @return the last error
*/
public final int getLastError() {
int rc = Kernel32.INSTANCE.GetLastError();
if (rc != 0) {
// System.out.println("error: " + rc);
}
return rc;
}
/**
* On session change.
*
* @param wParam
* the w param
* @param lParam
* the l param
*/
protected void onSessionChange(WPARAM wParam, LPARAM lParam) {
switch (wParam.intValue()) {
case Wtsapi32.WTS_CONSOLE_CONNECT: {
this.onConsoleConnect(lParam.intValue());
break;
}
case Wtsapi32.WTS_CONSOLE_DISCONNECT: {
this.onConsoleDisconnect(lParam.intValue());
break;
}
case Wtsapi32.WTS_SESSION_LOGON: {
this.onMachineLogon(lParam.intValue());
break;
}
case Wtsapi32.WTS_SESSION_LOGOFF: {
this.onMachineLogoff(lParam.intValue());
break;
}
case Wtsapi32.WTS_SESSION_LOCK: {
this.onMachineLocked(lParam.intValue());
break;
}
case Wtsapi32.WTS_SESSION_UNLOCK: {
this.onMachineUnlocked(lParam.intValue());
break;
}
default:
}
}
/**
* On console connect.
*
* @param sessionId
* the session id
*/
protected void onConsoleConnect(int sessionId) {
System.out.println("onConsoleConnect: " + sessionId);
}
/**
* On console disconnect.
*
* @param sessionId
* the session id
*/
protected void onConsoleDisconnect(int sessionId) {
System.out.println("onConsoleDisconnect: " + sessionId);
}
/**
* On machine locked.
*
* @param sessionId
* the session id
*/
protected void onMachineLocked(int sessionId) {
System.out.println("onMachineLocked: " + sessionId);
listener.onLocked();
}
/**
* On machine unlocked.
*
* @param sessionId
* the session id
*/
protected void onMachineUnlocked(int sessionId) {
System.out.println("onMachineUnlocked: " + sessionId);
listener.onUnLocked();
}
/**
* On machine logon.
*
* @param sessionId
* the session id
*/
protected void onMachineLogon(int sessionId) {
System.out.println("onMachineLogon: " + sessionId);
}
/**
* On machine logoff.
*
* @param sessionId
* the session id
*/
protected void onMachineLogoff(int sessionId) {
System.out.println("onMachineLogoff: " + sessionId);
}
/**
* On device change.
*
* @param wParam
* the w param
* @param lParam
* the l param
* @return the result. Null if the message is not processed.
*/
protected LRESULT onDeviceChange(WPARAM wParam, LPARAM lParam) {
switch (wParam.intValue()) {
case DBT.DBT_DEVICEARRIVAL: {
return onDeviceChangeArrival(lParam);
}
case DBT.DBT_DEVICEREMOVECOMPLETE: {
return onDeviceChangeRemoveComplete(lParam);
}
case DBT.DBT_DEVNODES_CHANGED: {
//lParam is 0 for this wParam
return onDeviceChangeNodesChanged();
}
default:
System.out.println(
"Message WM_DEVICECHANGE message received, value unhandled.");
}
return null;
}
protected LRESULT onDeviceChangeArrivalOrRemoveComplete(LPARAM lParam, String action) {
DEV_BROADCAST_HDR bhdr = new DEV_BROADCAST_HDR(lParam.longValue());
switch (bhdr.dbch_devicetype) {
case DBT.DBT_DEVTYP_DEVICEINTERFACE: {
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363244.aspx
DEV_BROADCAST_DEVICEINTERFACE bdif = new DEV_BROADCAST_DEVICEINTERFACE(bhdr.getPointer());
System.out.println("BROADCAST_DEVICEINTERFACE: " + action);
System.out.println("dbcc_devicetype: " + bdif.dbcc_devicetype);
System.out.println("dbcc_name: " + bdif.getDbcc_name());
System.out.println("dbcc_classguid: " + bdif.dbcc_classguid.toGuidString());
break;
}
case DBT.DBT_DEVTYP_HANDLE: {
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363245.aspx
DEV_BROADCAST_HANDLE bhd = new DEV_BROADCAST_HANDLE(bhdr.getPointer());
System.out.println("BROADCAST_HANDLE: " + action);
break;
}
case DBT.DBT_DEVTYP_OEM: {
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363247.aspx
DEV_BROADCAST_OEM boem = new DEV_BROADCAST_OEM(bhdr.getPointer());
System.out.println("BROADCAST_OEM: " + action);
break;
}
case DBT.DBT_DEVTYP_PORT: {
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363248.aspx
DEV_BROADCAST_PORT bpt = new DEV_BROADCAST_PORT(bhdr.getPointer());
System.out.println("BROADCAST_PORT: " + action);
System.out.println("dbcp_devicetype: " + bpt.dbcp_devicetype);
System.out.println("dbcp_name: " + bpt.getDbcpName());
break;
}
case DBT.DBT_DEVTYP_VOLUME: {
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363249.aspx
DEV_BROADCAST_VOLUME bvl = new DEV_BROADCAST_VOLUME(bhdr.getPointer());
int logicalDriveAffected = bvl.dbcv_unitmask;
short flag = bvl.dbcv_flags;
boolean isMediaNotPhysical = 0 != (flag & DBT.DBTF_MEDIA/*value is 1*/);
boolean isNet = 0 != (flag & DBT.DBTF_NET/*value is 2*/);
System.out.println(action);
int driveLetterIndex = 0;
while (logicalDriveAffected != 0) {
if (0 != (logicalDriveAffected & 1)) {
System.out.println("Logical Drive Letter: " +
((char) ('A' + driveLetterIndex)));
}
logicalDriveAffected >>>= 1;
driveLetterIndex++;
}
System.out.println("isMediaNotPhysical:"+isMediaNotPhysical);
System.out.println("isNet:"+isNet);
break;
}
default:
return null;
}
// return TRUE means processed message for this wParam.
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363205.aspx
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363208.aspx
return new LRESULT(1);
}
protected LRESULT onDeviceChangeArrival(LPARAM lParam) {
return onDeviceChangeArrivalOrRemoveComplete(lParam, "Arrival");
}
protected LRESULT onDeviceChangeRemoveComplete(LPARAM lParam) {
return onDeviceChangeArrivalOrRemoveComplete(lParam, "Remove Complete");
}
protected LRESULT onDeviceChangeNodesChanged() {
System.out.println("Message DBT_DEVNODES_CHANGED");
// return TRUE means processed message for this wParam.
// see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363211.aspx
return new LRESULT(1);
}
/**
* On create.
*
* @param wParam
* the w param
* @param lParam
* the l param
*/
protected void onCreate(WPARAM wParam, LPARAM lParam) {
// System.out.println("onCreate: WM_CREATE");
}
/**
* The main method.
*
* @param args
* the arguments
*/
@SuppressWarnings("ResultOfObjectAllocationIgnored")
public static void main(String[] args) {
WinState winState = new WinState();
winState.setListener(new OnWindowsLockListener() {
@Override
public void onLocked() {
System.out.println("锁屏了");
}
@Override
public void onUnLocked() {
System.out.println("解锁");
}
});
}
}
3、编写ServiceApp提供网页
pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>${spring-boot.version}</version>
</dependency>
controller
@Controller
@Slf4j
public class ReleaseHitController {
@Resource
TimeHitService timeHitService;
@Resource
TbLogService tbLogService;
@GetMapping("/" + TIME_HIT_PATH)
public String releaseHit(Model model) {
ReleaseHitDTO dto = timeHitService.getReleaseTime();
String stateMsg = "主机当前状态 : " + dto.getState();
String msg = "距离上次休息时间 : " + dto.getReleaseTime();
String useMsg = "距离上次解锁时间 : " + dto.getUserTime();
List<TbLogEntity> list = tbLogService.listLastLog(10);
List<String> msgList = new ArrayList<>(list.size());
for (TbLogEntity en:list){
msgList.add(en.getContent());
}
model.addAttribute("msgList", msgList);
model.addAttribute("useMsg", useMsg);
model.addAttribute("state", stateMsg);
model.addAttribute("time", msg);
return TIME_HIT_PATH;
}
}
HTML
各位如果觉得写HTML太麻烦,可以使用H5在线页面快速开发,使用拖动等快捷方式做更好看的界面
<!doctype html>
<html lang="ch" xmlns:th="http://www.thymeleaf.org">
<title>休息时间</title>
<body>
<div style="position:fixed;left: 48px">
<h1 th:text="${state}">主机使用状态</h1>
<h1 th:text="${time}">距离上次休息</h1>
<h1 th:text="${useMsg}">距离上次解锁</h1>
<ul>
<li th:each="tag:${msgList}" th:text="${tag}"></li>
</ul>
</div>
<!-- 底部右侧 -->
<div style="position:fixed;bottom:84px;top:auto;right: 48px;">
<button th:onclick = "'location.reload()'"
style="width: 240px;height: 120px;font-size: xxx-large">刷新</button>
</div>
</body>
</html>
效果:左侧是浮窗、右侧是休息提醒
最后:
由于本人最近一年因生活、身体等原因,经常打不起精神,故需要外出放风。
生命的时间有限,摸鱼不可取,建议各位不要学习。
可以把有限的时间投入到写这篇博客、完成类似本篇一样的小项目、或者写日记心得。