App Inventor 2 云数据库配置教程 - Firebase/云数据库1/自建后台

本文 PHP 后端已配套 Node.js 与 Python 版本,见页面中的对应实现;全部后端源码与运行说明。

« 返回首页

App Inventor 2 云数据库配置教程 - Firebase/云数据库1/自建后台

在App Inventor 2中,如果你的应用需要在多台设备之间共享数据,或者需要将数据持久化存储在云端,就需要使用云数据库组件。本文将全面介绍App Inventor 2提供的云存储方案,并给出详细的配置步骤和实践代码。


一、云存储方案对比

App Inventor 2内置了多种云端数据存储组件,每种方案各有适用场景:

特性 云数据库1 Firebase数据库1 网络微数据库1(自建后台) Google Spreadsheet
底层技术 Redis服务器 Google Firebase Realtime DB 自定义Web服务API Google Sheets API
默认服务器 MIT提供默认服务器 需自行配置Firebase项目 需自行搭建 需配置Google账户
实时同步 ✅ 支持DataChanged事件 ✅ 实时数据监听 ❌ 需手动轮询 ❌ 需手动轮询
数据共享 ✅ 多用户共享 ✅ 多用户共享 ✅ 多用户共享 ✅ 多用户共享
离线支持 ❌ 需联网 ✅ 本地缓存+同步 ❌ 需联网 ❌ 需联网
列表操作 ✅ 原子追加/移除 ✅ 支持 ❌ 需自行实现 ✅ 读写行列
安全规则 Token认证 Firebase Rules 自行实现 Google权限管理
适用场景 通用云端存储、实时协作 实时聊天、排行榜 完全自定义业务逻辑 数据报表、表单收集
难度 ⭐ 简单 ⭐⭐ 中等 ⭐⭐⭐ 较高 ⭐⭐ 中等
费用 MIT免费(有限额) Firebase免费额度 服务器自费 Google免费额度

需要用户账号、图片等文件、按条件查询时,上面几种都很吃力,直接用 BaaS 拓展:国内用 CloudBase(腾讯云开发),海外或不想实名认证用 Supabase。Firebase 和 Google Spreadsheet 在国内访问不稳定。


二、云数据库1 —— MIT官方云数据库

2.1 简介

CloudDB是App Inventor 2内置的非可视化组件,基于Redis服务器实现。它允许应用用户之间共享数据。默认情况下,数据存储在MIT维护的服务器上,你也可以搭建自己的Redis服务器。

核心特点:

  • 开箱即用,默认配置即可直接使用
  • 支持实时数据变更通知(DataChanged事件)
  • 原子性列表操作,多设备并发安全
  • 支持自建Redis服务器

2.2 配置步骤(使用MIT默认服务器)

步骤1:添加组件

在App Inventor 2设计视图中,从左侧组件面板的「Storage」分类中拖拽 云数据库1 组件到屏幕上。

步骤2:配置属性

属性 设置值 说明
RedisServer DEFAULT 使用MIT默认服务器
RedisPort 6381 默认端口,无需修改
Token 不修改 系统自动填充认证令牌
UseSSL true 默认服务器必须开启SSL
ProjectID 自动生成 每个项目唯一标识

提示: 使用MIT默认服务器时,所有属性保持默认即可,无需手动配置。

2.3 配置步骤(使用自建Redis服务器)

如果你需要完全掌控数据,可以搭建自己的Redis服务器:

步骤1:安装Redis

# Ubuntu/Debian
sudo apt update
sudo apt install redis-server

# CentOS
sudo yum install redis

# Docker方式(推荐)
docker run -d --name redis-clouddb \
  -p 6381:6379 \
  -v redis-data:/data \
  redis:7 redis-server --appendonly yes --requirepass your_password_here

步骤2:配置Redis

编辑Redis配置文件(通常为 /etc/redis/redis.conf):

# 设置监听端口
port 6381

# 设置密码
requirepass your_secure_password

# 开启持久化
appendonly yes
appendfilename "appendonly.aof"

# 绑定地址(根据需要调整)
bind 0.0.0.0

步骤3:在App Inventor中配置

属性 设置值 说明
RedisServer 你的服务器IP或域名 如 192.168.1.100 或 redis.example.com
RedisPort 6381 与服务器端口一致
Token your_secure_password Redis服务器密码
UseSSL 根据服务器配置 如果服务器启用了SSL则为true
ProjectID 自定义 多项目可使用不同ProjectID隔离数据

2.4 数据读写积木块操作

存储数据

云数据库1.StoreValue("userName", "张三")

