feat: build.sh 按 webapp-deploy 技能重写一键部署(venv+clone全模块+四步安装+/healthz门禁);加 start/stop;/healthz钩子;密码加密

This commit is contained in:
agent.develop 2026-08-28 16:15:37 +08:00
parent 93dadab835
commit 8057ba2888
6 changed files with 362 additions and 78 deletions

View File

@ -53,5 +53,22 @@ def init():
load_world_sync() load_world_sync()
# ── /healthz 健康检查(部署门禁:GET /healthz 必须 200)──
# ahapp_built 钩子在应用构建完成时由 ConfiguredServer 触发,拿到 aiohttp app 注册路由。
from appPublic.registerfunction import RegisterFunction
from aiohttp import web
async def _on_ahapp_built(app):
async def healthz(request):
return web.json_response({"status": "ok", "app": "scense", "port": 9380})
app.router.add_get("/healthz", healthz)
RegisterFunction().register("ahapp_built", _on_ahapp_built)
if __name__ == "__main__": if __name__ == "__main__":
webapp(init) webapp(init)

171
build.sh Normal file → Executable file
View File

@ -1,73 +1,88 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail
# ============================================================================= # =============================================================================
# 唤景平台世界模式(scense)一键部署脚本 —— 测试环境 # 唤景平台世界模式(scense)一键部署脚本 —— 测试环境
# 部署目录 ~/scense_app,应用端口 9380,数据库 localhost:3306/scense (test/test123) # 部署目录 ~/scense_app,应用端口 9380,数据库 localhost:3306/scense
# 规范:产线技能库 webapp-deploy(含「模块安装四步」)
# #
# 按 web-application-spec 的 8 步完整集成: # 步骤:
# 1) 代码导入(git clone 模块到 pkgs/) 2) 依赖安装(pip install -e) # 0) venv
# 3) 前端软链 wwwroot/{模块} 4) i18n 合并 # 1) 创建 pkgs/,全部模块 git clone 进来(基础共享包 + 6 业务模块;禁止 mv 源码)
# 5) 建表(json2ddl) 6) CRUD 生成(xls2ui) # 2) pip install 全部模块(-e,失败即退出)
# 7) 初始化数据 8) 运行挂载(load_{模块}) # 3) bricks 前端构建 + wwwroot/bricks 软链
# 4) 建库 + 基础框架表与初始业务表(scripts/ddl.sql,幂等 IF NOT EXISTS)
# 5) i18n 合并
# 6) 逐模块四步安装:① models/json2ddl 建表 ② json/build.sh(xls2ui) ③ wwwroot 软链 ④ 数据导入
# 7) 应用代码同步 + runtime 目录
# 8) 重启 + 健康检查(/healthz 必须 200,否则 exit 1)
# 硬性约束:set -euo pipefail,任何一步失败立即终止,禁止带病启动。
# ============================================================================= # =============================================================================
set -euo pipefail
APP_NAME="scense"
APP_PORT=9380
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
DEPLOY_DIR="${DEPLOY_DIR:-$HOME/scense_app}" DEPLOY_DIR="${DEPLOY_DIR:-$HOME/scense_app}"
VENV="${DEPLOY_DIR}/venv"
APP_PORT="${APP_PORT:-9380}"
DB_HOST="${DB_HOST:-localhost}" DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-3306}" DB_PORT="${DB_PORT:-3306}"
DB_NAME="${DB_NAME:-scense}" DB_NAME="${DB_NAME:-scense}"
DB_USER="${DB_USER:-test}" DB_USER="${DB_USER:-test}"
DB_PASSWORD="${DB_PASSWORD:-test123}" DB_PASSWORD="${DB_PASSWORD:-test123}"
GIT_HTTPS="https://git.opencomputing.cn/yumoqing"
# 基础模块(框架层)与业务模块 FOUNDATION_PKGS="apppublic sqlor ahserver rbac xls2ddl appbase bricks-for-python bricks"
FOUNDATION_MODULES="apppublic sqlor ahserver bricks"
DB_MODULES="appbase rbac"
BUSINESS_MODULES="world scene entity script_engine world_snapshot world_sync" BUSINESS_MODULES="world scene entity script_engine world_snapshot world_sync"
echo "==> [1/8] sync code" APP_SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
mkdir -p "${DEPLOY_DIR}/app" "${DEPLOY_DIR}/conf" "${DEPLOY_DIR}/wwwroot" \ mkdir -p "${DEPLOY_DIR}"
"${DEPLOY_DIR}/files" "${DEPLOY_DIR}/logs" "${DEPLOY_DIR}/pkgs"
rsync -avz --delete \
"${APP_DIR}/app/" "${DEPLOY_DIR}/app/" \
"${APP_DIR}/conf/" "${DEPLOY_DIR}/conf/" \
"${APP_DIR}/wwwroot/" "${DEPLOY_DIR}/wwwroot/" \
"${APP_DIR}/scripts/" "${DEPLOY_DIR}/scripts/"
echo "==> [2/8] clone + install modules" echo "==> [0/9] venv"
if [ ! -x "${VENV}/bin/python" ]; then
python3 -m venv "${VENV}"
fi
"${VENV}/bin/pip" install -q --upgrade pip >/dev/null 2>&1 || true
"${VENV}/bin/pip" install -q wheel >/dev/null 2>&1 || true
echo "==> [1/9] clone modules into pkgs/ (git clone only, no mv)"
cd "${DEPLOY_DIR}" cd "${DEPLOY_DIR}"
mkdir -p pkgs mkdir -p pkgs
for m in ${FOUNDATION_MODULES} ${DB_MODULES} ${BUSINESS_MODULES}; do for m in ${FOUNDATION_PKGS} ${BUSINESS_MODULES}; do
if [ ! -d "pkgs/${m}" ]; then if [ ! -d "pkgs/${m}/.git" ]; then
git clone "git@git.opencomputing.cn:org/${m}.git" "pkgs/${m}" || \ rm -rf "pkgs/${m}"
git clone "https://git.opencomputing.cn/org/${m}.git" "pkgs/${m}" || true git clone -q "${GIT_HTTPS}/${m}.git" "pkgs/${m}" || { echo "ERROR: clone ${m} failed" >&2; exit 1; }
fi echo " cloned ${m}"
if [ -d "pkgs/${m}" ]; then
(cd "pkgs/${m}" && git pull --ff-only || true)
if [ -f "pkgs/${m}/pyproject.toml" ] || [ -f "pkgs/${m}/setup.py" ]; then
pip install -e "pkgs/${m}" || true
else else
export PYTHONPATH="${DEPLOY_DIR}/pkgs/${m}:${PYTHONPATH:-}" git -C "pkgs/${m}" pull -q --ff-only || echo "WARN: pull ${m} failed, using existing"
fi fi
fi
done done
echo "==> [3/8] wwwroot symlinks" echo "==> [2/9] pip install all modules"
for m in ${DB_MODULES} ${BUSINESS_MODULES}; do for m in ${FOUNDATION_PKGS} ${BUSINESS_MODULES}; do
if [ -d "pkgs/${m}/wwwroot" ]; then if [ -f "pkgs/${m}/pyproject.toml" ] || [ -f "pkgs/${m}/setup.py" ] || [ -f "pkgs/${m}/setup.cfg" ]; then
ln -sfn "../pkgs/${m}/wwwroot" "wwwroot/${m}" "${VENV}/bin/pip" install -q -e "pkgs/${m}" || { echo "ERROR: pip install ${m} failed" >&2; exit 1; }
fi echo " installed ${m}"
else
echo "WARN: ${m} has no packaging metadata, skipped pip install"
fi
done done
if [ -d "pkgs/bricks/dist" ]; then
ln -sfn "../pkgs/bricks/dist" "wwwroot/bricks" echo "==> [3/9] bricks frontend build + symlink"
if [ ! -f "pkgs/bricks/dist/bricks.js" ]; then
(cd pkgs/bricks/bricks && bash build.sh) || { echo "ERROR: bricks build failed" >&2; exit 1; }
fi fi
mkdir -p wwwroot
ln -sfn "../pkgs/bricks/dist" "wwwroot/bricks"
echo "==> [4/8] i18n merge" echo "==> [4/9] database + foundation/initial tables"
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASSWORD}" \
-e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" \
|| { echo "ERROR: create database failed" >&2; exit 1; }
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASSWORD}" "${DB_NAME}" \
< "${APP_SRC_DIR}/scripts/ddl.sql" || { echo "ERROR: ddl.sql failed" >&2; exit 1; }
echo "==> [5/9] i18n merge"
mkdir -p wwwroot/i18n/zh wwwroot/i18n/en mkdir -p wwwroot/i18n/zh wwwroot/i18n/en
python - <<'PY' || true "${VENV}/bin/python" - <<'PY'
import json, os import json, os
ok = 0
for lang in ("zh", "en"): for lang in ("zh", "en"):
merged = {} merged = {}
for m in ("apppublic","sqlor","ahserver","appbase","rbac","world","scene","entity","script_engine","world_snapshot","world_sync"): for m in ("apppublic","sqlor","ahserver","appbase","rbac","world","scene","entity","script_engine","world_snapshot","world_sync"):
@ -79,36 +94,58 @@ for lang in ("zh", "en"):
k, v = line.split("=", 1) k, v = line.split("=", 1)
merged[k.strip()] = v.strip() merged[k.strip()] = v.strip()
out = f"wwwroot/i18n/{lang}/i18n.json" out = f"wwwroot/i18n/{lang}/i18n.json"
os.makedirs(os.path.dirname(out), exist_ok=True)
json.dump(merged, open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=2) json.dump(merged, open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
print("i18n merged") ok += len(merged)
print(f"i18n merged: {ok} keys")
PY PY
echo "==> [5/8] init db (ddl)" echo "==> [6/9] per-module install: ① json2ddl建表 ② json/build.sh ③ wwwroot软链 ④ 数据导入"
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASSWORD}" \
< "${APP_DIR}/scripts/ddl.sql"
echo "==> [6/8] CRUD generate"
for m in ${BUSINESS_MODULES}; do for m in ${BUSINESS_MODULES}; do
if [ -d "pkgs/${m}/json" ] && [ -d "pkgs/${m}/models" ]; then echo " -- ${m}"
(cd "pkgs/${m}" && xls2ui -m ../models -o "${DEPLOY_DIR}/wwwroot" "${m}" json/*.json || true) # ① 建表:模块 models 是唯一事实源;去掉 DROP 行,重跑不丢数据
fi (cd "pkgs/${m}/models" && "${VENV}/bin/json2ddl" mysql .) > "/tmp/${m}_ddl.sql" \
|| { echo "ERROR: ${m} json2ddl failed" >&2; exit 1; }
[ -s "/tmp/${m}_ddl.sql" ] || { echo "ERROR: ${m} DDL empty" >&2; exit 1; }
grep -vi "^drop table" "/tmp/${m}_ddl.sql" > "/tmp/${m}_ddl_nodrop.sql"
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASSWORD}" "${DB_NAME}" \
< "/tmp/${m}_ddl_nodrop.sql" || { echo "ERROR: ${m} create tables failed" >&2; exit 1; }
# ② CRUD 页面(模块仓库自带 json/build.sh)
(cd "pkgs/${m}/json" && bash build.sh) || { echo "ERROR: ${m} json/build.sh failed" >&2; exit 1; }
# ③ wwwroot 软链(非 cp)
ln -sfn "../pkgs/${m}/wwwroot" "wwwroot/${m}"
# ④ 初始化数据(有 data/*.xlsx 才导)
if ls "pkgs/${m}/data/"*.xlsx >/dev/null 2>&1; then
(cd "pkgs/${m}/data" && for f in *.xlsx; do "${VENV}/bin/dbloader" "${DEPLOY_DIR}" "${DB_NAME}" "$f"; done) \
|| { echo "ERROR: ${m} dbloader failed" >&2; exit 1; }
fi
done done
echo "==> [7/8] init data" echo "==> [7/9] sync app code + runtime dirs"
for m in ${BUSINESS_MODULES}; do for d in app conf scripts wwwroot; do
if [ -d "pkgs/${m}/data" ]; then if [ -d "${APP_SRC_DIR}/${d}" ] && [ "$(realpath "${APP_SRC_DIR}/${d}")" != "$(realpath "${DEPLOY_DIR}/${d}")" ]; then
(cd "pkgs/${m}/data" && for f in *.xlsx; do mkdir -p "${DEPLOY_DIR}/${d}"
[ -e "$f" ] && dbloader "${DEPLOY_DIR}" "${DB_NAME}" "$f" || true cp -a "${APP_SRC_DIR}/${d}/." "${DEPLOY_DIR}/${d}/"
done) fi
fi
done done
mkdir -p logs files
echo "==> [8/8] restart" echo "==> [8/9] restart service"
if [ -f "${DEPLOY_DIR}/run.sh" ]; then bash "${APP_SRC_DIR}/stop.sh" || true
bash "${DEPLOY_DIR}/run.sh" restart sleep 1
else bash "${APP_SRC_DIR}/start.sh"
echo "warning: run.sh not found, start via: nohup python app/scense.py > logs/scense.log 2>&1 &"
echo "==> [9/9] health check: GET /healthz"
ok=""
code=""
for i in $(seq 1 15); do
code=$(curl -s -o /tmp/scense_healthz.json -w "%{http_code}" "http://127.0.0.1:${APP_PORT}/healthz" || true)
if [ "${code}" = "200" ]; then ok=1; break; fi
sleep 2
done
if [ -z "${ok}" ]; then
echo "ERROR: /healthz not 200 after 30s (last=${code:-000}); logs/scense.log tail:" >&2
tail -30 logs/scense.log 2>/dev/null >&2 || true
exit 1
fi fi
echo "healthz: $(cat /tmp/scense_healthz.json)"
echo "==> done, app port ${APP_PORT}" echo "==> deploy OK: app=scense port=${APP_PORT} healthz=200"

View File

@ -1,5 +1,9 @@
{ {
"app": {"name": "scense", "port": 9380, "title": "唤景平台世界模式"}, "app": {
"name": "scense",
"port": 9380,
"title": "唤景平台世界模式"
},
"password_key": "scense_test_env_key", "password_key": "scense_test_env_key",
"logger": { "logger": {
"name": "scense", "name": "scense",
@ -14,7 +18,7 @@
"host": "localhost", "host": "localhost",
"port": 3306, "port": 3306,
"user": "test", "user": "test",
"password": "test123", "password": "Kj2wTl+LYpPtDSk3B01U7w==",
"database": "scense", "database": "scense",
"charset": "utf8mb4" "charset": "utf8mb4"
} }
@ -24,18 +28,48 @@
"port": 9380, "port": 9380,
"root": "$[workdir]$/wwwroot", "root": "$[workdir]$/wwwroot",
"processors": [ "processors": [
[".tmpl", "tmpl"], [
[".ui", "bui"], ".tmpl",
[".dspy", "dspy"], "tmpl"
[".wss", "ws"] ],
[
".ui",
"bui"
],
[
".dspy",
"dspy"
],
[
".wss",
"ws"
]
],
"indexes": [
"index.ui",
"index.html"
], ],
"indexes": ["index.ui", "index.html"],
"session_max_time": 28800, "session_max_time": 28800,
"session_issue_time": 7200 "session_issue_time": 7200,
"paths": [
[
"$[workdir]$/wwwroot",
""
]
]
}, },
"modules": [ "modules": [
"apppublic", "sqlor", "ahserver", "appbase", "rbac", "apppublic",
"world", "scene", "entity", "script_engine", "world_snapshot", "world_sync" "sqlor",
"ahserver",
"appbase",
"rbac",
"world",
"scene",
"entity",
"script_engine",
"world_snapshot",
"world_sync"
], ],
"wwwroot": "wwwroot" "wwwroot": "wwwroot"
} }

View File

@ -6,3 +6,158 @@ CREATE TABLE IF NOT EXISTS entity (id BIGINT NOT NULL AUTO_INCREMENT, scene_id B
CREATE TABLE IF NOT EXISTS script_engine (id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(128) NOT NULL, script_type VARCHAR(32) NOT NULL DEFAULT 'lua', content MEDIUMTEXT, status VARCHAR(16) NOT NULL DEFAULT 'active', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_script_name (name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='脚本引擎'; CREATE TABLE IF NOT EXISTS script_engine (id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(128) NOT NULL, script_type VARCHAR(32) NOT NULL DEFAULT 'lua', content MEDIUMTEXT, status VARCHAR(16) NOT NULL DEFAULT 'active', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_script_name (name)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='脚本引擎';
CREATE TABLE IF NOT EXISTS world_snapshot (id BIGINT NOT NULL AUTO_INCREMENT, world_id BIGINT NOT NULL, version INT NOT NULL DEFAULT 1, snapshot_json LONGTEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_snapshot_world (world_id, version)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='世界快照'; CREATE TABLE IF NOT EXISTS world_snapshot (id BIGINT NOT NULL AUTO_INCREMENT, world_id BIGINT NOT NULL, version INT NOT NULL DEFAULT 1, snapshot_json LONGTEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_snapshot_world (world_id, version)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='世界快照';
CREATE TABLE IF NOT EXISTS world_sync (id BIGINT NOT NULL AUTO_INCREMENT, world_id BIGINT NOT NULL, sync_type VARCHAR(32) NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'pending', payload_json TEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_sync_world (world_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='世界同步'; CREATE TABLE IF NOT EXISTS world_sync (id BIGINT NOT NULL AUTO_INCREMENT, world_id BIGINT NOT NULL, sync_type VARCHAR(32) NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'pending', payload_json TEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_sync_world (world_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='世界同步';
-- ========== 基础框架表(rbac/appbase 单库模式,全新库需先建)==========
/*M!999999\- enable the sandbox mode */
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `users` (
`id` varchar(32) NOT NULL COMMENT '用户id',
`username` varchar(255) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`email` varchar(255) DEFAULT NULL COMMENT '邮件地址',
`orgid` varchar(32) DEFAULT NULL COMMENT '所属机构',
`nick_name` varchar(255) DEFAULT NULL COMMENT '显示名',
`address` varchar(255) DEFAULT NULL COMMENT '地址',
`mobile` varchar(255) DEFAULT NULL COMMENT '手机',
`user_status` varchar(1) DEFAULT '0' COMMENT '用户状态',
`created_at` date DEFAULT NULL COMMENT '注册日期',
`last_login` timestamp NULL DEFAULT NULL COMMENT '最后登录时间',
`login_fail_count` smallint(6) NOT NULL DEFAULT 0 COMMENT '连续登录失败次数',
`last_login_fail` timestamp NULL DEFAULT NULL COMMENT '最后登录失败时间',
`sync_from` varchar(32) DEFAULT NULL COMMENT '同步应用id',
PRIMARY KEY (`id`),
UNIQUE KEY `users_idx2` (`username`),
KEY `users_idx1` (`orgid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `organization` (
`id` varchar(32) NOT NULL COMMENT '机构编码',
`orgname` varchar(100) DEFAULT NULL COMMENT '机构名称',
`alias_name` varchar(100) DEFAULT NULL COMMENT '机构别名',
`contactor` varchar(32) DEFAULT NULL COMMENT '联系人',
`contactor_phone` varchar(100) DEFAULT NULL COMMENT '联系人电话',
`province_id` varchar(32) DEFAULT NULL COMMENT '所在省id',
`city_id` varchar(32) DEFAULT NULL COMMENT '所在城市id',
`distinct_id` varchar(32) DEFAULT NULL COMMENT '所在地区id',
`emailaddress` varchar(256) DEFAULT NULL COMMENT '邮箱',
`address` varchar(400) DEFAULT NULL COMMENT '地址',
`main_business` varchar(1000) DEFAULT NULL COMMENT '主营业务描述',
`orgcode` varchar(100) DEFAULT NULL COMMENT '组织结构代码',
`license_img` varchar(400) DEFAULT NULL COMMENT '营业执照',
`id_img` varchar(400) DEFAULT NULL COMMENT '身份证',
`parentid` varchar(32) DEFAULT NULL COMMENT '父机构id',
`org_type` varchar(32) DEFAULT NULL COMMENT '机构类型',
`accountid` varchar(32) DEFAULT NULL COMMENT '账号',
`del_flg` varchar(1) DEFAULT '0' COMMENT '删除标志',
`create_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '创建时间戳',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='机构';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `orgtypes` (
`id` varchar(32) NOT NULL COMMENT 'id',
`orgid` varchar(32) DEFAULT NULL COMMENT '机构id',
`orgtypeid` varchar(32) DEFAULT NULL COMMENT '机构类型',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='机构拥有角色';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `org_roles` (
`id` varchar(32) NOT NULL COMMENT 'id',
`orgid` varchar(32) DEFAULT NULL COMMENT '机构编码',
`roleid` varchar(32) DEFAULT NULL COMMENT '角色编码',
`del_flg` varchar(1) DEFAULT '0' COMMENT '删除标志',
`create_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '创建时间戳',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='机构角色';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `role` (
`id` varchar(32) NOT NULL COMMENT '角色id',
`orgtypeid` varchar(32) DEFAULT '0' COMMENT '机构类型',
`role` varchar(100) DEFAULT NULL COMMENT '角色',
`rolelabel` varchar(100) DEFAULT NULL COMMENT '角色名称',
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `role_idx1` (`orgtypeid`,`role`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT=' 角色';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `rolepermission` (
`id` varchar(32) NOT NULL COMMENT 'id',
`roleid` varchar(32) DEFAULT NULL COMMENT '角色id',
`permid` varchar(32) DEFAULT NULL COMMENT '权限id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色权限表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `userrole` (
`id` varchar(32) NOT NULL COMMENT '用户id',
`userid` varchar(32) DEFAULT NULL COMMENT '用户id',
`roleid` varchar(32) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户角色';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `permission` (
`id` varchar(32) NOT NULL COMMENT '权限id',
`name` varchar(255) DEFAULT NULL COMMENT '名称',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
`ptype` varchar(20) DEFAULT NULL COMMENT '类型',
`parentid` varchar(32) DEFAULT NULL COMMENT '父权限id',
`path` varchar(255) DEFAULT NULL COMMENT '路径',
`title` varchar(255) DEFAULT NULL COMMENT '标题',
`icon` varchar(255) DEFAULT NULL COMMENT '图标',
PRIMARY KEY (`id`),
KEY `permission_idx1` (`ptype`),
KEY `permission_idx2` (`parentid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='权限';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE IF NOT EXISTS `params` (
`id` varchar(32) NOT NULL,
`params_name` varchar(255) NOT NULL,
`params_value` varchar(4000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

23
start.sh Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
# scense 启动脚本(部署目录 ~/scense_app,端口 9380)
set -euo pipefail
DEPLOY_DIR="${DEPLOY_DIR:-$HOME/scense_app}"
APP_PORT="${APP_PORT:-9380}"
cd "${DEPLOY_DIR}"
mkdir -p logs
if pgrep -f "python.*app/scense.py" >/dev/null 2>&1; then
echo "scense already running: $(pgrep -f 'python.*app/scense.py' | tr '\n' ' ')"
exit 0
fi
nohup "${DEPLOY_DIR}/venv/bin/python" app/scense.py -w "${DEPLOY_DIR}" -p "${APP_PORT}" \
>> logs/scense.log 2>&1 </dev/null &
sleep 2
pid=$(pgrep -f "python.*app/scense.py" | head -1 || true)
if [ -z "${pid}" ]; then
echo "ERROR: scense failed to start; logs/scense.log tail:" >&2
tail -30 logs/scense.log >&2 || true
exit 1
fi
echo "scense started: pid=${pid} port=${APP_PORT}"

18
stop.sh Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
# scense 停止脚本(优雅停止,保留日志)
pids=$(pgrep -f "python.*app/scense.py" || true)
if [ -z "${pids}" ]; then
echo "scense not running"
exit 0
fi
pkill -TERM -f "python.*app/scense.py" || true
sleep 2
pids=$(pgrep -f "python.*app/scense.py" || true)
if [ -n "${pids}" ]; then
sleep 2
pids=$(pgrep -f "python.*app/scense.py" || true)
if [ -n "${pids}" ]; then
echo "WARN: still alive: ${pids}"
fi
fi
echo "scense stopped"