NYAASP Yet Another Auto Signing Program
前言
████中学使用电子班牌进行管理。早晨到校和晚自习回班都要刷卡或刷脸,如果不刷就算迟到,要扣分。 可我每天早上都会被床硬控,所以经常被扣分。 因此我编写了NYAASP,有了它,就再也不用手动刷卡了!
逆向工程
我通过特殊手段拿到了班牌程序,反编译得到以下代码:
NOTE注释不是我写的。对我理解它很有用。大家也要保持写注释的好习惯www
//向服务器发送用户的刷卡记录var sendIdcardRecord = function (findinfo) { if (isEmpty(findinfo)) return false; var saverecord = { card_no: findinfo.card_no, user_name: findinfo.user_name, record_time: findinfo.record_time, unique_code: settings.uuid, lesson_code: app.getArrayVal( "lesson_code", findinfo, app.getDefaultVal(sessionStorage.getItem("$nowLessonCode")), ), place_id: deviceCfg.place_id, source: 2, //数据来源班牌设备 user_id: findinfo.user_id, user_type: findinfo.user_type, dept_id: findinfo.dept_id, method: "checkonwork.idcard.saverecord", }; request({ url: settings.apiUrl + "/rest", method: "POST", data: saverecord, }).catch(function () { //记录考勤保存失败 });};很好,这正是我们需要的。简要分析一下API:
签到API
POST /rest
| 参数 | 示例 | 说明 |
|---|---|---|
| card_no | 123456789 | 卡号 |
| user_name | 李华 | 姓名 |
| record_time | 1783517738000 | 刷卡时间戳 |
| unique_code | ? | UUID |
| lesson_code | ? | 课程ID |
| place_id | ? | 班级ID |
| source | 2 | 2=班牌刷卡 |
| user_id | ? | 个人ID |
| dept_id | ? | 部门ID |
| method | checkonwork.idcard.saverecord | Identifier |
参数获取
API地址
点击班牌右上角设置按钮,进入设置界面,即可看到服务器地址和端口。
若地址为空,则默认为 127.0.0.1:80 ,这时只能在班牌上动手脚了。
新版本中移除了该设置按钮,此时可以暂时断开网线,进入“网络异常”界面,尝试输入默认密码 1234567890 进入设置界面。
卡号
卡号可以通过在考勤界面刷卡得到,是一串十进制数字。
也可通过校卡UID(2字节)计算得到
Classcard 智慧班牌系统 — 完整分析文档
本文档整合了针对
Classcard(智慧班牌/考勤终端)Android 应用的两次深度分析: A. JADX 原生层反编译分析 与 B. www 前端源码层分析(含模拟晚习打卡)。
目录
- 系统概述
- A. JADX 原生层分析
- A1. 应用结构总览
- A2. 设备控制核心(DeviceControl/ConfigSettings)
- A3. 服务框架与组件
- A4. 网络层分析
- A5. 原生层关键结论
- B. www 前端源码层分析
- B1. 运行启动链路
- B2. 服务器地址机制
- B3. 刷卡打卡核心机制(完整数据流)
- B4. 全部 API 接口清单
- B5. 各页面职责对照表
- B6. 关键发现与注意点
- C. 模拟晚习打卡分析
- C1. 前置环境依赖
- C2. 服务器端必需接口
- C3. 完整 settings 数据结构
- C4. 用户卡库数据结构
- C5. 模拟启动流程
- C6. 正常运维需手动输入的数据
- 附:代码文件清单
一、系统概述
Classcard 是一款运行于 Android 终端(班牌、段牌、校牌、宿舍牌、门卫牌)上的智慧考勤展示应用。采用 HTML5+(DCloud / 5+ Runtime) 混合开发模式:
- 外层:Android 原生壳 + HTML5+ Runtime(
plus.*API) - 内层:
www目录下的 HTML/JS/CSS 前端,负责界面展示与业务逻辑 - 原生扩展:
deviceControl、icCardread(串口读卡器)等自定义原生插件 - 通信协议:HTTP POST JSON 到服务器
/rest接口,method 参数区分业务
典型部署形态:每台终端对应一个教室(或门卫点),绑定后自动下载课表、座位表、用户卡库,支持学生刷卡打卡(到班、离班、早自习、午间、晚自习、进校、出校),并上报服务器统计考勤。
默认服务器地址:http://hezdh.wzzd.cn/
A. JADX 原生层分析
A1. 应用结构总览
通过 JADX 反编译,应用为 io.dcloud.PandoraEntry(HTML5+ 标准入口)包装的混合应用。包结构包含:
- 设备控制插件(核心自定义原生模块)
- H5+ Runtime 框架(
io.dcloud系列) - 网络请求模块
- 串口读卡模块(
icCardread) - 系统级操作模块(重启、关机、开屏、导航栏控制等)
A2. 设备控制核心(DeviceControl / ConfigSettings)
关键类(推断名):ConfigSettings
负责管理 settings.json 设备配置文件,主要字段:
| 字段 | 类型 | 用途 |
|---|---|---|
apiUrl | String | 服务器 API 地址 |
serverUrl | String | 服务器主机 |
serverPort | String | 服务器端口 |
domainPerfix | String | 学校域名前缀 |
uuid | String | 设备唯一标识 |
client_ip | String | 设备 IP |
mac_addr | String | MAC 地址 |
mac_os | String | 操作系统标识 |
resolution | String | 屏幕分辨率(如 1920*1080) |
resolutio_ratio_true | String | 真实分辨率 |
screenscale | String | 屏幕缩放比例 |
version / frame_version | String | 应用/框架版本 |
docPagecmd | Int | 页面包命令(-1/0/1) |
tmplid / tmplname | String | 页面模板 ID / 名称 |
deviceCfg | Object | 绑定后的设备配置(含 place_id、device_name 等) |
plus.deviceControl 原生插件提供的方法(推断):
getSettings()/setSettings()— 读写配置openSettings()— 打开系统设置openHomeSettings()— 切换主屏设置reboot()/shutdown()— 重启 / 关机statusMonitor(n)— 状态监控(n=0 停止)watchdog(n)— 看门狗(代码中已注释停用)identCode()— 获取设备唯一标识SettingWin()— 打开终端网络设置窗口
读卡器插件 plus.icCardread:
- 通过串口读取 ID 卡/IC 卡卡号
- 回调
receiveCardNoCallback将卡号注入前端 - 可配置串口路径(如
/dev/ttyS1)、波特率(如 9600)
A3. 服务框架与组件
- HTML5+ 标准入口:
io.dcloud.PandoraEntry - WebView 窗口管理:
plus.webview管理主窗口与子窗口 - PJAX 页面注入:通过
mui.fire/evalJS在子页面间传递数据 - 下载/解压:
plus.downloader+plus.zip下载并解压界面包(page.zip) - 文件操作:
plus.io+plus.deviceControl管理_doc/本地目录
A4. 网络层分析
- 统一入口:
POST {apiUrl}/rest - 请求封装:
app.getJSON()/mui.ajax/request()(axios 封装) - 数据格式:JSON
- 业务分发:
method参数(如checkonwork.idcard.saverecord) - 跨域:
crossDomain: true - 重试机制:失败记录存入
$failrecordTable,定时重发
A5. 原生层关键结论
- 配置持久化:核心配置存于
settings.json,通过plus.deviceControl.getSettings()读写。 - 读卡通过原生串口:读卡器卡号由原生层回调注入前端,前端再做业务处理。
- 看门狗已停用:
watchdog(0)在代码中被注释,系统级保护弱化。 - 设备重启/关机:通过
device.quit()/plus.deviceControl.reboot()实现。
B. www 前端源码层分析
B1. 运行启动链路
index.html (根) ├─ js/mui.min.js + js/app.js (框架 + 工具库) ├─ plus.deviceControl.getSettings() → settings (配置对象) ├─ settings.apiUrl → 服务器API地址 ├─ 根据 docPagecmd 加载页面包: │ ├─ docPagecmd=0 → _doc/page/ (本地解压页面包) │ ├─ docPagecmd=1 → page/{tmplid}/index.html (内置页面包) │ └─ docPagecmd=-1 → 未安装页面,跳转 program.html ├─ 内置页面包: │ ├─ hbpzwb → 班牌座位表界面 │ ├─ duanpai → 段牌展播(带用户)界面 │ ├─ xiaopai → 校牌展播界面 │ ├─ sushe → 宿舍展播界面 │ ├─ guardplate → 门卫进出校刷卡(横屏) │ └─ guardplatev → 门卫进出校刷卡(竖屏) ├─ page/hbpzwb/index.html (班牌主界面) │ ├─ autoMode() 自动切换模式(按时段加载不同 module) │ ├─ module/attence.html → 到/离班刷卡 │ ├─ module/morning.html → 早自习签到 │ ├─ module/noonday.html → 午间静校签到 │ ├─ module/evening.html → 晚自习签到 │ ├─ module/course.html → 今日课表 │ ├─ module/classtime.html → 作息时间 │ ├─ module/advertisement.html → 轮播展示 │ └─ module/classinfo.html → 班级信息 └─ 读卡事件分发: plus.icCardread.receiveCardNoCallback(cardno) → DOM 事件 card.receiveCardNo → index.html(根) 监听 → subWebview.evalJS('inputListener("'+cardno+'")') → 各模块 callIdcardFun(cardno) 处理B2. 服务器地址机制
- 默认回退:
settings.apiUrl = app.getDefaultVal(settings.apiUrl, 'http://127.0.0.1') - 实际配置来源:
setting.html/manage.html手动输入服务器地址 + 端口,验证后写入settings - 默认服务器:
http://hezdh.wzzd.cn/(端口 80),API 地址 =http://hezdh.wzzd.cn/rest - 服务器返回字段:
api_url、domainPerfix、uuid等 - 设备信息:IP =
device.deviceIp(),MAC =device.macAddr()
B3. 刷卡打卡核心机制(完整数据流)
1. 读卡(硬件 → JS)
- 原生插件
plus.icCardread(串口读卡器) - 原生回调 → DOM 事件
card.receiveCardNo - index.html(根)监听 →
subWebview.evalJS('inputListener("'+cardno+'")')注入页面
2. 前端分发(page/hbpzwb/index.html)
inputListener(cardno) 分发逻辑:
- 有
#frmlogin→ 用户登录验证(checkIdcard) - 有
#infopage→ 德育/点赞 - 否则 →
callIdcardFun(cardno)→ 考勤模块刷卡处理
3. 考勤页面 callIdcardFun 流程
- 从
localStorage.$cacheIdcardTable(本地卡库缓存)查找卡号 - 找到用户 →
saveIdcardAttence(findinfo)→sendIdcardRecord(findinfo)上报 layer.msg弹窗显示:卡号 + 姓名 + 班级,成功绿图标 / 失败红图标- 页面同步更新学生到班状态(绿=已签 / 红=缺勤 / 橙=迟到 / 灰=请假)
4. 上报参数(checkonwork.idcard.saverecord)
{ method: 'checkonwork.idcard.saverecord', unique_code: settings.uuid, // 设备唯一标识 lesson_code: $lesson_code, // 课节代码 (K0X X z/a/w/c/j) place_id: deviceCfg.place_id, // 教室/场所ID source: 2, // 数据来源: 2=班牌设备 user_id: findinfo.user_id, // 用户ID user_type: findinfo.user_type, // 用户类型 dept_id: findinfo.dept_id, // 部门(班级)ID user_name: findinfo.user_name, // 用户姓名 card_no: findinfo.card_no, // 卡号 record_time: findinfo.record_time, // 记录时间}5. 课节代码(lesson_code)含义
| 代码 | 含义 |
|---|---|
K000z / K0{周}z | 到班/早自习签到 |
K000w / K0{周}w | 离班/晚自习签到 |
K000j | 进校(门禁) |
K000c | 出校(门禁) |
K0{周}{节}{类型} | 某周某节课(a=午间, w=晚间, z=早间) |
晚习示例:周一晚习第1节 = K01w,周二 = K02w,…周日 = K07w
B4. 全部 API 接口清单
考勤上报核心接口(POST {apiUrl}/rest)
| Method | 用途 | 来源页面 |
|---|---|---|
checkonwork.idcard.saverecord | 保存刷卡记录(核心考勤上报) | 所有考勤/门禁 |
checkonwork.idcard.cardlist | 获取用户卡库信息 | 所有考勤/门禁 |
checkonwork.idcard.statistics | 获取考勤统计数据 | morning/noonday/evening |
checkonwork.idcard.paybycard | 获取最近刷卡记录 | guardplate |
classcard.classdata.attence | 获取到班记录 | attence.html |
classcard.classdata.gradestudent | 获取班级学生列表 | attence.html |
classcard.classdata.offclass | 发送离班通知 | attence.html.bak |
classcard.classdata.schedule | 获取今日课表 | 主页 |
studentwork.Classtypeseat.gettypeseatlist | 获取座位表 | 早/中/晚 |
studentwork.studentleave.userleave | 请假验证 | 门禁 |
studentwork.studentleave.guardverify | 门卫请假审核 | 门禁 |
设备/绑定/设置接口
| Method | 用途 | 来源页面 |
|---|---|---|
classcard.basic.checkManage | 管理员验证登录 | 设置页 |
classcard.basic.getDeviceConfig | 获取设备配置 | 定时任务 |
classcard.basic.bindDevice | 绑定设备到教室 | binding.html |
classcard.basic.saveProgram | 保存页面程序 | 设置/程序页 |
classcard.basic.deviceProgram | 获取可安装程序列表 | program.html |
classcard.basic.getBindConfig | 获取绑定配置/教室列表 | binding.html |
classcard.polling.* | 定时轮询任务 | index(根) |
B5. 各页面职责对照表
| 页面 | 功能 | 关键接口 |
|---|---|---|
index.html(根) | 启动入口,管理所有窗口 | — |
manage.html | 首次设备绑定/登录 | classcard.basic.checkManage |
setting.html | 设置服务器地址/端口 | 同上 |
binding.html | 绑定设备到教室 | classcard.basic.bindDevice |
program.html | 选择安装页面包 | classcard.basic.deviceProgram |
system.html | 系统设置中心 | — |
page/hbpzwb/index.html | 班牌主界面+考勤分发 | — |
module/attence.html | 到/离班刷卡 | saverecord, attence, gradestudent |
module/morning.html | 早自习签到 | saverecord, statistics, gettypeseatlist |
module/noonday.html | 午间静校签到 | 同上 |
module/evening.html | 晚自习签到 | 同上 |
guardplate/index.html | 门禁进校刷卡 | saverecord(K000j), paybycard |
guardplate/goout.html | 门禁出校刷卡 | saverecord(K000c), paybycard |
B6. 关键发现与注意点
- www 目录文件均为明文:无 DCloud 加密,HTML/JS 全部可读。
- 失败重发机制:
sendIdcardRecord失败后存入$failrecordTable,每 5 秒重试。 - 本地卡库缓存:
$cacheIdcardTable缓存用户卡号列表,刷卡先本地匹配再上报。 - 默认服务器地址:
http://hezdh.wzzd.cn/,API =http://hezdh.wzzd.cn/rest。 - attence.html.bak:备份版含更丰富功能(离班通知 sendOffclassNotice),新版精简。
- 系统设置 180 秒超时:
system.html无人操作自动返回($system_modetime)。 - 模式自动切换:
autoMode()依据$nowLessonCode(当前课节)决定加载哪个考勤模块。 - 设备模式 60 秒倒计时:
$device_modetime归零后自动刷新本地卡库。
C. 模拟晚习打卡分析
C1. 前置环境依赖(模拟必需)
| 依赖 | 来源 | 说明 |
|---|---|---|
settings 对象 | localStorage.$settings | 含 apiUrl, uuid, deviceCfg |
deviceCfg 对象 | settings.deviceCfg | 含 place_id, device_name |
$nowLessonCode | sessionStorage.$nowLessonCode | 晚习应为 K000w 或 K0{周}w |
$cacheIdcardTable | localStorage.$cacheIdcardTable | 用户卡库(卡号→用户映射) |
| jQuery / layer / axios / mui | 页面引入 | JS 库 |
request 函数 | js/app.js(hbpzwb) | axios 封装 |
sendIdcardRecord | page/hbpzwb/index.html | 刷卡记录上报 |
getIdcardTable | page/hbpzwb/index.html | 卡库刷新 |
app 对象 | js/app.js | 工具函数 |
C2. 服务器端必需接口(模拟时需可访问)
POST {apiUrl}/rest→checkonwork.idcard.saverecord(保存考勤记录)POST {apiUrl}/rest→checkonwork.idcard.cardlist(获取卡库)POST {apiUrl}/rest→checkonwork.idcard.statistics(考勤统计)POST {apiUrl}/rest→studentwork.Classtypeseat.gettypeseatlist(座位表)
C3. 完整 settings 数据结构(模拟需构造)
// localStorage.$settingsvar settings = { "apiUrl": "http://hezdh.wzzd.cn", // 服务器API地址 "serverUrl": "hezdh.wzzd.cn", // 服务器主机 "serverPort": "80", // 端口 "domainPerfix": "", // 学校域名前缀 "uuid": "SIMULATED-UUID-001", // 设备唯一标识(伪造) "client_ip": "192.168.1.100", // 设备IP "mac_addr": "AA-BB-CC-DD-EE-FF", // MAC地址 "resolution": "1920*1080", // 分辨率 "screenscale": "1", // 屏幕缩放 "mac_os": "Android", // 操作系统 "version": "2.23.0616", // 应用版本 "frame_version": "1.18.1215", // 框架版本 "docPagecmd": 1, // 内置页面模式 "tmplid": "hbpzwb", // 页面模板ID "tmplname": "班牌座位表界面", "deviceCfg": { "place_id": "10001", // 教室ID(伪造) "device_name": "高三(1)班", // 设备名称 "schoolname": "某中学", "placedata": { "class_cfg": { "open_offclass": -1 } }, "cardread": { "type": "1", "path": "/dev/ttyS1", "baud": "9600" } }};C4. 用户卡库数据结构(模拟需构造)
// localStorage.$cacheIdcardTable - 由 checkonwork.idcard.cardlist 返回[ { "user_id": "10001", "user_name": "张三", "user_sex": "1", // 1=男 2=女 "user_avatar": "", // 头像URL "user_type": "2", // 2=学生 "user_type_text": "学生", "dept_id": "2001", // 班级ID "dept_id_text": "高三(1)班", "card_value": ["1234567890"], // 卡号数组(可多张卡) "extend_is_stay": "否" // 是否住校 }, { "user_id": "10002", "user_name": "李四", "user_sex": "2", "user_type": "2", "user_type_text": "学生", "dept_id": "2001", "dept_id_text": "高三(1)班", "card_value": ["0987654321"], "extend_is_stay": "是" }]C5. 模拟启动流程
1. 打开 page/hbpzwb/index.html2. 在控制台设置: localStorage.setItem('$settings', JSON.stringify(settings)) localStorage.setItem('$cacheIdcardTable', JSON.stringify(cardTable)) sessionStorage.setItem('$nowLessonCode', 'K000w') // 或 K01w(周一)
3. 手动导航到 module/evening.html4. 在控制台调用 inputListener('1234567890') → 走 callIdcardFun(cardno) → 本地卡库找到张三 → sendIdcardRecord() → POST http://hezdh.wzzd.cn/rest → method=checkonwork.idcard.saverecord → 弹窗显示 "张三 刷卡成功"C6. 正常运维需手动输入的数据
| 步骤 | 操作 | 需手动输入 |
|---|---|---|
| ① 设置服务器 | manage.html 登录 | 服务器地址 + 端口 + 管理员账号 + 管理员密码 |
| ② 选择学校 | manage.html 学校下拉 | 选择学校(若有多个) |
| ③ 绑定教室 | binding.html | 选择教室 或 自定义设备名称 |
| ④ 安装页面包 | program.html | 选择界面包(如 hbpzwb 班牌座位表) |
之后设备自动运行(自动下载课表/座位表/卡库,定时轮询),无需人工干预。
模拟时额外缺失数据:
- 设备 UUID(可伪造,如上例
SIMULATED-UUID-001) - place_id(可伪造)
$cacheIdcardTable(需从服务器获取或手动构造)$nowLessonCode(手动设置为晚习代码)
附:代码文件清单
www 根目录
| 文件 | 说明 |
|---|---|
index.html | 应用启动入口,窗口/页面包管理 |
manage.html | 首次设备绑定/管理员登录 |
setting.html | 设置服务器地址/端口 |
binding.html | 绑定设备到教室 |
program.html | 选择安装页面包 |
system.html | 系统设置中心 |
js/app.js | 核心工具库(getJSON, getSettings, setSettings 等) |
js/mui.min.js | MUI 框架 |
js/vue.min.js | Vue 框架 |
页面包 page/
| 目录 | 说明 |
|---|---|
hbpzwb/ | 班牌座位表界面(含 module 考勤模块) |
duanpai/ | 段牌展播(带用户) |
xiaopai/ | 校牌展播 |
sushe/ | 宿舍展播 |
guardplate/ | 门卫进出校刷卡(横屏) |
guardplatev/ | 门卫进出校刷卡(竖屏) |
班牌考勤模块 page/hbpzwb/module/
| 文件 | 功能 |
|---|---|
attence.html | 到/离班刷卡 |
morning.html | 早自习签到 |
noonday.html | 午间静校签到 |
evening.html | 晚自习签到 |
course.html | 今日课表 |
classtime.html | 作息时间 |
advertisement.html | 轮播展示 |
classinfo.html | 班级信息 |
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