当存储完成后,会触发 云数据库1.UpdateDone(tag, operation) 事件。

读取数据

云数据库1.GetValue("userName", "")
when 云数据库1.GotValue(tag, value) {
  if tag == "userName" {
    标签1.Text = value
  }
}

注意: GetValue 是异步操作,不会直接返回值。你必须在 GotValue 事件中处理返回的数据。

监听数据变化

when 云数据库1.DataChanged(tag, value) {
  // 当任何设备修改了数据,所有连接的设备都会收到此事件
  标签_状态.Text = join("数据已更新:", tag, " = ", value)
}

列表操作

// 原子性追加元素到列表(多设备并发安全)
云数据库1.AppendValueToList("chatMessages", "你好!")
// 原子性获取并移除列表首元素(适合消息队列)
云数据库1.RemoveFirstFromList("taskQueue")
when 云数据库1.FirstRemoved(value) {
  标签_结果.Text = join("取出:", value)
}

获取所有标签

云数据库1.GetTagList()
when 云数据库1.TagList(value) {
  // value 是所有标签名的列表
  列表选择器1.Elements = value
}

删除数据

// 删除指定标签的数据
云数据库1.ClearTag("userName")
// 检查连接状态
标签_状态.Text = 云数据库1.CloudConnected()

2.5 完整示例:简易聊天应用

界面设计:

  • TextBox_Chat:输入聊天消息
  • 按钮_发送:发送按钮
  • Label_Messages:显示聊天记录

积木块逻辑:

// Screen初始化
when Screen1.Initialize() {
    云数据库1.GetValue("chatHistory", makeList())
}

// 发送消息
when 按钮_发送.Click() {
    云数据库1.AppendValueToList(
        "chatHistory",
        TextBox_Chat.Text
    )
    TextBox_Chat.Text = ""
}

// 收到历史消息
when 云数据库1.GotValue(tag, value) {
    if tag == "chatHistory" {
        Label_Messages.Text = join(value)
    }
}

// 实时监听新消息
when 云数据库1.DataChanged(tag, value) {
    if tag == "chatHistory" {
        Label_Messages.Text = join(value)
    }
}

三、Firebase数据库1 —— Google实时数据库

3.1 简介

FirebaseDB组件对接Google Firebase Realtime Database,提供实时数据同步能力。数据以JSON树形结构存储,支持离线缓存和自动同步。

核心特点:

  • Google级别的稳定性和扩展性
  • 真正的实时数据同步
  • 离线缓存,网络恢复后自动同步
  • 丰富的安全规则配置
  • 免费额度充足(Spark计划)

3.2 Firebase项目创建

步骤1:注册Firebase

  1. 访问 Firebase控制台
  2. 使用Google账户登录
  3. 点击「添加项目」
  4. 输入项目名称(如 my-appinventor-app)
  5. 选择是否启用Google Analytics(可选)
  6. 点击「创建项目」

步骤2:创建Realtime Database

  1. 在Firebase控制台左侧菜单中找到「Build」→「Realtime Database」
  2. 点击「Create Database」
  3. 选择数据库位置(建议选择离用户最近的区域)
  4. 选择安全规则模式:
    • 测试模式:允许所有读写(开发阶段使用)
    • 锁定模式:拒绝所有读写(生产环境推荐)

步骤3:获取配置信息

在Firebase项目设置中记录以下信息:

  • 数据库URL:格式为 https://your-project-id-default-rtdb.firebaseio.com/
  • Firebase Token:在Realtime Database的规则中配置

3.3 在App Inventor中配置FirebaseDB

步骤1:添加组件

在设计视图中,从「Storage」分类拖拽 Firebase数据库1 组件到屏幕。

步骤2:配置属性

属性 设置值 说明
FirebaseToken 从Firebase获取的Token 项目设置中的Web API Key
FirebaseURL 数据库URL https://xxx.firebaseio.com/
UseService false 使用Token认证方式

3.4 Firebase安全规则配置

在Firebase控制台的「Realtime Database」→「Rules」中配置安全规则:

开发/测试阶段规则(允许所有读写):

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

生产环境推荐规则(需要认证):

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    },
    "public": {
      ".read": true,
      ".write": "auth !== null"
    }
  }
}

⚠️ 安全警告: 测试模式规则会在30天后自动过期。生产环境务必配置适当的安全规则,避免数据泄露。

3.5 数据读写积木块操作

存储数据

Firebase数据库1.StoreValue("score", 100)
when Firebase数据库1.DataChanged(tag, value) {
  // 数据存储成功或变更时触发
}

读取数据

