本地脚本

概述

本地脚本功能基于设备本地 SQLite 数据库页面设计能力表单页)实现。通过中控服务,可将设备上已配置的脚本数据同步至云控后台,实现:

  • 配置脚本:在设备端设置脚本参数并同步到云端
  • 管理模板:支持添加、删除脚本配置模板
  • 执行脚本:触发设备端运行指定脚本
数据存储在设备本地,无需额外部署服务端数据库。

功能架构说明

本功能本质上是对云控后台二次开发能力的扩展,主要包括:

  • 表单页设计 → 用于编辑数据
  • SQLite 数据库 → 用于本地数据存储

二者组合,实现完整的脚本管理能力。

学习前提

在使用本功能前,建议先掌握云控后台二次开发的基础能力,否则可能无法正确理解页面设计与数据结构之间的关系。

建议学习顺序如下:

  1. 表单页设计
  2. Bot.js Pro sqlite 模块

数据库结构

数据库由 核心元数据表脚本参数配置表 两部分组成。

本地脚本.db
├── meta_script(脚本元数据)
├── meta_form(表单元数据)
├── 脚本示例(脚本参数配置表)
└── ...(其他脚本参数配置表)

核心元数据表(必选)

以下两张表为系统运行的基础,必须存在且结构不可修改

表名作用
meta_script存储脚本内容、脚本名称、脚本路径、运行配置
meta_form定义脚本参数配置的表单结构

主要功能:存储表格布局、字段配置、交互逻辑等元信息。

脚本配置数据表(自定义)

业务表用于存储实际数据,支持灵活扩展:

  • 表名:即脚本名称,完全自定义(支持中文)
  • 字段:完全自定义(支持中文)
  • 数量:可创建多个脚本配置数据表
  • 用途:承载具体脚本配置数据,并与元数据表关联使用

表结构说明

meta_script(脚本元数据)

用于定义列表展示结构及其关联关系:

id              INTEGER PK     主键
scriptContent   TEXT           脚本内容代码
scriptName      TEXT           脚本名称
scriptPath      TEXT           脚本文件路径
scriptConfig    TEXT           脚本执行配置参数
targetTable     TEXT           关联脚本参数配置表

meta_form(表单元数据)

用于定义表单结构及字段配置:

id              INTEGER PK     主键
label           TEXT           表单名称
data            TEXT           表单设计数据
dict            TEXT           字典/配置
title           TEXT           表单标题
targetTable     TEXT           关联脚本参数配置表

脚本参数配置表(示例:脚本示例)

用于存储实际业务数据(字段完全可自定义,支持中文,推荐英文,设计表格时可以显示中文标签名):

id              INTEGER PK     主键
label           TEXT           标签
category_id     INTEGER        分类ID
task_type_id    INTEGER        任务类型ID
device_id       INTEGER        设备ID
status          INTEGER        状态
user_id         INTEGER        用户ID
uid             TEXT           唯一标识
created_at      INTEGER        创建时间戳

upload          TEXT           上传文件
input           TEXT           输入框
textarea        TEXT           多行文本
inputNumber     INTEGER        数字输入

datePicker      TEXT           日期选择
timePicker      TEXT           时间选择
colorPicker     TEXT           颜色选择

rate            INTEGER        评分
slider          INTEGER        滑块
switch          INTEGER        开关

radio           INTEGER        单选框
checkbox        TEXT           多选框
select_field    INTEGER        选择器
selectV2        INTEGER        选择器V2
treeSelect      INTEGER        树形选择

image           TEXT           图片
video           TEXT           视频
play            TEXT           播放

download        TEXT           下载
copy            TEXT           复制
link            TEXT           链接
...

关键关系说明

  • meta_script.targetTable → 关联脚本参数配置表
  • meta_form.targetTable → 关联脚本参数配置表

数据库创建

可在后台创建并编辑 SQLite 模板,详情参阅 SQLite 数据库

完成数据库设计后,数据库可通过以下两种方式在设备端实现:

数据库文件分发

适用于已设计完成的 SQLite 数据库模板。

数据库文件(.db)可通过任意方式分发至设备,例如本地传输或远程下载等,实现快速部署与同步。

存放路径要求:

/sdcard/cloud/SQLite/

将数据库文件放入上述目录后,系统即可自动识别并使用。

