尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Spy++ Lite实战:窗口句柄抓取与Windows消息流排查
简介Spy Lite 3.1.0.1 是一款面向 Windows 开发、测试与逆向分析人员的轻量级窗口句柄探测工具适合排查窗体结构、控件属性及跨 32/64 位程序的界面问题。压缩包共 4 个文件约 300KB包含可执行主程序、COMCTL32.OCX 控件、HTM 下载说明与 readme.txt 文本体量虽小但组件齐全解压后即可按说明运行。软件支持以十进制和十六进制查看窗口句柄、窗口样式和类样式能生成父窗口、子窗口及兄弟窗口的句柄树并可调节窗口状态、读取程序路径和截取窗口画面。它还支持提取列表控件、树视图、下拉框、列表框、菜单及字体信息可用于任务管理器、行情数据等场景的界面分析。已有 734 人学习适合需要快速定位窗口层级与控件数据的读者参考使用。1. Spy Lite 3.1.0.1一个几 MB 的窗口句柄探针能替你省下多少猜写 Windows 桌面自动化的人多半遇到过这种时刻目标窗口明明就在屏幕上FindWindow却返回 0或者句柄抓到了发过去的消息却没有反应。Spy Lite 3.1.0.1 就是对付这种情况的它是经典 Spy 的轻量替身核心工作只有一个——把窗口句柄HWND以及围绕它的窗口层级、类名、标题、样式、进程线程归属、消息流全部摊开给你看。解压即用不必装几个 G 的 Visual Studio。做 RPA、UI 自动化、辅助功能测试、老 Win32 客户端维护的人都能用上尤其是接手别人代码、需要先确认「这个控件到底存不存在句柄」的场景。2. 窗口句柄与窗口树把靶心对准再谈代码怎么写2.1 HWND 只是索引类名才是身份证句柄在 Windows 里不是指针而是一张进程私有句柄表里的索引值。这句话听起来像课本但它带来的后果极其具体窗口销毁后这个数值可能被系统回收并分配给新窗口。你昨天抓到的0x000A1234今天指向的可能完全不是同一个东西。所以任何把句柄写成常量的代码生命周期都不会超过一次重启。真正稳定的定位依据是三元组类名Class Name、标题Caption、所属进程名。类名由RegisterClass注册同一进程内基本固定用户改不了标题会随文档、状态、语言变化进程名在升级换版本时可能变。三者组合起来才是可维护的定位条件。窗口树则是另一层Desktop 下面挂顶层窗口顶层窗口下面挂子窗口和控件GetParent/GetWindow(GW_CHILD)/GetWindow(GW_HWNDNEXT)就是在这棵树上移动。Spy Lite 的 Window 视图本质上把这棵树可视化了你要做的是先在这棵树上找到目标节点再决定用哪条 API 走到它。注意父窗口Parent和所有者窗口Owner是两回事。对话框的 Owner 是父级窗口但它通常不是 Parent 的子窗口。判断归属时看错这一层遍历逻辑就会漏掉目标。2.2 拖靶心抓窗口一次抓取要记下哪七个字段按这类工具的常见形态启动后界面上会有一枚靶心图标按住它拖到目标窗口上松手就能锁定一个句柄。锁定之后不要急着写代码先把属性面板里的字段抄下来这七个是后面所有脚本的输入字段典型值用途Handle0x000A1234临时定位不可硬编码Class NameNotepad / Chrome_WidgetWin_1稳定定位的主要依据Caption无标题 - 记事本辅助匹配注意会变Style / ExStyle0x14CF0000判断是否可见、是否有边框Window Rect(100, 100, 900, 700)坐标换算的基准Process ID8124区分同名多开Thread ID8128判断消息循环归属抓窗口时有个动作值得养成习惯拖靶心的时候别松手太快先在目标上停半秒。很多现代应用有悬浮层、拖拽遮罩、无障碍提示层松手位置差几个像素抓到的可能是遮罩而不是底下的真实窗口。血泪经验是抓完先看 Class Name 是不是你预期的那个如果出现了Chrome_*、Intermediate D3D Window、SysShadow这类名字基本可以判定抓错了层。抓到之后立刻做一次交叉验证在窗口树里找到同一个节点看它的层级路径是否合理。比如你要操作的是某个输入框那它应该挂在主窗口下面若干层而不是直接挂在 Desktop 上。挂在 Desktop 上的往往是独立的弹出层行为逻辑和子控件完全不同。2.3 窗口树视图分辨「有句柄的控件」和「画出来的控件」这一步是新手和老手的分水岭。很多人拿着 Spy Lite 翻遍整棵窗口树就是找不到那个「确定」按钮于是怀疑工具坏了。真实原因是那个按钮压根没有独立句柄它是父窗口自绘的。Win32 里有两类控件。一类是标准控件比如Button、Edit、ComboBox每个都有自己的 HWND能在窗口树里单独出现能接收WM_SETTEXT、BM_CLICK。另一类是自绘或 DirectUI 实现的控件界面上看着是按钮实际上只是父窗口WM_PAINT里画出来的一块矩形点击事件靠父窗口的WM_LBUTTONDOWN加坐标命中判断。后者你在 Spy Lite 里怎么找都找不到节点只能拿到父窗口句柄然后用坐标点击或者改用 UI Automation 走无障碍树。用代码对照工具视图是最快的验证方式。下面这段用 ctypes 递归枚举子窗口输出结果和工具里的树状结构应该一一对应import ctypes from ctypes import wintypes user32 ctypes.WinDLL(user32, use_last_errorTrue) EnumChildProc ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM) def get_text(hwnd: int) - str: buf ctypes.create_unicode_buffer(512) # 跨进程窗口可能取不到标题返回空串属于正常现象 user32.GetWindowTextW(wintypes.HWND(hwnd), buf, 512) return buf.value def get_class(hwnd: int) - str: buf ctypes.create_unicode_buffer(256) user32.GetClassNameW(wintypes.HWND(hwnd), buf, 256) return buf.value def walk(hwnd: int, depth: int 0) - None: print( * depth f0x{hwnd:08X} [{get_class(hwnd)}] {get_text(hwnd)!r}) EnumChildProc def _cb(child, _lparam): walk(child, depth 1) return True # 只枚举直接子节点递归交给 walk 自己处理 user32.EnumChildWindows(wintypes.HWND(hwnd), _cb, 0) if __name__ __main__: target int(000A1234, 16) # 换成 Spy Lite 里复制的句柄 walk(target)这里的逻辑值得拆开说。EnumChildWindows本身会枚举所有后代但代码里在回调中又递归调用了walk所以实际行为是把整棵子树逐层展开——如果只看直接子节点应该改用GetWindow(hwnd, GW_CHILD)配合GW_HWNDNEXT手工循环。WINFUNCTYPE声明回调类型是必须的用普通的 Python 函数传进去64 位下会因为调用约定不匹配直接崩掉这是新手最容易翻车的一处。get_text在目标进程权限更高时返回空串不是 bug是 UIPI 拦截第 5 章细说。跑完把输出和工具里的窗口树对一遍如果深度和节点数完全一致说明你的遍历逻辑没问题可以放心往脚本里搬如果不一致先检查是不是工具默认隐藏了不可见窗口——GetWindow系列能看到WS_VISIBLE为假的窗口很多工具的树视图默认过滤掉它们。3. 消息视图与 WM_ 解码把消息流从黑匣子变成可对照的日志3.1 三栏视图与过滤先砍掉 90% 的噪音消息视图一打开大概率立刻刷屏。因为WM_MOUSEMOVE、WM_PAINT、WM_NCHITTEST这类消息在鼠标划过窗口时每秒能产生上百条把所有消息都列出来有用的那一条会被淹掉。Spy 系列的惯例做法是先按消息组收窄再用字符串过滤我一般会按这个顺序操作只勾选「当前窗口」而不是「所有窗口」避免系统里其它进程的消息混进来关掉鼠标、绘制、非客户区这三个消息组它们贡献了绝大部分噪音在过滤框里填目标消息名比如只看WM_COMMAND、WM_SETTEXT、WM_CLOSE用排除写法先看全量、再逐个剔掉高频消息比正向列举更容易发现意料之外的消息。过滤这一步做完剩下的往往只有几十条逐条读就轻松了。这里有个反直觉的点很多人以为要抓的是「点击按钮」这个动作实际上真正有价值的是动作发生后窗口收到了什么消息。你在工具里手动点一下目标按钮看看消息列表里多出哪一条那条消息就是脚本里要复现的东西。3.2 wParam / lParam 的拆法四条常用消息逐位看消息的两个参数都是 64 位整数32 位进程下是 32 位但它们不是整体使用的而是按位切分的。看不懂位切分十六进制在眼前就是天书。消息wParamlParamWM_COMMAND低 16 位控件 ID高 16 位通知码控件句柄WM_CLOSE00WM_SETTEXT0指向字符串的指针WM_LBUTTONDOWN虚拟键状态位低 16 位 X高 16 位 Y客户区坐标WM_COMMAND是最常被误读的一个。假设工具里显示wParam 0x000003E9、lParam 0x000B0F2C含义是控件 ID 为 0x3E9十进制 1001通知码为 0BN_CLICKED触发它的控件句柄是 0xB0F2C。拆解代码就是ctrl_id wparam 0xFFFF和notify (wparam 16) 0xFFFF。如果你把整个 wParam 当成 ID 去比对永远对不上。WM_SETTEXT的 lParam 要特别小心。它是指向目标进程地址空间里的字符串指针你用ReadProcessMemory硬读大概率失败原因是地址空间隔离加上权限限制。正确做法是用SendMessage(hwnd, WM_SETTEXT, 0, 文本)让系统自己去跨进程封送。同理任何 lParam 看起来像指针的消息WM_COPYDATA、WM_GETTEXT等都不要试图直接解引用。3.3 从「看到」到「发出」用 PostMessage 复现一条消息工具里的消息记录最大的价值是给你一份「标准答案」——你可以照着它在脚本里发一条一模一样的消息然后回到工具里验证是否被接收。这套闭环比闷头调代码高效得多。import win32gui import win32con # 从 Spy Lite 复制的句柄实际使用时应通过类名重新查找 parent_hwnd int(000A1234, 16) control_hwnd int(000B0F2C, 16) control_id 1001 # 对应 WM_COMMAND 的 wParam 低 16 位 notify_code 0 # BN_CLICKED wparam (notify_code 16) | control_id # PostMessage 异步投递不阻塞SendMessage 会等目标处理完再返回 win32gui.PostMessage(parent_hwnd, win32con.WM_COMMAND, wparam, control_hwnd) # 关闭窗口可以用另一种方式验证消息通路 # win32gui.PostMessage(parent_hwnd, win32con.WM_CLOSE, 0, 0)参数上有几个判断点。PostMessage和SendMessage的区别不只是同步异步后者会等目标窗口的消息处理函数执行完才返回如果目标卡死你的脚本也一起卡死前者直接丢进消息队列就走但拿不到处理结果。做自动化验证时先用PostMessage试看到工具里确实收到了消息再换成SendMessage获取返回值。另外wParam的位运算顺序不能反notify 16 | id和id 16 | notify传过去是完全不同的两条指令。发完之后立刻回到 Spy Lite 的消息视图如果那条消息出现在列表里说明句柄、消息号、参数三者都对如果没出现问题一定在句柄上直接跳到窗口树重新确认节点。4. 句柄喂给脚本ctypes 与 pywin32 两套写法对照4.1 句柄的三种写法写错一个就返回 0从工具里复制出来的句柄落地到代码里有三种形态十进制整数、带0x前缀的十六进制字面量、十六进制字符串。新手最常犯的错是把000A1234直接当整数塞进 API结果得到 0 或者一个莫名其妙的错误码。工具显示0x000A1234Python 里写0x000A1234直接用工具只显示000A1234写成int(000A1234, 16)工具显示十进制660020直接写660020。另外在 64 位 Python 里HWND本质是c_void_p用ctypes传参时整数会被自动封送但如果你要把它放进结构体或者回调里就得显式转成wintypes.HWND。pywin32 则统一用 Pythonint表示句柄省事但少了类型检查传错也不会报错只会静默返回 0。这就是为什么建议在关键调用之后立刻if not hwnd: raise把错误暴露在发生的那一行而不是几百行之后。4.2 ctypes 版不装第三方库读标题、类名、矩形给客户部署脚本时多一个依赖就多一份风险。ctypes 是标准库不装任何东西就能跑适合做成单文件工具。import ctypes from ctypes import wintypes user32 ctypes.WinDLL(user32, use_last_errorTrue) def read_window(hwnd: int) - dict: title ctypes.create_unicode_buffer(512) user32.GetWindowTextW(wintypes.HWND(hwnd), title, 512) cls ctypes.create_unicode_buffer(256) user32.GetClassNameW(wintypes.HWND(hwnd), cls, 256) rect wintypes.RECT() # 返回的是屏幕坐标包含窗口边框和标题栏 user32.GetWindowRect(wintypes.HWND(hwnd), ctypes.byref(rect)) pid wintypes.DWORD() tid user32.GetWindowThreadProcessId(wintypes.HWND(hwnd), ctypes.byref(pid)) return { hwnd: f0x{hwnd:08X}, title: title.value, class: cls.value, rect: (rect.left, rect.top, rect.right, rect.bottom), pid: pid.value, tid: tid, visible: bool(user32.IsWindowVisible(wintypes.HWND(hwnd))), } if __name__ __main__: info read_window(int(000A1234, 16)) for k, v in info.items(): print(f{k:8}: {v})几个参数要说明白。create_unicode_buffer的长度是字符数不是字节数512 对绝大多数标题够用但浏览器标签页标题可能超长截断了不会报错只会悄悄少一截所以读回来之后可以比对一下长度是否等于缓冲区大小减一。GetWindowRect返回的是屏幕坐标且包含非客户区如果你要算客户区坐标需要再调GetClientRect加ClientToScreen。GetWindowThreadProcessId同时返回线程 ID 和写入 PID返回值是线程 ID这个返回值容易被忽略。还有一处容易忽略WinDLL加了use_last_errorTrue之后失败时可以用ctypes.get_last_error()拿错误码不加的话只能靠GetLastError的时机经常拿到过期值。排查句柄失效时这个错误码就是你的黑匣子。4.3 pywin32 版递归遍历与按类名过滤需要批量扫描、按条件筛选的时候pywin32 的封装更省事尤其是遍历类 API 已经帮你把回调封装好了。import win32gui def find_all_by_class(class_name: str) - list[tuple[int, str]]: hits [] def _cb(hwnd, _extra): # 顶层窗口回调不递归子窗口遍历另行处理 if win32gui.GetClassName(hwnd) class_name: hits.append((hwnd, win32gui.GetWindowText(hwnd))) return True win32gui.EnumWindows(_cb, None) return hits def find_child_by_title(parent: int, keyword: str) - int: found 0 def _cb(hwnd, _extra): nonlocal found if keyword in win32gui.GetWindowText(hwnd): found hwnd return False # 返回 False 终止枚举 return True win32gui.EnumChildWindows(parent, _cb, None) return found if __name__ __main__: for hwnd, title in find_all_by_class(Notepad): print(f0x{hwnd:08X} {title}) child find_child_by_title(int(000A1234, 16), 确定) print(child:, hex(child) if child else 未找到)回调返回False会终止枚举这个特性用来做「找到就停」非常合适返回True则继续。EnumWindows只遍历顶层窗口子窗口得靠EnumChildWindows两者别混用。另外GetWindowText在跨进程时底层是发WM_GETTEXT目标窗口如果正忙或者拒绝响应这个调用会阻塞到超时——在一堆窗口上循环调用时这个阻塞累积起来能让脚本卡十几秒。稳妥的替代是GetWindowTextLength先探长度或者干脆只依赖类名筛选。4.4 高 DPI 下的坐标先声明感知再谈换算同一份坐标代码在 125% 缩放的笔记本上偏移在 100% 的外接屏上正常这类问题九成出在 DPI 虚拟化。进程如果没有声明 DPI 感知系统会把GetWindowRect的结果按缩放比例缩放后再返回你拿到的坐标和屏幕上真实像素对不上。声明必须在创建任何窗口之前完成import ctypes def enable_dpi_awareness() - None: try: # 2 PER_MONITOR_AWARE_V2 级别Win8.1 及以上可用 ctypes.windll.shcore.SetProcessDpiAwareness(2) except (AttributeError, OSError): # 老系统回退到系统级感知 ctypes.windll.user32.SetProcessDPIAware() if __name__ __main__: enable_dpi_awareness() # 之后的坐标调用才是真实像素SetProcessDpiAwareness的参数里0 是不感知、1 是系统级、2 是每显示器级。多屏不同缩放的环境必须用 2否则窗口拖到另一块屏上坐标又乱了。这个函数只能调用一次重复调用会返回错误码捕获异常即可。声明感知之后GetWindowRect返回的就是物理像素做截图和点击都不用再乘缩放系数。5. 避坑与排查句柄失效、权限拦截、DPI 偏移这五关5.1 现象脚本跑了一晚上第二天全部报句柄无效现象很典型昨天还能正常操作的脚本今天启动目标程序后所有SendMessage都无声失败IsWindow返回 0。原因只有一个——句柄被复用了或者窗口被重建了。目标程序退出重开、切换文档、主题变化、语言切换都可能销毁原有窗口并创建新窗口新窗口拿到的句柄数值完全是另一套。任何把0x000A1234写死在代码里的做法都是在赌目标程序的启动顺序永远不变这个赌注必输。玄学的是有时候连续几天都没事让你误以为硬编码能用。解决办法是每次运行都重新定位用类名加标题正则加进程名做匹配找到就用找不到就报错退出而不是继续用旧值。更进一步把「查找」封装成一个带重试和超时的函数因为目标程序启动慢的时候窗口可能在你查找之后才出现。5.2 现象工具里能看到完整窗口树脚本里读到的标题全是空现象是GetWindowText返回空字符串GetClassName正常EnumChildWindows也枚举不出任何子节点。原因是权限级别不对等。Windows 的 UIPI 机制规定低完整性级别的进程不能向高完整性级别进程的窗口发送消息而GetWindowText跨进程时底层依赖WM_GETTEXT被拦下来就返回空。典型场景是目标程序以管理员身份运行你的脚本是普通权限。工具能看到内容是因为工具本身可能也提权了或者它读取的是缓存信息。解决办法是让脚本和目标程序运行在同一权限级别。检查方法很简单用任务管理器看两边的完整性级别或者用whoami /groups看当前进程是否在高完整性下。以管理员启动终端再跑脚本问题通常立刻消失。如果部署环境不允许提权那就退一步只依赖类名和句柄做定位标题用GetWindowTextLength探个长度即可。5.3 现象坐标对得上点击却总偏一截现象是打印出来的 rect 和屏幕上肉眼看到的位置差不多但按这个坐标去点击落点偏了几十像素且偏移量随窗口在屏幕上的位置变化。原因通常有两层。第一层是 DPI 虚拟化进程没声明感知系统返回的是缩放后的逻辑坐标第二层是坐标系选错GetWindowRect给的是屏幕坐标而某些点击 API 要的是客户区坐标直接混用必然偏移。还有一种情况是窗口有阴影或扩展边框GetWindowRect包含不可见的边框区域实际可点击区域比返回值小。解决办法是三步走先声明 Per-Monitor V2 感知再用GetClientRect加ClientToScreen拿到客户区原点最后把目标控件在客户区内的相对坐标换算成屏幕坐标。换算完可以先不点击用工具里的「高亮窗口」功能把算出来的矩形画出来和屏幕上真实位置叠一下差多少一眼就能看出来。5.4 现象消息视图一开工具直接卡住不动现象是点开 Messages 视图后界面失去响应风扇狂转过一会儿才缓过来列表里全是同一条消息。原因是高频消息洪水。鼠标在窗口上移动产生的WM_MOUSEMOVE、窗口重绘产生的WM_PAINT、鼠标经过非客户区的WM_NCHITTEST一秒几百条完全正常。工具要把每条都格式化、加时间戳、塞进列表控件内存和 CPU 瞬间吃满。这不是工具的缺陷是它诚实地把系统里发生的每一件事都记了下来。解决办法是打开消息视图之前先设好过滤把这三类高频消息排除掉只保留你要看的。如果已经卡住了别硬点用任务管理器结束进程重开配置一般会保留。我的习惯是每次启动工具第一件事就是改过滤把WM_MOUSEMOVE、WM_PAINT、WM_NCHITTEST、WM_SETCURSOR全部加进排除列表后悔药没处买。5.5 现象窗口树翻了三遍就是没有那个按钮的节点现象和 2.3 说的一样界面上明明有按钮树里就是找不到。原因是这个控件没有独立 HWND。现代 UI 框架DirectUI、Electron 的自绘区域、部分 Qt 自绘控件为了性能常常把一堆「控件」画在同一个 HWND 上点击靠坐标命中判断。你拿父窗口句柄发WM_COMMAND不会有任何反应因为根本没有控件 ID 这回事。解决办法分两条。第一条是坐标点击拿到父窗口句柄和控件在父窗口客户区内的相对位置用PostMessage(WM_LBUTTONDOWN)加WM_LBUTTONUP模拟注意lParam要打包成y 16 | x。第二条是换技术路线改用 UI Automation 走无障碍树那套接口对这些自绘控件是有支持的代价是代码量和依赖都上去了。判断该走哪条路的信号很简单用 Inspect 类工具无障碍树能看到这个按钮就走第二条看不到就老老实实算坐标。6. 进阶验证把一次抓取固化成可重复的回归快照调通一次不算本事能在目标程序升级、主题切换、语言变更之后快速判断「哪里变了」才是省时间的地方。我的做法是把抓取结果存成一份结构化快照每次运行前跑一遍比对差异直接打出来。import json import sys import win32gui TARGET_CLASS Notepad # 换成你的目标类名 SNAPSHOT_FILE ui_snapshot.json def take_snapshot() - list[dict]: rows [] def _cb(hwnd, _extra): if win32gui.GetClassName(hwnd) ! TARGET_CLASS: return True # 只记录结构信息不含句柄避免每次运行都产生假差异 rows.append({ title: win32gui.GetWindowText(hwnd), rect: list(win32gui.GetWindowRect(hwnd)), children: [ win32gui.GetClassName(c) for c in iter_child_classes(hwnd) ], }) return True win32gui.EnumWindows(_cb, None) return rows def iter_child_classes(parent: int): def _cb(hwnd, _extra): yield hwnd # 注意生成器在回调里不会真正逐条返回 return True # 实际使用请改为先收集到列表见下方说明 result [] win32gui.EnumChildWindows(parent, lambda h, e: (result.append(h), True)[1], None) return result if __name__ __main__: current take_snapshot() if --update in sys.argv: with open(SNAPSHOT_FILE, w, encodingutf-8) as f: json.dump(current, f, ensure_asciiFalse, indent2) print(f已写入 {len(current)} 条快照) sys.exit(0) with open(SNAPSHOT_FILE, encodingutf-8) as f: baseline json.load(f) if current ! baseline: print(结构有变化) print(json.dumps(current, ensure_asciiFalse, indent2)) sys.exit(1) print(结构与基线一致)这段代码的关键设计是快照里刻意不存句柄。句柄每次启动都会变存进去只会制造噪音把真正的结构变化淹没掉。存的是标题、矩形、子控件类名列表这些在程序升级后变与不变都有意义。第一次运行加--update生成基线之后每次改动代码或目标程序升级先跑一遍比对差异会直接告诉你哪个窗口没了、哪个控件多出来了。上面iter_child_classes里我留了一个反面示范在EnumChildWindows的回调里用生成器yield是不成立的因为回调是 C 侧调用的生成器根本没机会被驱动结果永远是空。正确写法是先收集到列表再返回代码里那行lambda就是在做这件事。这个问题我在两个项目里都踩过症状是子控件列表永远为空而顶层窗口又一切正常排查方向特别容易跑偏。从那以后我每次接一个陌生的 Windows 客户端项目第一步都不是写业务代码而是先用 Spy Lite 抓一份快照存起来跑通之后再动手。这套流程救过我很多次尤其是在客户那边说「程序升级后你的脚本不动了」的时候两份快照一 diff五秒钟就能判断是控件结构变了还是权限问题。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Neo4j企业版5.15.0 Windows安装:JDK17与服务配置排错