Firebase数据库1.GetValue("score", 0)
when Firebase数据库1.GotValue(tag, value) {
  if tag == "score" {
    标签_分数.Text = join("分数:", value)
  }
}

删除数据

Firebase数据库1.ClearTag("score")

追加到列表

Firebase数据库1.AppendValue("messages", "新消息")

移除列表首元素

Firebase数据库1.RemoveFirst("taskQueue")
when Firebase数据库1.FirstRemoved(value) {
  标签_结果.Text = value
}

3.6 完整示例:实时排行榜

界面设计:

  • 文本输入框_名称:输入玩家名
  • 文本输入框_分数:输入分数
  • Button_Submit:提交按钮
  • Label_Leaderboard:显示排行榜

积木块逻辑:

// 初始化加载排行榜
when Screen1.Initialize() {
    Firebase数据库1.GetValue("leaderboard", makeList())
}

// 提交分数
when Button_Submit.Click() {
    var entry = join(文本输入框_名称.Text, ":", 文本输入框_分数.Text)
    Firebase数据库1.AppendValue(
        "leaderboard",
        entry
    )
}

// 收到数据
when Firebase数据库1.GotValue(tag, value) {
    if tag == "leaderboard" {
        Label_Leaderboard.Text = join(value)
    }
}

// 实时更新
when Firebase数据库1.DataChanged(tag, value) {
    if tag == "leaderboard" {
        Label_Leaderboard.Text = join(value)
    }
}

四、网络微数据库1 —— 自建网络微数据库后台

4.1 简介

TinyWebDB是App Inventor 2中最灵活的云存储方案。它不绑定特定的云服务商,而是通过HTTP接口与你自己搭建的Web服务器通信。你可以使用PHP、Node.js、Python等任何后端技术来实现服务接口。

从下载源码到手机读写成功的完整步骤(自己电脑、云服务器两条路线),见《网络微数据库后台搭建教程》。

核心特点:

  • 完全自主可控,数据存储在自己的服务器
  • 可以使用MySQL、SQLite、MongoDB等任何数据库
  • 可以实现复杂的业务逻辑(用户认证、数据过滤等)
  • 可以部署在国内服务器(阿里云、腾讯云等),访问速度快
  • 支持PHP、Node.js、Python、Java等后端技术

4.2 网络微数据库1 API规范

TinyWebDB组件与后台服务之间通过标准HTTP POST请求通信,使用特定的JSON格式:

保存数据(storeavalue)

请求:

POST /api/storeavalue
Content-Type: application/x-www-form-urlencoded

tag=userName&value="张三"

value 是值的 JSON 文本(文本带引号,列表是 [...]),实际发送时会再做表单编码。服务端原样保存即可。

成功响应(返回 HTTP 200 即可,内容不检查):

["STORED", "userName", "\"张三\""]

获取数据(getvalue)

请求:

POST /api/getvalue
Content-Type: application/x-www-form-urlencoded

tag=userName

成功响应(数据存在):

["VALUE", "userName", "\"张三\""]

第三项放回保存时收到的 JSON 文本,组件会把它解析回原来的值。

成功响应(数据不存在):

["VALUE", "userName", ""]

4.3 在App Inventor中配置TinyWebDB

步骤1:添加组件

在设计视图中,从「Storage」分类拖拽 网络微数据库1 组件到屏幕。

步骤2:配置属性

属性 设置值 说明
ServiceURL http://你的服务器地址/api 你的后台服务URL

注意: ServiceURL 末尾不要加 /,组件会自动拼成 服务地址/storeavalue;多写一个 / 会变成 //storeavalue,下面的 Node.js 示例就匹配不上。如果使用HTTPS,确保服务器SSL证书有效。

4.4 PHP后台实现方案

方案一:基于文件的简易存储

以下PHP代码使用JSON文件存储数据,适合小型应用:

请求路径是 /tinywebdb/api/storeavalue、/tinywebdb/api/getvalue,需要让它们都交给 index.php 处理:Apache 在 api/ 目录放一个 .htaccess,写一行 FallbackResource /tinywebdb/api/index.php;Nginx 用 try_files $uri /tinywebdb/api/index.php;。

文件结构:

/var/www/html/tinywebdb/
├── api/
│   ├── index.php      # API入口
│   └── data/
│       └── store.json # 数据文件

api/index.php:

<?php
/**
 * 网络微数据库1 API - 文件存储版
 * 适用于小型应用,无需数据库
 */

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

$dataFile = __DIR__ . '/data/store.json';

// 确保数据目录存在
if (!is_dir(__DIR__ . '/data')) {
    mkdir(__DIR__ . '/data', 0755, true);
}