脚本动态创建

适用于运行时初始化或动态生成数据库结构的场景。

无需依赖预置 .db 文件,可通过脚本在设备端直接创建表结构。

// 👉 示例调用

createLocalScriptDB("/sdcard/cloud/SQLite/本地脚本.db", "脚本示例");

/**
 * 创建【本地脚本数据库模板】
 *
 * 📌 功能说明:
 * 根据指定路径创建一个完整的 SQLite 数据库,包含:
 * 1️⃣ 脚本配置表(meta_script)
 * 2️⃣ 表单定义表(meta_form)
 * 3️⃣ 动态业务数据表(如:脚本示例)
 *
 * 📌 使用场景:
 * - 云控初始化设备数据库
 * - 本地脚本模板下发
 * - 新设备首次运行自动建库
 * - 数据库恢复 / 克隆
 *
 * 📌 特点:
 * - 自动删除旧数据库(避免结构冲突)
 * - 支持中文表名(如:脚本示例)
 * - 表结构 + 初始数据一键生成
 *
 * ⚠️ 注意:
 * - targetTableName 必须和 meta_script / meta_form 中保持一致
 * - 表字段必须与前端表单定义一致,否则会导致数据无法回显
 *
 * @param {string} dbPath 数据库路径(如:/sdcard/cloud/SQLite/本地脚本.db)
 * @param {string} targetTableName 业务表名(如:脚本示例)
 */
