B2B网络软件

标题: 日志分级落盘配置持久化任务调度 [打印本页]

作者: YYPOST群发软件    时间: 1 小时前
标题: 日志分级落盘配置持久化任务调度

日志分级落盘配置持久化任务调度


日志分级落盘配置持久化任务调度 B2B网络软件

日志分级落盘配置持久化任务调度 B2B网络软件

日志分级落盘配置持久化任务调度 B2B网络软件



  1. // =====================================================================
  2. //  例子5:日志分级落盘 + 配置持久化 + 任务调度(真机可直接运行)
  3. //  作用:把脚本从"能跑"升级到"能长期跑、出问题能查"——
  4. //        · 日志分级 + 自动分卷,跑一晚上也不会把文件撑爆
  5. //        · 配置写文件,杀掉脚本重开还能接着上次的参数跑
  6. //        · 定时启动、限时结束、轮次休息、优雅停止(随时刹车不丢数据)
  7. //        · 统计上报:成功/失败计数 + 断网缓存补报
  8. //  引擎:ES5 / Rhino
  9. // =====================================================================

  10. // ---------------- 一、路径与配置 ----------------
  11. var BASE = "/sdcard/AIWROK/技巧5_任务调度";
  12. var LOG_DIR = BASE + "/日志";
  13. var CONF_FILE = BASE + "/配置.ini";
  14. var QUEUE_FILE = BASE + "/待上报队列.txt";
  15. var STAT_FILE = BASE + "/统计.txt";
  16. var STOP_FILE = "/sdcard/AIWROK/STOP_ALL";   // 建这个文件 = 优雅停止
  17. var PAUSE_FILE = BASE + "/PAUSE";            // 建这个文件 = 暂停

  18. var CFG = {
  19.     // 定时任务:到点才开工(24小时制,如 "09:30");留空 = 立即开始
  20.     startTime: "",
  21.     // 限时:最多跑多少分钟,到点自动收工(防跑一夜)
  22.     maxMinutes: 2,
  23.     // 轮次:总共跑几轮,每轮之间休息多少秒
  24.     rounds: 5,
  25.     restMinSec: 5,
  26.     restMaxSec: 12,
  27.     // 日志分卷:单个日志文件超过多少行就切新文件
  28.     logMaxLines: 200,
  29.     // 上报地址(没有服务器就留空,脚本会自动跳过上报)
  30.     reportUrl: "",
  31.     deviceTag: "",
  32.     // 目标APP:快手(刷视频)
  33.     targetPkg: "com.smile.gifmaker",
  34.     // 短视频翻页参数:必须一次完整快速长滑,分段曲线只会导致画面抖动不翻页
  35.     videoSwipeMinMs: 220,
  36.     videoSwipeMaxMs: 380,
  37.     stayMinMs: 2000,        // 每轮看完视频停留
  38.     stayMaxMs: 5000,
  39.     verifySwipe: true,      // 翻页后截图比对,确认真的换了视频
  40.     finishGoHome: false     // 跑完停在快手,不回桌面
  41. };

  42. function rand(a, b) { return Math.floor(a + Math.random() * (b - a + 1)); }
  43. function rSleep(a, b) { sleep.millisecond(rand(a, b)); }
  44. function dirOf(p) { var i = p.lastIndexOf("/"); return i > 0 ? p.substring(0, i) : p; }

  45. // 返回键多通道:实测部分设备 auto 对象没有 pressBack
  46. function pressBackSafe() {
  47.     try { if (auto.back) { auto.back(); return true; } } catch (e) {}
  48.     try { if (device.pressBack) { device.pressBack(); return true; } } catch (e) {}
  49.     try { if (gesture.back) { gesture.back(); return true; } } catch (e) {}
  50.     try { if (auto.pressBack) { auto.pressBack(); return true; } } catch (e) {}
  51.     return false;
  52. }
  53. function ts() {
  54.     try { return time.now(); } catch (e) {
  55.         var d = new Date();
  56.         return d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
  57.     }
  58. }

  59. // ---------------- 二、日志系统 ----------------
  60. var LOG_BUF = [];
  61. var LOG_LINES = 0;
  62. var LOG_NOW = "";

  63. function ensureDirs() {
  64.     try { file.createFloder(BASE); } catch (e) {}
  65.     try { file.createFloder(LOG_DIR); } catch (e) {}
  66. }

  67. function flushLog(force) {
  68.     if (LOG_BUF.length === 0) return;
  69.     if (!force && LOG_BUF.length < 5) return; // 攒够再写,减少 IO
  70.     try {
  71.         if (!LOG_NOW) LOG_NOW = LOG_DIR + "/run_" + new Date().getTime() + ".log";
  72.         txt.writeTxt(LOG_NOW, LOG_BUF.join("\n") + "\n", true);
  73.         LOG_BUF = [];
  74.     } catch (e) {}
  75. }

  76. // 分级日志:D 调试 / I 信息 / W 警告 / E 错误
  77. function logD(s) { writeLog("D", s); }
  78. function logI(s) { writeLog("I", s); }
  79. function logW(s) { writeLog("W", s); }
  80. function logE(s) { writeLog("E", s); }

  81. function writeLog(level, s) {
  82.     var line = ts() + " [" + level + "] " + s;
  83.     try { printl(line); } catch (e) {}
  84.     LOG_BUF.push(line);
  85.     LOG_LINES++;
  86.     // 分卷:超过上限就换新文件(长期跑的必备)
  87.     if (LOG_LINES >= CFG.logMaxLines) {
  88.         flushLog(true);
  89.         LOG_LINES = 0;
  90.         LOG_NOW = LOG_DIR + "/run_" + new Date().getTime() + ".log";
  91.         logI("日志分卷,新文件: " + LOG_NOW);
  92.     } else {
  93.         flushLog(false);
  94.     }
  95. }

  96. // ---------------- 三、配置持久化 ----------------
  97. // 技巧:config 读写会自动建文件;读的时候一定给默认值,防止首次运行取到空
  98. function saveConfig() {
  99.     try {
  100.         config.setConfig(CONF_FILE, "rounds", "" + CFG.rounds);
  101.         config.setConfig(CONF_FILE, "maxMinutes", "" + CFG.maxMinutes);
  102.         config.setConfig(CONF_FILE, "startTime", CFG.startTime);
  103.         // 注意:targetPkg 不写入配置!否则上次跑的设置页会被记住,
  104.         // 下次把代码改成快手也会被旧配置覆盖回去(已实测踩坑)
  105.         config.setConfig(CONF_FILE, "lastRun", ts());
  106.         config.setConfig(CONF_FILE, "runTimes", "" + (parseInt(getConf("runTimes", "0"), 10) + 1));
  107.         logI("配置已保存: " + CONF_FILE);
  108.     } catch (e) { logW("配置保存失败: " + (e.message || e)); }
  109. }

  110. function getConf(key, defVal) {
  111.     try {
  112.         var v = config.getConfig(CONF_FILE, key, defVal);
  113.         if (v === null || v === undefined || v === "") return defVal;
  114.         return v;
  115.     } catch (e) { return defVal; }
  116. }

  117. function loadConfig() {
  118.     try {
  119.         CFG.rounds = parseInt(getConf("rounds", "" + CFG.rounds), 10);
  120.         CFG.maxMinutes = parseInt(getConf("maxMinutes", "" + CFG.maxMinutes), 10);
  121.         CFG.startTime = getConf("startTime", CFG.startTime);
  122.         // targetPkg 一律以代码里的为准,绝不被旧配置覆盖
  123.         // (历史配置里存过 com.android.settings,导致改成快手后仍打开设置页)
  124.         logI("配置已加载,历史运行次数=" + getConf("runTimes", "0"));
  125.     } catch (e) {}
  126. }

  127. function resetConfig() {
  128.     try { file.delete(CONF_FILE); logI("配置已重置"); } catch (e) {}
  129. }

  130. // ---------------- 四、优雅停止 / 暂停 ----------------
  131. function shouldStop() { try { return file.isExists(STOP_FILE); } catch (e) { return false; } }
  132. function shouldPause() { try { return file.isExists(PAUSE_FILE); } catch (e) { return false; } }

  133. // 暂停时原地打转,删掉 PAUSE 文件就继续(调试脚本神器)
  134. function waitIfPaused() {
  135.     var guard = 0;
  136.     while (shouldPause() && !shouldStop() && guard < 600) {
  137.         if (guard === 0) logW("已暂停(删除 " + PAUSE_FILE + " 继续)");
  138.         sleep.millisecond(1000);
  139.         guard++;
  140.     }
  141.     if (guard > 0) logI("恢复运行");
  142. }

  143. // ---------------- 五、定时任务 ----------------
  144. // 等到 startTime 才开工;返回 false 表示被停止
  145. function waitForStartTime() {
  146.     // 配置文件可能读回空白字符,统一清洗后再判断,避免空值误触发定时等待
  147.     try { CFG.startTime = String(CFG.startTime || "").replace(/\s/g, ""); } catch (e) { CFG.startTime = ""; }
  148.     if (!CFG.startTime) return true;
  149.     logI("定时模式,等待到 " + CFG.startTime + " 开工");
  150.     var guard = 0;
  151.     while (guard < 3600 * 12) { // 最多等 12 小时
  152.         if (shouldStop()) return false;
  153.         var now = "";
  154.         try { now = time.now(); } catch (e) { now = ""; }
  155.         // time.now() 一般形如 2026-09-09 09:30:12,取中间 hh:mm
  156.         var hm = now.substring(11, 16);
  157.         if (now.indexOf(CFG.startTime) >= 0 || hm === CFG.startTime) {
  158.             logI("到点开工");
  159.             return true;
  160.         }
  161.         if (guard % 300 === 0) logD("等待中,当前 " + now);
  162.         sleep.millisecond(1000);
  163.         guard++;
  164.     }
  165.     return true;
  166. }

  167. // ---------------- 六、统计与上报 ----------------
  168. var STAT = { ok: 0, fail: 0, skip: 0, startMs: 0 };

  169. function statAdd(key) {
  170.     if (key === "ok") STAT.ok++;
  171.     else if (key === "fail") STAT.fail++;
  172.     else STAT.skip++;
  173. }

  174. // 上报:有网直接发,没网写队列,等有网再补报(防止数据丢)
  175. function reportLine(obj) {
  176.     if (!CFG.reportUrl) return; // 没配地址就直接跳过
  177.     var body = "";
  178.     try { body = JSON.stringify(obj); } catch (e) { body = "" + obj; }
  179.     try {
  180.         var r = http.json(CFG.reportUrl, body);
  181.         if (r) { logD("上报成功"); return; }
  182.         throw new Error("空响应");
  183.     } catch (e) {
  184.         logW("上报失败,入队缓存: " + (e.message || e));
  185.         try { txt.writeTxt(QUEUE_FILE, body + "\n", true); } catch (e2) {}
  186.     }
  187. }

  188. // 补报队列(联网后调用一次即可)
  189. function flushQueue() {
  190.     if (!CFG.reportUrl) return;
  191.     var content = "";
  192.     try { content = txt.readFileSdcard(QUEUE_FILE); } catch (e) { return; }
  193.     if (!content) return;
  194.     var lines = content.split("\n");
  195.     var left = [];
  196.     for (var i = 0; i < lines.length; i++) {
  197.         var ln = lines[i];
  198.         if (!ln) continue;
  199.         try {
  200.             http.json(CFG.reportUrl, ln);
  201.             logI("补报成功一条");
  202.         } catch (e) {
  203.             left.push(ln);
  204.         }
  205.     }
  206.     try {
  207.         file.delete(QUEUE_FILE);
  208.         if (left.length > 0) txt.writeTxt(QUEUE_FILE, left.join("\n") + "\n", true);
  209.         logI("补报完成,剩余 " + left.length + " 条");
  210.     } catch (e) {}
  211. }

  212. function saveStat() {
  213.     var s = "开始:" + ts() + " 成功:" + STAT.ok + " 失败:" + STAT.fail +
  214.             " 跳过:" + STAT.skip + " 耗时(分):" +
  215.             Math.round((new Date().getTime() - STAT.startMs) / 60000);
  216.     try { txt.writeTxt(STAT_FILE, s + "\n", true); } catch (e) {}
  217.     logI("统计: " + s);
  218. }

  219. // ---------------- 六点五、快手刷视频相关工具 ----------------
  220. function sw() { try { return screen.getScreenWidth(); } catch (e) { return 1080; } }
  221. function sh() { try { return screen.getScreenHeight(); } catch (e) { return 1920; } }

  222. // 前台包名多通道(该设备 getTopPackName 会返回空)
  223. function topPkg() {
  224.     try { var t1 = app.getTopPackName(); if (t1) return t1; } catch (e) {}
  225.     try { var t2 = app.getTopPackNameV2(); if (t2) return t2; } catch (e) {}
  226.     try { var t3 = auto.getTopPackName(); if (t3) return t3; } catch (e) {}
  227.     var act = "";
  228.     try { act = "" + app.getCurrentActivity(); } catch (e) {
  229.         try { act = "" + app.getAgentCurrenAcitvity(); } catch (e2) { act = ""; }
  230.     }
  231.     if (act && act.indexOf("/") > 0) return act.substring(0, act.indexOf("/"));
  232.     return act || "";
  233. }

  234. function isInstalled(pkg) {
  235.     try { var v = app.getVerName(pkg); if (v) return true; } catch (e) {}
  236.     try { var n = app.getAppName(pkg); if (n) return true; } catch (e) {}
  237.     return false;
  238. }

  239. // 确保快手在前台,不在就拉起来
  240. var _launched = false;
  241. function ensureForeground() {
  242.     var top = topPkg();
  243.     if (top === CFG.targetPkg) { _launched = true; return true; }
  244.     // 该设备前台API取不到包名时不要每轮重启APP:只保证首轮拉起过即可
  245.     if (!top && _launched) return true;
  246.     logI("拉起快手(当前前台=" + (top || "空") + ")");
  247.     try { app.openApp(CFG.targetPkg); } catch (e) {
  248.         try { app.startApp(CFG.targetPkg); } catch (e2) {}
  249.     }
  250.     sleep.millisecond(4000);
  251.     _launched = true;
  252.     return true;
  253. }

  254. // 短视频翻页:一次完整快速长滑(不能分段,否则只抖不翻)
  255. function onceSwipe(x1, y1, x2, y2, durMs) {
  256.     try { auto.swip(x1, y1, x2, y2, durMs, 0); }
  257.     catch (e) { try { auto.swipe(x1, y1, x2, y2, durMs); } catch (e2) {} }
  258. }

  259. function shotFingerprint() {
  260.     try {
  261.         var b = screen.screenShotFull().toBase64();
  262.         if (!b) return "";
  263.         return b.substring(0, 200) + "|" + b.substring(b.length - 200) + "|" + b.length;
  264.     } catch (e) { return ""; }
  265. }

  266. // 返回 true = 确认翻到新视频
  267. function swipeUpWithVerify() {
  268.     var before = shotFingerprint();
  269.     var w = sw(), h = sh();
  270.     var x = rand(Math.round(w * 0.35), Math.round(w * 0.65));
  271.     var y1 = Math.round(h * (0.78 + Math.random() * 0.07));
  272.     var y2 = Math.round(h * (0.12 + Math.random() * 0.08));
  273.     var dur = rand(CFG.videoSwipeMinMs, CFG.videoSwipeMaxMs);
  274.     onceSwipe(x, y1, x + rand(-12, 12), y2, dur);
  275.     logI("翻页上滑 起点y=" + y1 + " 终点y=" + y2 + " 距离" + (y1 - y2) + "px 耗时" + dur + "ms");
  276.     sleep.millisecond(700);
  277.     var after = shotFingerprint();
  278.     if (before && after && before === after) {
  279.         logW("画面无变化,补一次强滑");
  280.         onceSwipe(Math.round(w / 2), Math.round(h * 0.88), Math.round(w / 2), Math.round(h * 0.08), 250);
  281.         sleep.millisecond(900);
  282.         var after2 = shotFingerprint();
  283.         return after2 && after2 !== before;
  284.     }
  285.     return true;
  286. }

  287. // ---------------- 七、业务任务:快手刷视频一轮 ----------------
  288. function doOneRound(idx) {
  289.     logI("---- 第 " + idx + " 轮开始 ----");

  290.     ensureForeground();

  291.     // 翻页(带校验)
  292.     var ok;
  293.     if (CFG.verifySwipe) ok = swipeUpWithVerify();
  294.     else { swipeUpWithVerify(); ok = true; }

  295.     if (ok) { statAdd("ok"); logI("本轮结果: 翻页成功"); }
  296.     else { statAdd("fail"); logW("本轮结果: 翻页失败"); }

  297.     // 停留观看(分段 sleep,随时能响应停止信号)
  298.     var stay = rand(CFG.stayMinMs, CFG.stayMaxMs);
  299.     logI("停留观看 " + stay + "ms");
  300.     var secs = Math.round(stay / 1000);
  301.     for (var s = 0; s < secs; s++) {
  302.         if (shouldStop()) break;
  303.         sleep.millisecond(1000);
  304.     }

  305.     logI("---- 第 " + idx + " 轮结束 ----");
  306. }

  307. // ---------------- 八、调度主循环 ----------------
  308. function main() {
  309.     ensureDirs();
  310.     logI("===== 例子5 日志配置与任务调度 开始 =====");

  311.     // 1) 读配置(第二次运行会沿用上次的参数)
  312.     loadConfig();

  313.     // 2) 设备标识
  314.     try { CFG.deviceTag = "" + device.getModel(); } catch (e) { CFG.deviceTag = "未知设备"; }
  315.     logI("设备: " + CFG.deviceTag);

  316.     // 3) 目标APP检查 + 启动
  317.     var vName = "", aName = "";
  318.     try { vName = app.getVerName(CFG.targetPkg); } catch (e) {}
  319.     try { aName = app.getAppName(CFG.targetPkg); } catch (e) {}
  320.     logI("目标APP: " + CFG.targetPkg + " 应用名=" + (aName || "取不到") + " 版本=" + (vName || "取不到"));
  321.     if (!isInstalled(CFG.targetPkg)) logW("安装检测未确认(可能误报),仍尝试启动");
  322.     ensureForeground();

  323.     // 4) 定时等待
  324.     if (!waitForStartTime()) { logW("等待期间被停止"); return; }

  325.     STAT.startMs = new Date().getTime();
  326.     var deadline = STAT.startMs + CFG.maxMinutes * 60 * 1000;

  327.     // 4) 主循环:轮次 + 限时 + 停止/暂停
  328.     for (var i = 1; i <= CFG.rounds; i++) {
  329.         if (shouldStop()) { logW("检测到停止文件,提前收工"); break; }
  330.         if (new Date().getTime() > deadline) { logW("到达限时,自动收工"); break; }

  331.         waitIfPaused();
  332.         if (shouldStop()) break;

  333.         doOneRound(i);

  334.         // 每轮上报一次
  335.         reportLine({ device: CFG.deviceTag, round: i, ok: STAT.ok, fail: STAT.fail, time: ts() });
  336.         saveConfig();

  337.         // 轮次间休息(最后一轮不用休)
  338.         if (i < CFG.rounds) {
  339.             var rest = rand(CFG.restMinSec, CFG.restMaxSec);
  340.             logI("休息 " + rest + " 秒");
  341.             for (var s = 0; s < rest; s++) {
  342.                 if (shouldStop()) break;
  343.                 sleep.millisecond(1000);
  344.             }
  345.         }
  346.     }

  347.     // 5) 收尾:默认停在快手(刷视频不要回桌面)
  348.     if (CFG.finishGoHome) {
  349.         try { auto.home(); } catch (e) {}
  350.         logI("已回桌面");
  351.     } else {
  352.         logI("收尾:停在 " + CFG.targetPkg + " 不退出");
  353.     }

  354.     // 6) 补报 + 统计落盘
  355.     flushQueue();
  356.     saveStat();
  357.     flushLog(true);

  358.     var msg = "调度结束 成功" + STAT.ok + " 失败" + STAT.fail;
  359.     logI("===== " + msg + " =====");
  360.     try { toast.show(msg); } catch (e) {}
  361. }

  362. main();
复制代码







欢迎光临 B2B网络软件 (http://bbs.niubt.cn/) Powered by Discuz! X3.2