// 读取现有数据
function loadData($file) {
    if (file_exists($file)) {
        $content = file_get_contents($file);
        return json_decode($content, true) ?: [];
    }
    return [];
}

// 保存数据
function saveData($file, $data) {
    file_put_contents($file, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}

// 获取POST参数
$tag = isset($_POST['tag']) ? $_POST['tag'] : '';
$value = isset($_POST['value']) ? $_POST['value'] : '';

// 路由处理
$action = basename($_SERVER['REQUEST_URI']);

if ($action === 'storeavalue') {
    // 保存数据
    if (empty($tag)) {
        echo json_encode(["ERROR", "Tag cannot be empty"]);
        exit;
    }

    $data = loadData($dataFile);
    $data[$tag] = $value;
    saveData($dataFile, $data);

    echo json_encode(["STORED", $tag, $value]);

} elseif ($action === 'getvalue') {
    // 获取数据
    if (empty($tag)) {
        echo json_encode(["ERROR", "Tag cannot be empty"]);
        exit;
    }

    $data = loadData($dataFile);
    $resultValue = isset($data[$tag]) ? $data[$tag] : "";

    echo json_encode(["VALUE", $tag, $resultValue]);

} else {
    echo json_encode(["ERROR", "Unknown action: $action"]);
}
?>

方案二:基于MySQL的完整方案

适合生产环境,支持高并发:

数据库初始化(SQL):

CREATE DATABASE IF NOT EXISTS tinywebdb
  DEFAULT CHARACTER SET utf8mb4
  DEFAULT COLLATE utf8mb4_unicode_ci;

USE tinywebdb;

CREATE TABLE IF NOT EXISTS store (
    id INT AUTO_INCREMENT PRIMARY KEY,
    tag VARCHAR(255) NOT NULL,
    value TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY idx_tag (tag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

api/index.php:

<?php
/**
 * 网络微数据库1 API - MySQL存储版
 * 适用于生产环境
 */

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

// 数据库配置
$dbHost = 'localhost';
$dbName = 'tinywebdb';
$dbUser = 'tinywebdb_user';
$dbPass = 'your_secure_password';

// 可选:简易API密钥认证
$apiKey = 'your_api_key_here';
$enableAuth = false; // 设为 true 启用认证

// 认证检查
if ($enableAuth) {
    $requestKey = isset($_SERVER['HTTP_X_API_KEY']) ? $_SERVER['HTTP_X_API_KEY'] : '';
    if ($requestKey !== $apiKey) {
        http_response_code(401);
        echo json_encode(["ERROR", "Unauthorized"]);
        exit;
    }
}

// 数据库连接
try {
    $pdo = new PDO(
        "mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4",
        $dbUser,
        $dbPass,
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
} catch (PDOException $e) {
    echo json_encode(["ERROR", "Database connection failed"]);
    exit;
}

// 获取POST参数
$tag = isset($_POST['tag']) ? $_POST['tag'] : '';
$value = isset($_POST['value']) ? $_POST['value'] : '';

// 路由处理
$action = basename($_SERVER['REQUEST_URI']);

if ($action === 'storeavalue') {
    if (empty($tag)) {
        echo json_encode(["ERROR", "Tag cannot be empty"]);
        exit;
    }

    // 使用 INSERT ... ON DUPLICATE KEY UPDATE 实现 upsert
    $stmt = $pdo->prepare(
        "INSERT INTO store (tag, value) VALUES (:tag, :value)
         ON DUPLICATE KEY UPDATE value = :value2, updated_at = NOW()"
    );
    $stmt->execute([
        ':tag'   => $tag,
        ':value' => $value,
        ':value2'=> $value
    ]);

    echo json_encode(["STORED", $tag, $value]);

} elseif ($action === 'getvalue') {
    if (empty($tag)) {
        echo json_encode(["ERROR", "Tag cannot be empty"]);
        exit;
    }

    $stmt = $pdo->prepare("SELECT value FROM store WHERE tag = :tag");
    $stmt->execute([':tag' => $tag]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    $resultValue = $row ? $row['value'] : "";
    echo json_encode(["VALUE", $tag, $resultValue]);

} else {
    echo json_encode(["ERROR", "Unknown action: $action"]);
}
?>

MySQL 存储的 Node.js 与 Python 版本

使用上面的同一份建库建表 SQL,接口路径、字段和响应数组与 PHP 版相同。两个版本都使用参数化 SQL,并利用 tag 的唯一索引更新已有值。数据库配置用环境变量:

export DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=tinywebdb
export DB_USER=tinywebdb_user DB_PASS=你的数据库密码
# 可选:设置后启用 X-API-Key 认证;不设置时与 PHP 的 enableAuth=false 一致
export API_KEY=你的API密钥

用 Web 客户端访问时将 X-API-Key 放进请求头。网络微数据库没有设置自定义请求头的属性,因此启用该认证后请通过 Web 客户端发送表单请求。启动后 ServiceURL 为 http://服务器IP:3000/tinywebdb/api,末尾不加 /。

Node.js 对应实现

保存为 tinywebdb-mysql.cjs,在文件所在目录安装依赖并启动:

npm install express@5 mysql2
node tinywebdb-mysql.cjs

下载 tinywebdb-mysql.cjs

// npm install express@5 mysql2
const express = require('express');
const mysql = require('mysql2/promise');
const app = express();
const pool = mysql.createPool({host: process.env.DB_HOST || '127.0.0.1', port: Number(process.env.DB_PORT || 3306),
  user: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME || 'tinywebdb', charset: 'utf8mb4'});
app.use(express.urlencoded({extended: false}));
app.use((req, res, next) => { res.set('Access-Control-Allow-Origin', '*'); next(); });
app.post(['/api/:action', '/tinywebdb/api/:action'], async (req, res) => {
  // 设置 API_KEY 启用认证;客户端发送 X-API-Key 请求头。
  if (process.env.API_KEY && req.get('X-API-Key') !== process.env.API_KEY) return res.status(401).json(['ERROR', 'Unauthorized']);
  const {tag = '', value = ''} = req.body || {};
  if (!tag) return res.json(['ERROR', 'Tag cannot be empty']);
  try {
    if (req.params.action === 'storeavalue') {
      await pool.execute('INSERT INTO store (tag, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value = ?, updated_at = NOW()', [tag, value, value]);
      return res.json(['STORED', tag, value]);
    }
    if (req.params.action === 'getvalue') {
      const [rows] = await pool.execute('SELECT value FROM store WHERE tag = ?', [tag]);
      return res.json(['VALUE', tag, rows.length ? rows[0].value : '']);
    }
    res.json(['ERROR', 'Unknown action: ' + req.params.action]);
  } catch (error) { res.json(['ERROR', 'Database operation failed']); }
});
if (require.main === module) app.listen(Number(process.env.PORT || 3000), '0.0.0.0');
module.exports = {app, pool};
Python 对应实现

保存为 tinywebdb_mysql.py,在文件所在目录安装依赖并启动:

python3 -m pip install Flask PyMySQL
python3 tinywebdb_mysql.py

下载 tinywebdb_mysql.py

# python3 -m pip install Flask PyMySQL
import os
import pymysql
from flask import Flask, request, jsonify
app = Flask(__name__)
def connect():
    return pymysql.connect(host=os.environ.get('DB_HOST', '127.0.0.1'), port=int(os.environ.get('DB_PORT', 3306)),
                           user=os.environ['DB_USER'], password=os.environ['DB_PASS'], database=os.environ.get('DB_NAME', 'tinywebdb'),
                           charset='utf8mb4', autocommit=True)
@app.after_request
def cors(response):
    response.headers['Access-Control-Allow-Origin'] = '*'
    return response
@app.post('/api/<action>')
@app.post('/tinywebdb/api/<action>')
def api(action):
    if os.environ.get('API_KEY') and request.headers.get('X-API-Key') != os.environ['API_KEY']:
        return jsonify(['ERROR', 'Unauthorized']), 401
    tag, value = request.form.get('tag', ''), request.form.get('value', '')
    if not tag: return jsonify(['ERROR', 'Tag cannot be empty'])
    if action not in ('storeavalue', 'getvalue'): return jsonify(['ERROR', 'Unknown action: ' + action])
    connection = None
    try:
        connection = connect()
        with connection.cursor() as cursor:
            if action == 'storeavalue':
                cursor.execute('INSERT INTO store (tag, value) VALUES (%s, %s) ON DUPLICATE KEY UPDATE value = %s, updated_at = NOW()', (tag, value, value))
                return jsonify(['STORED', tag, value])
            cursor.execute('SELECT value FROM store WHERE tag = %s', (tag,))
            row = cursor.fetchone()
            return jsonify(['VALUE', tag, row[0] if row else ''])
    except (pymysql.MySQLError, KeyError):
        return jsonify(['ERROR', 'Database operation failed'])
    finally:
        if connection is not None: connection.close()
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 3000)))

4.5 Node.js 与 Python 文件存储方案

下面两个版本对应 4.4 的 PHP 文件存储版。请求是表单 POST,字段为 tag、value;value 是组件序列化后的 JSON 文本,服务端原样保存,因此字符串、数字、列表和字典都能往返。成功响应分别为 ["STORED", tag, value]、["VALUE", tag, value],不存在的 tag 返回空文本。

启动后设置 ServiceURL = http://服务器IP:3000/tinywebdb/api,末尾不加 /。也兼容原 Node.js 示例的 /api 路径。数据默认保存在脚本旁 data/store.json,可用 DATA_FILE 更改。文件版适合单进程教学,跨进程或多台服务器写入请用 4.4 的 MySQL 版。

Node.js 对应实现

保存为 tinywebdb-file.cjs,在文件所在目录安装依赖并启动:

npm install express@5
node tinywebdb-file.cjs

下载 tinywebdb-file.cjs

// npm install express@5
const express = require('express');
const fs = require('node:fs');
const path = require('node:path');
const app = express();
const file = path.resolve(process.env.DATA_FILE || path.join(__dirname, 'data', 'store.json'));
fs.mkdirSync(path.dirname(file), {recursive: true});
app.use(express.urlencoded({extended: false}));
app.use((req, res, next) => { res.set('Access-Control-Allow-Origin', '*'); next(); });
// 同时保留 PHP 的路径和原 Node.js 示例的 /api 路径。
app.post(['/api/:action', '/tinywebdb/api/:action'], (req, res) => {
  const {tag = '', value = ''} = req.body || {};
  if (!tag) return res.json(['ERROR', 'Tag cannot be empty']);
  // 同步读写在单个 Node.js 进程内串行执行;多进程请使用数据库版。
  const data = Object.assign(Object.create(null), fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {});
  if (req.params.action === 'storeavalue') {
    data[tag] = value; // value 是 JSON 文本,不在服务端再次解析
    fs.writeFileSync(file + '.tmp', JSON.stringify(data), 'utf8');
    fs.renameSync(file + '.tmp', file);
    return res.json(['STORED', tag, value]);
  }
  if (req.params.action === 'getvalue') return res.json(['VALUE', tag, Object.hasOwn(data, tag) ? data[tag] : '']);
  res.json(['ERROR', 'Unknown action: ' + req.params.action]);
});
app.use((err, req, res, next) => res.status(500).json(['ERROR', 'Storage failed']));
if (require.main === module) app.listen(Number(process.env.PORT || 3000), '0.0.0.0');
module.exports = app;

Python 对应实现

保存为 tinywebdb_file.py,在文件所在目录安装依赖并启动:

python3 -m pip install Flask
python3 tinywebdb_file.py

下载 tinywebdb_file.py

# python3 -m pip install Flask
import json
import os
import threading
from pathlib import Path
from flask import Flask, request, jsonify
app = Flask(__name__)
FILE = Path(os.environ.get('DATA_FILE', Path(__file__).parent / 'data' / 'store.json'))
FILE.parent.mkdir(parents=True, exist_ok=True)
LOCK = threading.Lock()  # 单进程多线程;多进程部署请使用数据库版
@app.after_request
def cors(response):
    response.headers['Access-Control-Allow-Origin'] = '*'
    return response
@app.post('/api/<action>')
@app.post('/tinywebdb/api/<action>')
def api(action):
    tag, value = request.form.get('tag', ''), request.form.get('value', '')
    if not tag: return jsonify(['ERROR', 'Tag cannot be empty'])
    try:
        with LOCK:
            data = json.loads(FILE.read_text(encoding='utf-8')) if FILE.exists() else {}
            if action == 'storeavalue':
                data[tag] = value  # value 是 JSON 文本,原样存取
                temporary = FILE.with_name(FILE.name + '.tmp')
                temporary.write_text(json.dumps(data, ensure_ascii=False), encoding='utf-8')
                temporary.replace(FILE)
                return jsonify(['STORED', tag, value])
            if action == 'getvalue': return jsonify(['VALUE', tag, data.get(tag, '')])
            return jsonify(['ERROR', 'Unknown action: ' + action])
    except (OSError, ValueError):
        return jsonify(['ERROR', 'Storage failed']), 500
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 3000)))

