App Inventor 2 使用MongoDB作为后端数据库

一句话结论:MongoDB 以类 JSON 文档存储数据;App 需通过后端接口访问,本页介绍在宝塔面板安装配置 MongoDB 并接入。

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

« 返回首页

MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

宝塔安装mongodb

  1. 点击软件商店,搜索mongodb进行安装

  2. 找到安装好的mongodb进行设置,将配置文件bindIp改为0.0.0.0

  3. 安全 防火墙打开端口27017

  4. 云服务器安全组入方向开放端口27017

  5. 浏览器输入http://公网ip:27017 出现“It looks like you are trying to access MongoDB over HTTP on the native driver port.”说明配置成功

  6. 终端连接服务器

    cd /www/server/mongodb
    mongo
    use admin
    db.createUser({user:'root',pwd:'root',roles:['root']}) //这里可以使用自己的用户名和密码。
    db.auth('root','root') //返回1成功
    
  7. 电脑可以安装mongodbCompass,可以在本地查看、操作mongodb的数据

宝塔安装php7并开启网站

  1. 具体安装步骤略过
  2. 服务器要支持php,本例中需要是php7以上。
  3. 将php源码文件上传到服务器,并配置好php中的连接字符串。
  4. 在浏览器中输入http://你的网址/mongodb7.php,如果显示{“error”:"‘collection’ is missing"}说明配置成功。

使用Web组件操作数据

20250303_122428.png 20250303_122440.png 20250303_122453.png 20250303_122504.png

请求方式:

GET或者POST,推荐POST

参数列表:

  1. 插入数据

    参数名 必填 类型 用途 举例
    action yes String 请求类型 必须是insert
    collection yes String 表名 collection
    document yes jsonArray 数据 [{“id”:1,“x”:25,“y”:34},{“id”:2,“x”:52,“y”:37}]
  2. 查询数据

    参数名 必填 类型 用途 举例
    action yes String 请求类型 必须是query
    collection yes String 表名
    filter no jsonObject 符合的条件 {“x”:{"$gt":5}},详细见下方说明
    sort no String 排序字段 -x (负号表示逆序,正序不用加符号)
    key no String 返回字段 -id 或者x,y 正号负号不能同时使用
    limit no number 限制数据个数 10
    skip no number 跳过数据个数 10
  3. 更新数据

    参数名 必填 类型 用途 举例
    action yes String 请求类型 必须是update
    collection yes String 表名
    filter yes jsonObject 符合的条件 {“x”:{"$gt":5}} 详细见下方说明
    newobj yes jsonObject 新的数据 {"$set":{“x”:2}} 详细见下方说明
    upsert no boolean 是否自动添加数据 若为真,且没有符合条件的记录时,会自动添加
  4. 删除数据

    参数名 必填 类型 用途 举例
    action yes String 请求类型 必须是delete
    collection yes String 表名
    filter yes jsonObject 符合的条件 {“x”:{"$gt":5}} 详细见下方说明

常用查询条件filter

示例 表示意义
{“x”:2} 等于
{“x”:{"$lt":10}} 小于
{“x”:{"$lte":10}} 小于等于
{“x”:{"$gt":10}} 大于
{“x”:{"$gte":10}} 大于等于
{“x”:{"$ne":10}} 不等于
{“x”:{"$gt":5, “$lt”:10}} 并且
{"$or":[{“id”:{"$lt":5}},{“id”:{"$gt":8}}]} 或者
{“id”:{"$in":[1,3,5]}} 在数组中
{“id”:{"$nin":[1,3,5]}} 不在数组中
{“name”:{"$regex":“wang”}} 文本包含
{“name”:{"$regex":"^wang"}} 文本以xxx开始
{“name”:{"$exists":true}} 是否有某个字段

常用更新数据newobj

示例 意义
{"$set":{“x”:2}} 将x的值设为2,若不存在就创建
{"$unset":{“x”:1}} 删除某个字段
{"$inc":{“x”:2}} 将x增加2, 只能是数字类型
{"$push":{“x”:5}} 向x中追加一个数。x必须是数组类型
{"$pull":{“x”:5}} 将x中等于5的值删除。
{"$rename":{“x”:“y”}} 将字段x名称修改为y

返回数据

返回数据为json格式。

  1. 若出错,会有error字段

  2. 若没有出错,总有count字段和action字段

  3. 若是query操作,还有个records字段。

附上php后端源码

<?php

