|
|
汇率监控/盯盘报汇率定时采集入库
- /**
- * 案例17:汇率定时采集入库(纯 printl 日志版,输出到 H5 日志终端)
- *
- * 经典实用案例"汇率监控/盯盘报警",转换成 AIWROK 实现:
- * okHttp 请求 open.er-api.com 免费汇率接口(https 纯 JSON,无需注册、无需Key)
- * → 一次请求拿到全球160+币种对人民币的牌价 → 挑自选币种换算成"1外币=X元"
- * → SQLite 入库(rate_records 表) → 采集结果逐条完整打印到 H5 终端
- *
- * 案例15(股票)+案例16(天气)+案例17(汇率) 凑齐"API+数据库+定时器"监控三件套,
- * 全程不找图、不点击、无悬浮窗,手机后台长期挂机即可。
- *
- * 选 er-api 接口的原因:
- * 1. 完全免费、不用申请 Key,返回标准 JSON,最适合教学演示;
- * 2. 一次请求返回所有币种(以人民币为基准),本地挑币种换算,比逐币种请求省流量;
- * 3. 官方免费档每天更新一次,教学演示够用;长期挂机建议间隔调大(如 3600 秒)。
- *
- * 新知识点(对比案例15/16):
- * - 接口返回的是"1元=多少外币"(USD 0.14),演示里取倒数换算成"1外币=多少元"(7.14),
- * 这就是"数据加工":入库前先按业务口径转换,回看时不用每次再算;
- * - 报警从"绝对值差"升级为"百分比波动":外币间波动率可比,绝对差价不可比。
- */
- // ========== 配置区(改这里) ==========
- var 自选币种列表 = [
- { code: "USD", 名称: "美元" },
- { code: "EUR", 名称: "欧元" },
- { code: "JPY", 名称: "日元" },
- { code: "GBP", 名称: "英镑" },
- { code: "HKD", 名称: "港币" }
- ];
- var 采集间隔秒 = 30; // 每 30 秒采一轮(录像教学用;该接口每日更新,长期挂机改 3600)
- var 报警波动阈值 = 0.5; // 与上一轮汇率波动 |百分比| >= 0.5% 触发报警
- var SQLite路径 = "/sdcard/汇率监测.db";
- var 表名 = "rate_records";
- // ========== 全局变量顶部声明 ==========
- var gStop = false;
- var 数据库连接 = null;
- var 数据库可用 = false;
- var 累计入库条数 = 0;
- var 上一轮汇率 = {}; // { "USD": 7.14, ... } 首轮为空对象,不报波动
- // ========== importClass 只能放最外层 ==========
- importClass(android.database.sqlite.SQLiteDatabase);
- importClass(java.io.File);
- // ========== 工具函数(先定义后调用) ==========
- // 连接 SQLite(零配置;如需远程库可仿照案例14扩展 mysql 分支)
- function dbConnect() {
- try {
- 数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
- 数据库可用 = true;
- printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
- } catch (e) {
- 数据库可用 = false;
- printl("[DB] ⚠ 打开失败(本轮只打印不入库): " + e);
- }
- }
- // 建表(存在则跳过):code/name/rate_cny/collected_at
- function dbEnsureTable() {
- if (!数据库可用) return;
- try {
- var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
- "id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "code TEXT, name TEXT, rate_cny REAL, collected_at INTEGER)";
- 数据库连接.execSQL(sql);
- printl("[DB] ✅ 表已就绪: " + 表名);
- } catch (e) {
- printl("[DB] ⚠ 建表失败: " + e);
- 数据库可用 = false;
- }
- }
- // 单币种一轮汇率入库(rateCny = 1单位外币合多少人民币)
- function dbInsertRate(code, name, rateCny) {
- if (!数据库可用) return false;
- try {
- var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
- " (code, name, rate_cny, collected_at) VALUES (?,?,?,?)");
- stmt.bindString(1, "" + code);
- stmt.bindString(2, "" + name);
- stmt.bindDouble(3, rateCny);
- stmt.bindLong(4, time.nowStamp()); // nowStamp=毫秒数字;time.now()是格式化字符串不能bindLong
- stmt.executeInsert();
- stmt.close();
- return true;
- } catch (e) {
- printl("[DB] ❌ 入库失败: " + e);
- return false;
- }
- }
- function dbClose() {
- if (数据库可用 && 数据库连接 != null) {
- try { 数据库连接.close(); } catch (e) {}
- 数据库连接 = null;
- }
- }
- // 拉取一轮汇率:一次请求拿全部币种,返回数组 [{code,名称,汇率(1外币=X元)}],失败返回 null
- function 拉取汇率() {
- var url = "https://open.er-api.com/v6/latest/CNY"; // 以人民币为基准,一次拿全球币种
- try {
- printl("📡 正在请求汇率接口(基准 CNY,自选币种 " + 自选币种列表.length + " 个)...");
- var http = new okHttp();
- http.setTimeOut(5000, 8000, 5000); // 必须设超时,防网络挂死(踩坑经验)
- printl("📡 等待接口返回(最长8秒)...");
- var text = "" + http.get(url);
- printl("📡 接口已返回,开始解析 JSON...");
- var obj = JSON.parse(text);
- if (!obj || obj.result !== "success" || !obj.rates) {
- printl("[API] ⚠ 接口异常: " + text.substring(0, 120));
- return null;
- }
- var 结果 = [];
- for (var i = 0; i < 自选币种列表.length; i++) {
- var 币 = 自选币种列表[i];
- var 正向 = parseFloat(obj.rates[币.code]); // 1元人民币 = 正向 个外币
- if (isNaN(正向) || 正向 <= 0) {
- printl("⚠ 接口无 " + 币.名称 + "(" + 币.code + ") 牌价,跳过");
- continue;
- }
- var 反向 = 1 / 正向; // 1个外币 = 反向 元人民币(入库口径)
- 结果.push({ code: 币.code, 名称: 币.名称, 汇率: 反向 });
- }
- printl("✅ 解析完成,共取到 " + 结果.length + " 个币种汇率(牌价更新时间: " +
- obj.time_last_update_utc + ")");
- return 结果;
- } catch (e) {
- printl("[API] ❌ 拉取失败: " + e);
- return null;
- }
- }
- // 逐条完整打印采集结果(演示用:每个币种的正向/反向牌价都打出来)
- function 打印采集结果(汇率数组) {
- printl("📋 本轮采集结果(共 " + 汇率数组.length + " 个币种):");
- for (var i = 0; i < 汇率数组.length; i++) {
- var r = 汇率数组[i];
- printl(" [" + (i + 1) + "] " + r.名称 + "(" + r.code + ") 1 " + r.code +
- " = " + r.汇率.toFixed(4) + " 元 | 1 元 = " + (1 / r.汇率).toFixed(4) + " " + r.code);
- }
- }
- // 报警检查:与上一轮比波动百分比超阈值就打 ⚠(外币间波动率可比,不用绝对差)
- function 检查报警(汇率数组) {
- var 有报警 = false;
- for (var i = 0; i < 汇率数组.length; i++) {
- var r = 汇率数组[i];
- var 旧值 = 上一轮汇率[r.code];
- if (旧值 != null && 旧值 > 0) {
- var 波动 = (r.汇率 - 旧值) / 旧值 * 100;
- if (Math.abs(波动) >= 报警波动阈值) {
- 有报警 = true;
- var 符号 = 波动 >= 0 ? "+" : "";
- printl("⚠ 报警:" + r.名称 + "(" + r.code + ") 波动 " + 符号 + 波动.toFixed(3) +
- "%(" + 旧值.toFixed(4) + " → " + r.汇率.toFixed(4) + "),阈值 " + 报警波动阈值 + "%");
- }
- }
- 上一轮汇率[r.code] = r.汇率; // 记录本轮,供下一轮对比
- }
- if (!有报警) {
- printl("🔕 报警检查:全部币种波动 |百分比| < " + 报警波动阈值 + "%,无报警(首轮无对比基准)");
- }
- }
- // ========== 主流程 ==========
- printl("===== 案例17 汇率定时采集入库 启动 =====");
- // 步骤0:连库建表
- dbConnect();
- dbEnsureTable();
- printl("⚙ 配置:自选币种 " + 自选币种列表.length + " 个 · 采集间隔 " + 采集间隔秒 +
- " 秒 · 报警阈值 波动 >= " + 报警波动阈值 + "%");
- // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
- event.onStopBtnEvent(function () {
- gStop = true;
- dbClose();
- runTime.stopAllRuning();
- });
- // 步骤1:定时循环采集
- var 轮次 = 0;
- while (!gStop) {
- 轮次++;
- printl("\n---- 第 " + 轮次 + " 轮采集 ----");
- var 汇率数组 = 拉取汇率();
- if (汇率数组 != null && 汇率数组.length > 0) {
- // 1) 逐条完整打印采集结果(演示重点:正反两个口径的牌价全打出来)
- 打印采集结果(汇率数组);
- // 2) 入库
- var 本轮入库 = 0;
- printl("💾 开始逐条写入数据库(表: " + 表名 + ")...");
- for (var k = 0; k < 汇率数组.length; k++) {
- var q = 汇率数组[k];
- if (dbInsertRate(q.code, q.名称, q.汇率)) {
- 本轮入库++;
- 累计入库条数++;
- printl(" ✓ 已入库 [" + 本轮入库 + "/" + 汇率数组.length + "] " +
- q.名称 + "(" + q.code + ") 1 " + q.code + " = " + q.汇率.toFixed(4) + " 元");
- } else {
- printl(" ✗ 入库失败: " + q.名称 + "(" + q.code + ")");
- }
- }
- printl("[DB] 本轮入库 " + 本轮入库 + " 条 | 累计 " + 累计入库条数 + " 条 | 库文件: " + SQLite路径);
- // 3) 报警检查
- 检查报警(汇率数组);
- } else {
- printl("⚠ 本轮无数据,跳过入库(" + time.StampToTime(time.nowStamp()) + ")");
- }
- // 分片等待下一轮:每 5 秒打一条进度日志(画面不空,方便观察/录像)
- printl("⏳ 本轮全部完成," + 采集间隔秒 + " 秒后自动开始下一轮");
- var 剩余 = 采集间隔秒;
- while (剩余 > 0 && !gStop) {
- var 步长 = 剩余 >= 5 ? 5 : 剩余;
- printl("⏳ 等待下一轮...剩余 " + 剩余 + " 秒(期间脚本在待机,到点自动拉汇率→入库)");
- sleep.second(秒 = 步长);
- 剩余 = 剩余 - 步长;
- }
- }
- dbClose();
- printl("===== 流程结束:共 " + 轮次 + " 轮,累计入库 " + 累计入库条数 + " 条 =====");
复制代码
|
|