4.6 TinyWebDB数据读写积木块操作

存储数据

网络微数据库1.StoreValue("userName", "张三")
when 网络微数据库1.ValueStored() {
  // 数据存储成功
  标签_状态.Text = "保存成功"
}

读取数据

网络微数据库1.GetValue("userName")
when 网络微数据库1.GotValue(tag, valueFromWebDB) {
  if tag == "userName" {
    标签_名称.Text = valueFromWebDB
  }
}

注意: 与CloudDB/FirebaseDB类似,GetValue 是异步操作,必须在 GotValue 事件中处理返回数据。

错误处理

when 网络微数据库1.WebServiceError(message) {
  标签_错误.Text = join("网络错误:", message)
}

4.7 完整示例:用户登录注册系统

界面设计:

  • TextBox_Username:用户名输入
  • TextBox_Password:密码输入
  • Button_Register:注册按钮
  • Button_Login:登录按钮
  • 标签_状态:状态提示

积木块逻辑:

注册和登录都要先 GetValue,结果回到同一个 GotValue 事件里,所以用全局变量 mode 记住这次读取是为了注册还是登录。

global mode = ""

when Button_Register.Click() {
  if TextBox_Username.Text == "" or TextBox_Password.Text == "" {
    标签_状态.Text = "用户名和密码不能为空"
  } else {
    mode = "注册"
    网络微数据库1.GetValue(join("user:", TextBox_Username.Text))
  }
}

