【怜渠客】简单实现手机云控Windows电脑锁屏
手机云控 Windows 电脑锁屏
背景
在工作时,常会遇到这么一个场景:坐在电脑前办公,突然被叫了出去,可能出去只有一两分钟或几分钟,因此我不会将电脑锁屏,同时由于常用电脑做一些前台任务,电脑也不设置自动锁屏,但中断事件的时间是无法确定的,如果几十分钟甚至一两个小时还不回来,不锁屏的电脑无异于一台任人抚摸的小猫——吸引人且无力防御。
设计思路
反复几次后,我就萌生了写一个能远程云控电脑锁屏的小工具的想法,那样我只要在手机上设置一下锁屏,办公室的电脑就能自动锁屏了。思路也很简单:
实现方案
按照上述思路,实现起来就很简单了。可以拆分成以下几个小步骤:
- 写一个云控控制台,能在手机上访问
- 云端控制器能存储当前状态,并供受控端读取
- 写一个锁屏器,在受控电脑上安装,自动获取云端当前状态,并实现锁屏操作
具体实现如下:
- 云端控制台为了让通用性更强,且更简化、更快捷实现,选择了网页端来部署,没有选择用 APP,不然还要考虑 Android/IOS/Mac 等各大系统适配。
- 状态存储器用 php 实现,原因:代码简单易写,服务器方便部署且通用性强。
- 锁屏器用 golang 来写,最初考虑过 Python,但综合考量打包体积、跨设备兼容和交叉编译支持情况,还是选择了 golang。
实现代码
云端控制台,一个 html 静态小网页
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>远程锁屏控制面板 - 怜渠客</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script>tailwind.config = {theme: {extend: {colors: {primary: '#3B82F6',secondary: '#10B981',danger: '#EF4444',},fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],},}}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.btn {@apply px-4 py-2 rounded-lg font-medium transition-all duration-300 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-opacity-50;}.btn-primary {@apply bg-primary text-white focus:ring-primary;}.btn-danger {@apply bg-danger text-white focus:ring-danger;}.status-indicator {@apply w-4 h-4 rounded-full inline-block mx-2 transition-all duration-500;}.status-indicator-locked {@apply bg-danger;}.status-indicator-unlocked {@apply bg-secondary;}}</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col"><div class="container mx-auto px-4 py-8 max-w-3xl"><header class="text-center mb-8"><h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-gray-800">远程锁屏控制面板</h1><p class="text-gray-600 mt-2">通过此面板管理和查看远程设备的锁屏状态</p></header><div class="bg-white rounded-xl shadow-lg p-6 mb-8 transform transition-all duration-500 hover:shadow-xl"><div class="flex items-center justify-between mb-6"><div class="flex items-center"><span id="status-text" class="text-lg font-medium">当前状态: 未锁定</span><span id="status-indicator" class="status-indicator status-indicator-unlocked"></span></div><span id="last-updated" class="text-sm text-gray-500">上次更新: 刚刚</span></div><div class="grid grid-cols-1 md:grid-cols-2 gap-4"><button id="lock-btn" class="btn btn-danger flex items-center justify-center"><i class="fa fa-lock mr-2"></i> 设置为锁屏状态</button><button id="unlock-btn" class="btn btn-primary flex items-center justify-center"><i class="fa fa-unlock mr-2"></i> 设置为不锁屏状态</button></div><div id="message" class="mt-4 p-3 rounded-lg hidden transition-all duration-500"></div></div><div class="bg-gray-800 text-white rounded-xl p-4"><h2 class="text-xl font-semibold mb-2 flex items-center"><i class="fa fa-info-circle mr-2"></i> 使用说明</h2><ul class="list-disc pl-5 space-y-1 text-gray-300"><li>点击"设置为锁屏状态"按钮,远程设备将在下次检查时锁定屏幕</li><li>点击"设置为不锁屏状态"按钮,远程设备将保持解锁状态</li></ul></div></div><footer class="mt-auto py-4 bg-gray-800 text-white text-center"><p>远程锁屏系统 © 2025 By 怜渠客</p></footer><script>// API端点const API_URL = '###目标服务器地址###/state_query.php';// DOM元素const lockBtn = document.getElementById('lock-btn');const unlockBtn = document.getElementById('unlock-btn');const statusText = document.getElementById('status-text');const statusIndicator = document.getElementById('status-indicator');const lastUpdated = document.getElementById('last-updated');const message = document.getElementById('message');// 更新状态显示function updateStatusDisplay(state) {if (state === '0') {statusText.textContent = '当前状态: 已锁定';statusIndicator.className = 'status-indicator status-indicator-locked';} else {statusText.textContent = '当前状态: 未锁定';statusIndicator.className = 'status-indicator status-indicator-unlocked';}const now = new Date();const timeString = now.toLocaleTimeString();lastUpdated.textContent = `上次更新: ${timeString}`;}// 显示消息function showMessage(text, isError = false) {message.textContent = text;message.className = `mt-4 p-3 rounded-lg transition-all duration-500 ${isError ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`;message.classList.remove('hidden');// 3秒后隐藏消息setTimeout(() => {message.classList.add('hidden');}, 3000);}// 获取当前状态async function fetchStatus() {try {const response = await fetch(`${API_URL}?ask=1`);if (!response.ok) {throw new Error(`HTTP错误,状态码: ${response.status}`);}const state = await response.text();updateStatusDisplay(state.trim());return state.trim();} catch (error) {console.error('获取状态失败:', error);showMessage('获取状态失败,请稍后再试', true);return null;}}// 设置状态async function setStatus(newState) {try {const response = await fetch(`${API_URL}?set=${newState}`);if (!response.ok) {throw new Error(`HTTP错误,状态码: ${response.status}`);}const result = await response.text();showMessage(`状态已成功设置为: ${newState === '0' ? '锁定' : '未锁定'}`);// 立即刷新状态fetchStatus();} catch (error) {console.error('设置状态失败:', error);showMessage('设置状态失败,请稍后再试', true);}}// 事件监听器lockBtn.addEventListener('click', () => setStatus('0'));unlockBtn.addEventListener('click', () => setStatus('1'));// 初始化页面document.addEventListener('DOMContentLoaded', () => {// 立即获取一次状态fetchStatus();});</script>
</body>
</html>
云端 php 代码
<?php
// 状态文件路径
$stateFile = __DIR__ . '/state.txt';// 初始化状态文件(如果不存在)
if (!file_exists($stateFile)) {file_put_contents($stateFile, '1');
}// 处理GET请求
if (isset($_GET['ask'])) {// 查询状态$state = file_get_contents($stateFile);echo trim($state);
} elseif (isset($_GET['set'])) {// 设置状态$newState = $_GET['set'];if ($newState === '0' || $newState === '1') {file_put_contents($stateFile, $newState);echo trim($newState);} else {http_response_code(400);echo "error1";}
} else {// 无效请求http_response_code(400);echo "error2";
}
?>
只是为了向同目录下的 state.txt 里写入或读取当前状态,0 或 1
golang 锁屏器
package mainimport ("fmt""github.com/getlantern/systray""github.com/lxn/win""io/ioutil""log""net/http""syscall""time"
)const (checkInterval = 5 // 检查间隔(秒)stateURL = "###目标服务器地址###/state_query.php"
)func main() {systray.Run(onReady, onExit)
}func onReady() {systray.SetTitle("状态监控")systray.SetTooltip("系统状态监控程序")// 创建菜单项mOpen := systray.AddMenuItem("打开", "打开主窗口")systray.AddSeparator()mQuit := systray.AddMenuItem("退出", "退出程序")// 初始隐藏控制台窗口hideConsole()// 启动状态检查协程go func() {log.Println("开始监控状态...")ticker := time.NewTicker(time.Second * checkInterval)defer ticker.Stop()for {select {case <-ticker.C:checkState()case <-mQuit.ClickedCh:systray.Quit()returncase <-mOpen.ClickedCh:showConsole()}}}()
}func onExit() {log.Println("程序已退出")
}// 检查状态并根据结果锁屏
func checkState() {resp, err := http.Get(fmt.Sprintf("%s?ask=1", stateURL))if err != nil {log.Printf("请求异常: %v\n", err)return}defer resp.Body.Close()if resp.StatusCode != http.StatusOK {log.Printf("请求失败,状态码: %d\n", resp.StatusCode)return}body, err := ioutil.ReadAll(resp.Body)if err != nil {log.Printf("读取响应失败: %v\n", err)return}content := string(body)if len(content) == 0 {log.Println("收到空响应")setTrayIcon("unknown")return}firstChar := content[0]switch firstChar {case '0':log.Println("状态为0,执行锁屏")lockScreen()setTrayIcon("locked")case '1':log.Println("状态为1,不执行操作")setTrayIcon("unlocked")default:log.Printf("收到未知响应首字符: %c (完整响应: %s)\n", firstChar, content)setTrayIcon("unknown")}
}// 执行Windows锁屏操作
func lockScreen() {dll, err := syscall.LoadDLL("user32.dll")if err != nil {log.Printf("加载user32.dll失败: %v\n", err)return}defer dll.Release()proc, err := dll.FindProc("LockWorkStation")if err != nil {log.Printf("查找LockWorkStation函数失败: %v\n", err)return}r1, _, err := proc.Call()if r1 == 0 {log.Printf("锁屏失败: %v\n", err)} else {log.Println("已锁屏")}
}// 设置托盘图标状态
func setTrayIcon(status string) {// 这里简化处理,实际应用中可以使用不同的图标资源switch status {case "locked":systray.SetTooltip("系统状态监控程序 - 已锁定")case "unlocked":systray.SetTooltip("系统状态监控程序 - 未锁定")case "unknown":systray.SetTooltip("系统状态监控程序 - 未知状态")}
}// 隐藏控制台窗口
func hideConsole() {hwnd := win.GetConsoleWindow()if hwnd != 0 {var processID uint32win.GetWindowThreadProcessId(hwnd, &processID)if GetCurrentProcessId() == processID {win.ShowWindow(hwnd, win.SW_HIDE)}}
}// 获取当前进程ID
func GetCurrentProcessId() uint32 {kernel32, err := syscall.LoadDLL("kernel32.dll")if err != nil {return 0}proc, err := kernel32.FindProc("GetCurrentProcessId")if err != nil {return 0}r1, _, _ := proc.Call()return uint32(r1)
}// 显示控制台窗口
func showConsole() {hwnd := win.GetConsoleWindow()if hwnd != 0 {win.ShowWindow(hwnd, win.SW_SHOW)}
}
cmd 将可执行程序添加到自启动
@echo off
setlocal:: 设置应用程序名称和路径(可修改为实际程序路径)
set "APP_NAME=状态监控程序"
set "APP_PATH=%~dp0monitor.exe" :: 当前目录下的monitor.exe,可修改名称,可替换为完整路径:: 注册表路径
set "REG_PATH=HKCU\Software\Microsoft\Windows\CurrentVersion\Run":menu
cls
echo =====================================
echo 怜渠客云控自启动管理工具
echo =====================================
echo.
echo 1. 添加程序到自启动
echo 2. 从自启动中移除程序
echo 0. 退出
echo.
set /p choice=请选择操作 [0-2]:if "%choice%"=="1" goto add
if "%choice%"=="2" goto remove
if "%choice%"=="0" goto end
goto menu:add
:: 检查路径是否存在
if not exist "%APP_PATH%" (echo 错误:程序路径不存在 - %APP_PATH%echo 请修改脚本中的APP_PATH变量为正确路径goto :pause
):: 尝试添加注册表自启动项
echo 正在添加自启动项...
reg add "%REG_PATH%" /v "%APP_NAME%" /t REG_SZ /d "%APP_PATH%" /f:: 检查操作结果
if %errorLevel% equ 0 (echo 自启动项添加成功!echo 程序将在下次启动时自动运行
) else (echo 自启动项添加失败!echo 请尝试以管理员身份运行此脚本
)
goto :pause:remove
:: 尝试删除注册表自启动项
echo 正在移除自启动项...
reg delete "%REG_PATH%" /v "%APP_NAME%" /f:: 检查操作结果
if %errorLevel% equ 0 (echo 自启动项已成功移除!
) else (echo 自启动项移除失败!echo 可能该自启动项不存在,或需要管理员权限
)
goto :pause:pause
echo.
pause
goto menu:end
echo 程序已退出。
endlocal
另存为.bat 文件后,以管理员权限运行,这个是为了添加编译好的 golang 程序添加到开机自启动,就能自动后台运行了。
额外补充(可以不用)
同时也用 Python 实现了一个锁屏器,虽然最后没有采用。
import requests
import time
import subprocess
import osCHECK_INTERVAL = 5 # 检查间隔(秒)
STATE_URL = "###目标服务器地址###/state_query.php"def lock_screen():"""执行Windows锁屏操作"""try:subprocess.run("rundll32.exe user32.dll,LockWorkStation", check=True)print("已锁屏")except subprocess.CalledProcessError as e:print(f"锁屏失败: {e}")except Exception as e:print(f"发生未知错误: {e}")def main():"""主程序:循环检查状态并根据结果锁屏"""print(f"开始监控状态,每{CHECK_INTERVAL}秒检查一次...")while True:try:response = requests.get(f"{STATE_URL}?ask=1", timeout=10)if response.status_code == 200:content = response.text.strip()if content == "0":print("状态为0,执行锁屏")lock_screen()elif content == "1":print("状态为1,不执行操作")else:print(f"收到未知响应: {content}")else:print(f"请求失败,状态码: {response.status_code}")except requests.exceptions.RequestException as e:print(f"请求异常: {e}")except Exception as e:print(f"发生未知错误: {e}")time.sleep(CHECK_INTERVAL)if __name__ == "__main__":main()
部署
代码是如何实现的,无关紧要,只要能实现功能即可。那如何部署使用呢?
首先,控制台网页要部署到服务器(所以需要一台服务器,唯一的额外成本,小型轻量云即可,大约 50 元/年)
注意:html 里的服务器地址要改成自己的
然后,php 代码要传到服务器,才能在控制台和锁屏器里访问。
第三,将前面 go 代码里的服务器地址改成自己的,编译成 exe,并使用.bat添加到开机自启动
这里需要一些 golang 开发经验,至少知道怎么打包。
以上,即可实现手机云控电脑锁屏。
手机端效果图
PC 端效果图
最后来个效果视频
手机云控电脑锁屏演示视频