/*
 * Writen by: Kevinkun
 * Date: 26/10/2022
 * Contact: wangsk789@qq.com
 */

//error_reporting(0);

$connectString = "mongodb://root:root@localhost:27017";//change this to your connectString

try{
    $manager = new MongoDB\Driver\Manager($connectString);  

    $result = [];


    // collection
    if(!isset($_REQUEST["collection"])){
        $result["error"]="'collection' is missing";
        die(json_encode($result));
    }
    $col = $_REQUEST["collection"];
    if(strpos($col,".")==false){
        $col = "db." . $col;
    }

    //action
    $action =strtolower(isset($_REQUEST["action"])?$_REQUEST["action"]:"");

    if ($action == "query") {
        //filter
        $filter = isset($_REQUEST["filter"])?$_REQUEST["filter"]:"{}";
        $filter = json_decode($filter);
        if(!$filter){
            $result["error"]="'filter' is not a well-formated json";
            die(json_encode($result));
        }
        $filter = (array)$filter;
        if(array_key_exists("_id", $filter)){
            $id = $filter["_id"];
            $filter["_id"] = new \MongoDB\BSON\ObjectId($id);
        }

        //sort
        $sort = array();    
        $order = isset($_REQUEST["sort"])?$_REQUEST["sort"]:"_id";
        $order = explode(",",$order);
        foreach($order as $k){
            if(substr($k,0,1)=="-"){
                 $sort[substr($k,1)] = -1;
            }else if(substr($k,0,1)=="+"){
                 $sort[substr($k,1)] = 1;
            }else{
                 $sort[$k] = 1;
            }
        }

        //keys
        $projection = array("_id"=>0);
        $keys = isset($_REQUEST["key"])?$_REQUEST["key"]:"-_id";
        $keys = explode(",",$keys);
        foreach($keys as $k){
            if(substr($k,0,1)=="-"){
             $projection[substr($k,1)] = 0;
            }else if(substr($k,0,1)=="+"){
                 $projection[substr($k,1)] = 1;
            }else{
                 $projection[$k] = 1;
            }
        }

        //limit and skip
        $limit = isset($_REQUEST["limit"])?$_REQUEST["limit"]:"0";
        $skip = isset($_REQUEST["skip"])?$_REQUEST["skip"]:"0";


        $options = [
            'projection' => $projection,
            'sort' => $sort,
            'limit' => $limit,
            'skip' => $skip,
        ];

        $query = new MongoDB\Driver\Query($filter, $options);
        $cursor = $manager->executeQuery("$col", $query);
        $records = $cursor->toArray();

        $result["records"] =  $records;
        $result["count"] = count($records);
        $result["action"]= "query";
        echo json_encode($result);

    }else if ($action == "insert") {
        $bulk = new MongoDB\Driver\BulkWrite;
        $body = isset($_REQUEST["document"])?$_REQUEST["document"]:"[]";
        if(strpos($body, "[") !== 0){
            $body = "[".$body."]";
        }
        $body = json_decode($body);
        if(!$body){
            $result["error"]="'document' is not a well-formated jsonArray";
            die(json_encode($result));
        }
        foreach ($body as $b){
            if(is_object($b)){
                $bulk->insert($b);
            }
        }

        $postresult = $manager->executeBulkWrite("$col", $bulk);
        $result["count"]= $postresult->getInsertedCount();
        $result["action"]= "insert";
        echo json_encode($result);

    }else if ($action == "delete") {
        //filter
        $filter = isset($_REQUEST["filter"])?$_REQUEST["filter"]:"";
        $filter = json_decode($filter);
        if(!$filter){
            $result["error"]="'filter' is not a well-formated json";
            die(json_encode($result));
        }
        $filter = (array)$filter;
        if(array_key_exists("_id", $filter)){
            $id = $filter["_id"];
            $filter["_id"] = new \MongoDB\BSON\ObjectId($id);
        }

        $bulk = new MongoDB\Driver\BulkWrite;
        $bulk->delete($filter, ['limit' => 0]); 
        $writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);
        $delresult = $manager->executeBulkWrite("$col", $bulk, $writeConcern);
        $result["count"]= $delresult->getDeletedCount();
        $result["action"]= "delete";
        echo json_encode($result);

    }else if ($action == "update") {
        //filter
        $filter = isset($_REQUEST["filter"])?$_REQUEST["filter"]:"";
        $filter = json_decode($filter);
        if(!$filter){
            $result["error"]="'filter' is not a well-formated json";
            die(json_encode($result));
        }
        $filter = (array)$filter;
        if(array_key_exists("_id", $filter)){
            $id = $filter["_id"];
            $filter["_id"] = new \MongoDB\BSON\ObjectId($id);
        }

        //newobj
        $newobj = isset($_REQUEST["newobj"])?$_REQUEST["newobj"]:"[]";
        $newobj = json_decode($newobj);
        if(!$newobj){
                $result["error"]="'newobj' is not a well-formated json";
                die(json_encode($result));
        }

        $upsert = isset($_REQUEST["upsert"])?$_REQUEST["upsert"]:"";
        $upsert = strtolower($upsert)=="true"?true:false;

        $bulk = new MongoDB\Driver\BulkWrite;
        $bulk->update($filter, $newobj, ['multi'=>true, 'upsert'=>$upsert]);
        $writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);
        $updateresult = $manager->executeBulkWrite("$col", $bulk, $writeConcern);
        $result["count"]= $updateresult->getModifiedCount() + $updateresult->getUpsertedCount();
        $result["action"]= "update";
        echo json_encode($result);


    }else{
        $result["error"]="'action' is not recoginzed";
        die(json_encode($result));
    }

}catch (MongoDB\Driver\Exception\Exception $e) {
    $result["error"]= $e->getMessage();
    die(json_encode($result));
}
?>