when Button_Login.Click() {
  mode = "登录"
  网络微数据库1.GetValue(join("user:", TextBox_Username.Text))
}

when 网络微数据库1.GotValue(tagFromWebDB, valueFromWebDB) {
  if mode == "注册" {
    if valueFromWebDB == "" {
      网络微数据库1.StoreValue(tagFromWebDB, TextBox_Password.Text)
      标签_状态.Text = "注册成功!"
    } else {
      标签_状态.Text = "用户名已存在"
    }
  } else if mode == "登录" {
    if valueFromWebDB == "" {
      标签_状态.Text = "用户名不存在"
    } else if valueFromWebDB == TextBox_Password.Text {
      标签_状态.Text = "登录成功!"
    } else {
      标签_状态.Text = "密码错误"
    }
  }
  mode = ""
}

这个示例把密码明文存在网络微数据库里,任何知道服务地址的人都能读到,只适合学习原理。正式的账号系统请用 CloudBase 或 Supabase 的认证组件,详见注册登录教程。


五、Web组件 —— 通用HTTP数据交互

除了以上专门的数据库组件,App Inventor 2还提供了 Web 组件,可以与任何HTTP API进行数据交互,适合与自建 PHP/Node.js/Python 服务器进行复杂数据交换。

5.1 发送GET请求