function createLocalScriptDB(dbPath, targetTableName) {
  // =========================
  // 0️⃣ 删除旧数据库
  // =========================
  var file = new java.io.File(dbPath);
  if (file.exists()) {
    file.delete();
    console.log("🗑️ 已删除旧数据库:", dbPath);
  }

  var db = sqlite.open(dbPath);

  try {
    // =========================
    // 1️⃣ 创建表结构
    // =========================

    /**
     * 👉 meta_script(脚本执行配置表)
     *
     * 作用:
     * - 存储脚本执行逻辑
     * - 绑定目标数据表(targetTable)
     *
     * 核心字段:
     * - scriptContent:脚本代码
     * - scriptConfig:执行配置(循环、延迟等)
     * - targetTable:关联业务表
     */
    db.execSQL(
      "CREATE TABLE meta_script (" +
        "id INTEGER NOT NULL PRIMARY KEY, " +
        "scriptContent TEXT, " +
        "scriptName TEXT, " +
        "scriptPath TEXT, " +
        "scriptConfig TEXT, " +
        "targetTable TEXT" +
        ")"
    );

    /**
     * 👉 meta_form(表单结构定义表)
     *
     * 作用:
     * - 定义前端表单结构(动态表单)
     * - 控制字段类型、校验规则、数据来源等
     *
     * 核心字段:
     * - data:完整 JSON 表单配置
     * - targetTable:对应数据存储表
     */
    db.execSQL(
      "CREATE TABLE meta_form (" +
        "id INTEGER NOT NULL PRIMARY KEY, " +
        "label TEXT, " +
        "data TEXT, " +
        "dict TEXT, " +
        "title TEXT, " +
        "targetTable TEXT" +
        ")"
    );

    /**
     * 👉 动态业务表(如:脚本示例)
     *
     * 作用:
     * - 存储用户填写的表单数据
     * - 与 meta_form 一一对应
     *
     * ⚠️ 注意:
     * 字段必须与 meta_form.data 中的 name 对应
     */
    db.execSQL(
      "CREATE TABLE " +
        targetTableName +
        " (" +
        "id INTEGER NOT NULL PRIMARY KEY, " +
        "label TEXT, " +
        "category_id INTEGER, " +
        "task_type_id INTEGER, " +
        "device_id INTEGER, " +
        "status INTEGER, " +
        "user_id INTEGER, " +
        "content TEXT, " +
        "created_at TEXT, " +
        "input TEXT, " +
        "textarea TEXT, " +
        "inputNumber INTEGER, " +
        "datePicker TEXT, " +
        "timePicker TEXT, " +
        "colorPicker TEXT, " +
        "rate INTEGER, " +
        "slider INTEGER, " +
        "switch INTEGER, " +
        "radio INTEGER, " +
        "checkbox TEXT, " +
        "image TEXT, " +
        "video TEXT, " +
        "isTemplate INTEGER, " +
        "templateName TEXT" +
        ")"
    );

    // =========================
    // 2️⃣ 插入数据
    // =========================

    /**
     * 👉 插入 meta_script(脚本执行逻辑)
     *
     * 逻辑:
     * - 从业务表中取一条 isTemplate = 0 的数据
     * - 作为“可执行脚本配置”
     */
    db.insert("meta_script", {
      scriptContent:
        '\n// 打开数据库\nlet db = sqlite.open("/sdcard/cloud/SQLite/本地脚本.db");\n\n// 查询一条 isTemplate = 0 的数据(实际业务:获取“可执行脚本配置”)\nlet scriptConfig = db.rawQuery(\n  "SELECT * FROM `脚本示例` WHERE isTemplate = ? LIMIT 1",\n  [0]\n).single();\n\n// 判断结果\nif (!scriptConfig) {\n  throw new Error("未找到可用的脚本配置(isTemplate = 0)");\n}\n\n// 关闭数据库\ndb.close();\n\n// 输出结果\nconsole.log("获取到的脚本配置参数:");\nconsole.log(scriptConfig);\n                ',
      scriptName: targetTableName,
      scriptPath: "",
      scriptConfig: '{"delay":1000,"loopTimes":1,"interval":200,"path":["/sdcard/cloud/script"]}',
      targetTable: targetTableName,
    });

    /**
     * 👉 插入业务数据(示例 + 模板)
     *
     * isTemplate:
     * - 0 → 可执行数据
     * - 1 → 模板数据
     */
    db.insert(targetTableName, {
      label: "任务示例:数据录入流程演示",
      category_id: 73,
      task_type_id: 1,
      device_id: 58870,
      status: 1,
      user_id: 382,
      content: "我是内容",
      created_at: "2026-04-05T01:39:36.000Z",
      input: "我是单行文本",
      textarea: "我是多行文本",
      inputNumber: 3,
      datePicker: "2026-04-05T01:39:36.000Z",
      timePicker: "2026-04-05T01:39:24.000Z",
      colorPicker: "#FC1D1D",
      rate: 4,
      slider: 51,
      switch: 1,
      radio: 3,
      checkbox: "[\n    2\n]",
      image:
        "http://47.94.105.29:66/storage/folder/username/20240422/030638/01.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/02.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/03.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/04.jpg",
      video:
        "http://47.94.105.29:66/storage/folder/username/20240422/030621/01.mp4,http://47.94.105.29:66/storage/folder/username/20240422/030622/02.mp4,http://47.94.105.29:66/storage/folder/username/20240422/030622/03.mp4",
      isTemplate: 0,
    });

    db.insert(targetTableName, {
      label: "模板示例:标准任务配置(可复用)",
      category_id: 73,
      task_type_id: 1,
      device_id: 58870,
      status: 1,
      user_id: 382,
      content: "我是内容(模板示例)",
      created_at: "2026-04-05T01:39:36.000Z",
      input: "我是单行文本(模板示例)",
      textarea: "我是多行文本(模板示例)",
      inputNumber: 3,
      datePicker: "2026-04-05T01:39:36.000Z",
      timePicker: "2026-04-05T01:39:24.000Z",
      colorPicker: "#FC1D1D",
      rate: 4,
      slider: 51,
      switch: 2,
      radio: 1,
      checkbox: "[\n    1\n]",
      image:
        "http://47.94.105.29:66/storage/folder/username/20240422/030638/01.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/02.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/03.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/04.jpg",
      video:
        "http://47.94.105.29:66/storage/folder/username/20240422/030621/01.mp4,http://47.94.105.29:66/storage/folder/username/20240422/030622/02.mp4,http://47.94.105.29:66/storage/folder/username/20240422/030622/03.mp4",
      isTemplate: 1,
      templateName: "模板示例",
    });

    /**
     * 👉 插入 meta_form(动态表单)
     *
     * ⚠️ 这里是核心:
     * 前端 UI 就是根据这个 JSON 渲染出来的
     */
    db.insert("meta_form", {
      id: 1,
      label: "脚本示例表",
      data: '{list:[{type:"input",control:{modelValue:"",placeholder:"请输入标题"},config:{},name:"label",formItem:{label:"标题",rules:[{required:true,message:"必填项",trigger:"blur"},{max:20,message:"长度不能超过20个字符",trigger:"blur"}]}},{type:"selectPlus",control:{modelValue:"",appendToBody:true,valueKey:"id",filterable:true,props:{label:"label",value:"id"}},options:[],config:{optionsType:1,optionsFun:"http://47.94.105.29:66/api/DataListCaseCategoryList",method:"post",addUrl:"http://47.94.105.29:66/api/addDataListCaseCategory",editUrl:"http://47.94.105.29:66/api/editDataListCaseCategory",deleteUrl:"http://47.94.105.29:66/api/deleteDataListCaseCategory"},name:"category_id",formItem:{label:"分类"}},{type:"select",control:{modelValue:"",appendToBody:true,placeholder:"请选择任务类型"},options:[{label:"任务类型1",value:1},{label:"任务类型2",value:2},{label:"任务类型3",value:3},{label:"任务类型4",value:4}],config:{optionsType:1,optionsFun:"http://47.94.105.29:66/api/getFieldTaskTypeIdDict",method:"post"},name:"task_type_id",formItem:{label:"任务类型"}},{type:"select",control:{modelValue:"",appendToBody:true},options:[],config:{optionsType:1,optionsFun:"http://180.76.145.80/api/getUserList",method:"post",label:"",value:"id"},name:"user_id",formItem:{label:"选择用户"}},{type:"select",control:{modelValue:"",appendToBody:true},options:[],config:{optionsType:1,optionsFun:"http://180.76.145.80/api/getDeviceAllList",method:"post",value:"id",label:""},name:"device_id",formItem:{label:"选择设备"}},{type:"input",control:{modelValue:"",placeholder:"请输入内容"},config:{},name:"content",formItem:{label:"内容"}},{type:"input",control:{modelValue:"",placeholder:"请输入单行文本"},config:{},name:"input",formItem:{label:"单行文本"}},{type:"textarea",control:{modelValue:"",placeholder:"请输入多行文本"},config:{},name:"textarea",formItem:{label:"多行文本"}},{type:"inputNumber",control:{modelValue:0},config:{},name:"inputNumber",formItem:{label:"计数器"}},{type:"datePicker",control:{modelValue:"",type:"datetime"},config:{},name:"datePicker",formItem:{label:"日期选择器"}},{type:"timePicker",control:{modelValue:""},config:{},name:"timePicker",formItem:{label:"时间选择器"}},{type:"colorPicker",control:{modelValue:""},config:{},name:"colorPicker",formItem:{label:"取色器"}},{type:"rate",control:{modelValue:0},config:{},name:"rate",formItem:{label:"评分"}},{type:"slider",control:{modelValue:0,min:0,max:100,step:5},config:{},name:"slider",formItem:{label:"滑块"}},{type:"switch",control:{modelValue:false,activeValue:1,inactiveValue:0},config:{},name:"switch",formItem:{label:"开关"}},{type:"radio",control:{modelValue:1},options:[{label:"选项1",value:1},{label:"选项2",value:2},{label:"选项3",value:3}],config:{optionsType:1,transformData:"number"},name:"radio",formItem:{label:"单选框组"}},{type:"checkbox",control:{modelValue:[]},options:[{label:"选项1",value:1},{label:"选项2",value:2},{label:"选项3",value:3}],config:{optionsType:0,transformData:"number"},name:"checkbox",formItem:{label:"多选框组"}},{type:"upload",control:{modelValue:"",action:"http://47.94.105.29:66/api/upload",multiple:true,limit:4,listType:"picture-card"},config:{tip:"",btnText:""},name:"image",formItem:{label:"图片/文件"}},{type:"upload",control:{modelValue:"",action:"http://47.94.105.29:66/api/upload",limit:4,drag:true},config:{},name:"video",formItem:{label:"视频文件"}}],form:{size:"default",labelWidth:"100px",width:"100%"},config:{submitCancel:true}}',
      dict: "",
      title: "label",
      targetTable: targetTableName,
    });

    console.log("✅ 本地脚本数据库创建完成:", dbPath);
  } catch (e) {
    console.error("❌ 创建失败:", e);
  } finally {
    db.close();
  }
}