Neo4j企业版5.15.0 Windows安装:JDK17与服务配置排错

简介:Neo4j 企业版 5.15.0 Windows 安装包面向需要突破社区版容量、并发与集群限制的图数据库开发者、运维及架构人员,可用于企业级知识图谱、关系分析和因果集群部署等场景。压缩包共 205 个文件,约 107.64MB,以 178 个 jar 依赖…

📅 2026/10/30 17:14:34
微信小程序点餐系统:Spring Boot+原生小程序实战

微信小程序点餐系统:Spring Boot+原生小程序实战

简介:本资源是一套完整的微信点餐系统毕业设计开源方案,面向计算机专业本科生及Java全栈初学者,解决餐饮行业线上化点餐场景下的前后端协同开发实践问题。资源包含基于SSM框架的Java后端、微信小程序前端及配套文档,覆盖开题报告、…

📅 2026/10/30 17:14:34
Kitematic 0.17.11 for Mac:Docker Desktop 的轻量GUI外挂

Kitematic 0.17.11 for Mac:Docker Desktop 的轻量GUI外挂

简介:本资源是面向Mac平台开发者的Docker图形化管理工具Kitematic 0.17.11正式版安装包,专为不熟悉Docker CLI命令、希望快速上手容器部署的初学者及轻量级项目开发者设计。它通过一体化GUI实现镜像搜索、容器启停、端口映射、环境变量配置、卷挂载与日志…