// 设置请求URL
Web客户端1.Url = "http://你的服务器/api/getData.php?key=value"
Web客户端1.Get()
when Web客户端1.GotText(responseCode, responseContent) {
  if responseCode == 200 {
    标签_结果.Text = responseContent
  }
}

5.2 发送POST请求

// 设置请求URL和参数
Web客户端1.Url = "http://你的服务器/api/saveData.php"
Web客户端1.RequestHeaders = list(list("Content-Type", "application/x-www-form-urlencoded"))
Web客户端1.PostText(join("name=", Web客户端1.UriEncode(文本输入框_名称.Text), "&score=", Web客户端1.UriEncode(文本输入框_分数.Text)))
when Web客户端1.GotText(responseCode, responseContent) {
  if responseCode == 200 {
    标签_状态.Text = "提交成功"
  }
}

5.3 PHP后台接收示例

<?php
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');

$method = $_SERVER['REQUEST_METHOD'];

if ($method === 'GET') {
    $key = isset($_GET['key']) ? $_GET['key'] : '';
    // 处理GET请求,查询数据
    $result = ["status" => "ok", "data" => "查询结果"];
    echo json_encode($result, JSON_UNESCAPED_UNICODE);

} elseif ($method === 'POST') {
    $name = isset($_POST['name']) ? $_POST['name'] : '';
    $score = isset($_POST['score']) ? $_POST['score'] : '';
    // 处理POST请求,保存数据
    $result = ["status" => "ok", "message" => "保存成功"];
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
}
?>

5.4 Node.js 与 Python 接收示例

下面对应 5.3 的 PHP 示例,保留 /api/getData.php?key=… 和 /api/saveData.php 路径。POST 使用表单字段 name、score,返回同样的 JSON。将网址中的主机改成 服务器IP:3000 即可。示例只展示参数读取和响应,查询、保存业务请写在注释标记的位置。

Node.js 对应实现

保存为 web-data.cjs,在文件所在目录安装依赖并启动:

npm install express@5
node web-data.cjs

下载 web-data.cjs