使用脚本读写参数配置

在业务执行过程中,可通过脚本直接操作设备本地的 SQLite 数据库,实现配置读取与状态回写

支持的操作:

  • 查询配置(SELECT) 👉 获取当前执行参数(isTemplate = 0
  • 更新配置(UPDATE) 👉 回写执行状态 / 任务进度 / 执行结果

所有数据均存储于设备本地 .db 文件中,实现本地持久化,并支持云控后台同步查看。

通过 Bot.js Pro 提供的 sqlite 模块,可在脚本中完成数据库操作。

查询配置(获取执行参数)

用于获取当前“运行态配置”(即 isTemplate = 0 的数据)。

var scriptContent = getExecutableConfig("/sdcard/cloud/SQLite/本地脚本.db", "脚本示例");

if (scriptContent) {
  console.log("✅ 获取到的脚本配置参数:");
  console.log(JSON.stringify(scriptContent, null, 4));
} else {
  console.warn("⚠️ 未获取到可执行脚本配置!");
  console.warn("👉 可能原因:");
  console.warn("1️⃣ 表【脚本示例】不存在");
  console.warn("2️⃣ 表中没有 isTemplate = 0 的数据");
  console.warn("3️⃣ 数据库路径错误或文件不存在");
  console.warn("4️⃣ 数据库结构异常(字段缺失)");
}

/**
 * 获取【可执行脚本配置数据】
 *
 * 📌 功能说明:
 * 从指定数据库 + 表中,查询一条:
 * 👉 isTemplate = 0 的数据(即“可执行数据”)
 *
 * 📌 使用场景:
 * - 脚本执行前读取配置
 * - 动态任务参数获取
 * - 云控任务执行入口
 *
 * 📌 查询规则:
 * - 只取第一条(LIMIT 1)
 * - 如果没有数据 → 返回 null
 *
 * ⚠️ 注意:
 * - 表必须存在 isTemplate 字段
 * - 表名支持中文(已自动处理)
 *
 * @param {string} dbPath 数据库路径
 * @param {string} tableName 表名(如:脚本示例)
 *
 * @returns {Object|null} 返回数据对象,找不到返回 null
 */
function getExecutableConfig(dbPath, tableName) {
  var db = sqlite.open(dbPath);

  try {
    // 👉 查询 isTemplate = 0 的数据
    var result = db.rawQuery("SELECT * FROM `" + tableName + "` WHERE isTemplate = ? LIMIT 1", [0]).single();
    return result;
  } catch (e) {
    console.error("❌ 查询失败:", e);
    return null;
  } finally {
    db.close();
  }
}

更新配置(回写执行状态)

用于更新当前运行中的配置数据(同样基于 isTemplate = 0)。

var rows = updateExecutableConfig(
    "/sdcard/cloud/SQLite/本地脚本.db",
    "脚本示例",
    {
        status: 2,
        content: "执行完成"
    }
);

console.log("🔄 更新结果 rows =", rows);

/**
 * 更新【当前执行配置记录】
 *
 * 📌 功能说明:
 * 使用 SQLite 的 db.update 方法,
 * 更新表中满足条件的数据(固定条件:isTemplate = 0)
 *
 * 📌 适用场景:
 * - 更新任务执行状态(status)
 * - 写入执行结果(content)
 * - 更新运行过程数据(execCount / log 等)
 *
 * 📌 工作机制:
 * UPDATE tableName
 * SET data...
 * WHERE isTemplate = 0
 *
 * 📌 特点:
 * - 无需传入 id
 * - 自动定位当前执行中的记录(isTemplate = 0)
 * - 使用 db.update 原生 API,无需手写 SQL
 *
 * ⚠️ 约束条件:
 * - 表中必须存在 isTemplate 字段
 * - isTemplate = 0 的记录应唯一(否则会批量更新)
 *
 * @param {string} dbPath 数据库文件路径
 * @param {string} tableName 表名(支持中文表名)
 * @param {Object} data 需要更新的字段(键值对)
 * @return {number} 受影响的行数
 */
function updateExecutableConfig(dbPath, tableName, data) {

    var db = sqlite.open(dbPath);

    try {
        return db.update(
            tableName,
            data,
            "isTemplate = ?",
            [0]
        );
    } finally {
        db.close();
    }
}