📅 2026/10/30 17:14:34
MORE NEWS

更多资讯

📰

SpringBoot视频网站开发:从上传到播放的全流程实践

1. 项目背景与核心需求在线视频平台已经成为当代互联网基础设施的重要组成部分,从教育领域的在线课堂到娱乐领域的短视频应用,视频内容的传播与互动需求呈现爆发式增长。对于计算机专业毕业生而言,开发一个具备完整功能的视频网站&#xff0c…

📰

extract-xiso完全指南:Xbox光盘镜像提取与打包实战

简介:extract-xiso 是一款开源的光盘映像备份工具,专注于创建和提取 Xbox 游戏光盘的 .iso 文件,适用于 Linux、FreeBSD、macOS、Windows 等多个操作系统,目标用户是游戏备份爱好者、模拟器玩家及需要处理 Xbox 镜像的开发者。这份…

📰

地铁BAS PLC为何必须用STEP7 V5.5?兼容性与现场准入硬约束

简介:本资源是一套完整的地铁环境监控系统(BAS)PLC控制程序工程包,面向自动化、轨道交通及工业控制领域的工程师、高校师生与PLC初学者,聚焦西门子S7系列PLC在真实地铁场景中的工程化应用。资源基于STEP7 V5.5开发&…

📰

C语言+easyX实现Flappy Bird:图形编程底层实战指南

简介:这是一份面向C语言初学者与图形编程入门者的Flappy Bird像素鸟游戏实战源码包,聚焦Windows平台下基于easyX图形库的游戏开发实践,帮助学习者掌握双缓冲绘图、物理模拟(重力/碰撞)、状态机管理及音效集成等核心技能…

📰

【纯白嫖】IEC60870-5 中文版下载指南:101/104 协议标准一次拿齐全集

【纯白嫖】IEC60870-5 中文版下载指南:101/104 协议标准一次拿齐全集 【免费下载链接】IEC60870-5全集中文版本下载 IEC60870-5全集中文版本下载 项目地址: https://gitcode.com/open-source-toolkit/fc96f IEC60870-5 原版标准难找难读。IEC60870-5全集中文…

📰

刚更新的首次启动流程:模型来源、系统授权、Agent 配置一次配完

刚更新的首次启动流程:模型来源、系统授权、Agent 配置一次配完 【免费下载链接】OpenBot Open-source AI coworkers that each get a computer of their own: a browser, files and tools, with every action decided before it happens and recorded after. Bring…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