// npm install express@5
const express = require('express');
const app = express();
app.use(express.urlencoded({extended: false}));
app.use((req, res, next) => { res.set('Access-Control-Allow-Origin', '*'); next(); });
app.get('/api/getData.php', (req, res) => {
  const key = req.query.key || ''; // 在这里按 key 查询自己的数据
  res.json({status: 'ok', data: '查询结果'});
});
app.post('/api/saveData.php', (req, res) => {
  const {name = '', score = ''} = req.body || {}; // 在这里校验并保存 name、score
  res.json({status: 'ok', message: '保存成功'});
});
if (require.main === module) app.listen(Number(process.env.PORT || 3000), '0.0.0.0');
module.exports = app;

Python 对应实现

保存为 web_data.py,在文件所在目录安装依赖并启动:

python3 -m pip install Flask
python3 web_data.py

下载 web_data.py

# python3 -m pip install Flask
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.after_request
def cors(response):
    response.headers['Access-Control-Allow-Origin'] = '*'
    return response
@app.get('/api/getData.php')
def get_data():
    key = request.args.get('key', '')  # 在这里按 key 查询自己的数据
    return jsonify(status='ok', data='查询结果')
@app.post('/api/saveData.php')
def save_data():
    name, score = request.form.get('name', ''), request.form.get('score', '')
    # 在这里校验并保存 name、score
    return jsonify(status='ok', message='保存成功')
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 3000)))

六、安全最佳实践

6.1 数据传输安全

措施 说明
使用HTTPS 生产环境务必启用SSL证书(Let’s Encrypt免费获取)
API密钥认证 自建后台应实现API Key验证
输入验证 后台必须验证和过滤所有输入数据
速率限制 防止恶意请求,建议每IP每分钟限制请求数

6.2 Firebase安全规则进阶

{
  "rules": {
    // 公开只读数据
    "public": {
      ".read": true,
      ".write": false
    },
    // 已认证用户可读写
    "users": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid",
        ".write": "auth != null && auth.uid == $uid"
      }
    },
    // 限流:每分钟最多10次写入
    "messages": {
      ".read": true,
      ".write": "auth != null && newData.exists()",
      "$messageId": {
        ".validate": "newData.hasChildren(['text', 'timestamp'])"
      }
    }
  }
}

6.3 自建后台安全加固

Nginx反向代理配置示例:

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    # 限流
    limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;

    location /api/ {
        limit_req zone=api burst=10 nodelay;
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

七、方案选择建议

根据不同的应用场景,推荐以下选择策略:

场景 推荐方案 理由
课堂演示、快速原型 网络微数据库1(国内免费服务)或 云数据库1(默认服务器) 零搭建;默认服务器为共享测试用途,数据可能被清理
实时聊天/协作 云数据库1 DataChanged 事件实时推送
排行榜/成绩统计 云数据库1 原子列表操作,多设备并发安全
用户注册登录 CloudBase / Supabase 内建账号体系,密码不经过你的数据库
图片、文件上传与展示 CloudBase / Supabase 的存储组件 上传后拿 URL 直接给图像组件显示
按条件查询、排序、分页 CloudBase / Supabase 的数据库组件 网络微数据库只能按标签取值
国内用户为主、要自主可控 自建后台(网络微数据库协议 或 Web组件 + MySQL) 部署在国内服务器,访问速度快
需要复杂业务逻辑 Web组件 + 自建后台,或 CloudBase 云函数 逻辑放在服务端

八、常见问题(FAQ)

Q1:CloudDB的MIT默认服务器有容量限制吗?

MIT默认服务器是共享资源,适合开发和测试。对于生产环境或高并发应用,建议自建Redis服务器。

Q2:Firebase在国内能正常访问吗?

Firebase服务在国内访问可能不稳定。如果目标用户在国内,需要账号、数据库和文件存储一整套能力时用 CloudBase;只存简单数据用 CloudDB(自建Redis)或 TinyWebDB(自建后台)。Supabase 国内可以访问,速度偏慢,可作为 Firebase 的平替。

Q3:TinyWebDB的ServiceURL可以配置多个吗?

每个TinyWebDB组件只能配置一个ServiceURL。如果需要连接多个服务,可以添加多个TinyWebDB组件。

Q4:如何在不同应用之间共享CloudDB数据?

CloudDB的数据隔离基于ProjectID。要跨应用共享数据,可以使用相同的Token值。将一个应用的Token复制到另一个应用的CloudDB组件中即可实现数据共享。

Q5:自建后台可以用云服务器吗?

可以。阿里云ECS、腾讯云CVM、华为云ECS等国内云服务器都可以部署TinyWebDB后台。推荐使用Ubuntu系统,搭配Nginx反向代理和Let’s Encrypt免费SSL证书。


九、参考资源


© 2024 App Inventor 2 中文网。本文基于MIT官方文档编写,仅供学习参考。

« 返回首页

文档反馈