Node.js 与 Python 后端

两个版本保留 /mongodb7.php 路径,以及上文 action、collection、filter、document、sort、key、limit、skip、newobj、upsert 参数,支持 GET 和表单 POST(POST 同名参数优先)。只需将 Web 客户端网址改为 http://服务器IP:3000/mongodb7.php。

  • collection=students 使用 db.students;collection=school.students 使用指定数据库。
  • query 返回 records、count、action;insert、delete、update 返回 count、action;错误仍返回 error 字段。
  • filter 中的 _id 使用 24 位十六进制文本,后端转为 ObjectId;查询结果里的 ObjectId 仍表示为 {"$oid":"…"}。
  • newobj 用更新操作符,例如 {"$set":{"score":95}};update 更新所有匹配记录,upsert=true 时允许新增,count 为修改数加新增数。
  • 分页的 limit=0 表示不限制;投影字段不能混用包含与排除(_id 除外)。

先设置数据库连接串,使用自己的账号密码;数据库只需允许后端服务连接:

export MONGODB_URI='mongodb://用户名:密码@127.0.0.1:27017/?authSource=admin'

本接口与 PHP 示例一样开放集合操作,适合在自己的测试环境学习协议;对外提供服务时应接入自己的鉴权并限制允许操作的集合。

Node.js 对应实现

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

npm install express@5 mongodb
node mongodb.cjs

下载 mongodb.cjs

// npm install express@5 mongodb
const express = require('express');
const {MongoClient, ObjectId, BSON} = require('mongodb');
const app = express();
const client = new MongoClient(process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017', {serverSelectionTimeoutMS: 5000});
app.use(express.urlencoded({extended: false}));
function object(text, name) {
  const value = JSON.parse(text);
  if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`'${name}' is not a well-formated json`);
  return value;
}
app.all('/mongodb7.php', async (req, res) => {
  const p = {...req.query, ...(req.body || {})}; // GET 或表单 POST;POST 优先
  if (!p.collection) return res.json({error: "'collection' is missing"});
  const action = String(p.action || '').toLowerCase();
  try {
    const parts = String(p.collection).split('.');
    const database = parts.length > 1 ? parts.shift() : 'db';
    const collection = client.db(database).collection(parts.join('.'));
    if (!['query', 'insert', 'delete', 'update'].includes(action)) throw new Error("'action' is not recoginzed");
    let count;
    if (action === 'insert') {
      let documents = JSON.parse(p.document || '[]');
      if (!Array.isArray(documents)) documents = [documents];
      if (!documents.length || documents.some(d => !d || typeof d !== 'object' || Array.isArray(d))) throw new Error("'document' is not a well-formated jsonArray");
      count = (await collection.insertMany(documents)).insertedCount;
    } else {
      const filter = object(p.filter || (action === 'query' ? '{}' : ''), 'filter');
      if (Object.hasOwn(filter, '_id')) filter._id = new ObjectId(filter._id);
      if (action === 'query') {
        const sort = {}, projection = {_id: 0};
        for (const key of String(p.sort || '_id').split(',')) sort[key.replace(/^[+-]/, '')] = key.startsWith('-') ? -1 : 1;
        for (const key of String(p.key || '-_id').split(',')) projection[key.replace(/^[+-]/, '')] = key.startsWith('-') ? 0 : 1;
        const limit = Number(p.limit || 0), skip = Number(p.skip || 0);
        if (![limit, skip].every(n => Number.isInteger(n) && n >= 0)) throw new Error('Invalid limit or skip');
        const records = await collection.find(filter, {projection, sort, limit, skip}).toArray();
        // 保留 PHP BSON JSON 的 {"$oid": ...} 等类型表示。
        return res.type('application/json').send(BSON.EJSON.stringify({records, count: records.length, action}, {relaxed: true}));
      }
      if (action === 'delete') count = (await collection.deleteMany(filter)).deletedCount;
      else {
        const update = object(p.newobj || '{}', 'newobj');
        if (!Object.keys(update).some(k => k.startsWith('$'))) throw new Error('newobj 请使用 $set 等更新操作符');
        const result = await collection.updateMany(filter, update, {upsert: String(p.upsert || '').toLowerCase() === 'true'});
        count = result.modifiedCount + result.upsertedCount;
      }
    }
    res.json({count, action});
  } catch (error) { res.json({error: error.message}); }
});
if (require.main === module) app.listen(Number(process.env.PORT || 3000), '0.0.0.0');
module.exports = {app, client};

Python 对应实现

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

python3 -m pip install Flask pymongo
python3 mongodb.py

下载 mongodb.py

# python3 -m pip install Flask pymongo
import json
import os
from bson import ObjectId, json_util
from bson.errors import InvalidId
from flask import Flask, request, jsonify, Response
from pymongo import MongoClient
from pymongo.errors import PyMongoError
app = Flask(__name__)
client = MongoClient(os.environ.get('MONGODB_URI', 'mongodb://127.0.0.1:27017'), serverSelectionTimeoutMS=5000)
def object_value(text, name):
    value = json.loads(text)
    if not isinstance(value, dict): raise ValueError(f"'{name}' is not a well-formated json")
    return value
@app.route('/mongodb7.php', methods=['GET', 'POST'])
def api():
    p = dict(request.args)
    p.update(request.form)
    if not p.get('collection'): return jsonify(error="'collection' is missing")
    action = p.get('action', '').lower()
    try:
        database, separator, name = p['collection'].partition('.')
        collection = client[database if separator else 'db'][name if separator else database]
        if action not in ('query', 'insert', 'delete', 'update'): raise ValueError("'action' is not recoginzed")
        if action == 'insert':
            documents = json.loads(p.get('document', '[]'))
            if not isinstance(documents, list): documents = [documents]
            if not documents or any(not isinstance(d, dict) for d in documents):
                raise ValueError("'document' is not a well-formated jsonArray")
            count = len(collection.insert_many(documents).inserted_ids)
        else:
            query = object_value(p.get('filter', '{}' if action == 'query' else ''), 'filter')
            if '_id' in query: query['_id'] = ObjectId(query['_id'])
            if action == 'query':
                sort = [(k.lstrip('+-'), -1 if k.startswith('-') else 1) for k in p.get('sort', '_id').split(',')]
                projection = {'_id': 0}
                projection.update({k.lstrip('+-'): 0 if k.startswith('-') else 1 for k in p.get('key', '-_id').split(',')})
                limit, skip = int(p.get('limit', 0)), int(p.get('skip', 0))
                if min(limit, skip) < 0: raise ValueError('Invalid limit or skip')
                records = list(collection.find(query, projection).sort(sort).skip(skip).limit(limit))
                return Response(json_util.dumps(dict(records=records, count=len(records), action=action), ensure_ascii=False), mimetype='application/json')
            if action == 'delete': count = collection.delete_many(query).deleted_count
            else:
                update = object_value(p.get('newobj', '{}'), 'newobj')
                if not any(k.startswith('$') for k in update): raise ValueError('newobj 请使用 $set 等更新操作符')
                result = collection.update_many(query, update, upsert=p.get('upsert', '').lower() == 'true')
                count = result.modified_count + int(result.upserted_id is not None)
        return jsonify(count=count, action=action)
    except (ValueError, TypeError, InvalidId, PyMongoError) as error:
        return jsonify(error=str(error))
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 3000)))
文档反馈