From 8057ba2888ade985c264efebfe151151029b9427 Mon Sep 17 00:00:00 2001 From: "agent.develop" Date: Fri, 28 Aug 2026 16:15:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20build.sh=20=E6=8C=89=20webapp-deploy=20?= =?UTF-8?q?=E6=8A=80=E8=83=BD=E9=87=8D=E5=86=99=E4=B8=80=E9=94=AE=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=EF=BC=88venv+clone=E5=85=A8=E6=A8=A1=E5=9D=97+?= =?UTF-8?q?=E5=9B=9B=E6=AD=A5=E5=AE=89=E8=A3=85+/healthz=E9=97=A8=E7=A6=81?= =?UTF-8?q?=EF=BC=89=EF=BC=9B=E5=8A=A0=20start/stop=EF=BC=9B/healthz?= =?UTF-8?q?=E9=92=A9=E5=AD=90=EF=BC=9B=E5=AF=86=E7=A0=81=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scense.py | 17 +++++ build.sh | 171 ++++++++++++++++++++++++++++------------------- conf/config.json | 56 +++++++++++++--- scripts/ddl.sql | 155 ++++++++++++++++++++++++++++++++++++++++++ start.sh | 23 +++++++ stop.sh | 18 +++++ 6 files changed, 362 insertions(+), 78 deletions(-) mode change 100644 => 100755 build.sh create mode 100755 start.sh create mode 100755 stop.sh diff --git a/app/scense.py b/app/scense.py index d660553..6e708dd 100644 --- a/app/scense.py +++ b/app/scense.py @@ -53,5 +53,22 @@ def init(): 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__": webapp(init) diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index e9a6340..8309a70 --- a/build.sh +++ b/build.sh @@ -1,73 +1,88 @@ #!/usr/bin/env bash -set -euo pipefail - # ============================================================================= # 唤景平台世界模式(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) -# 3) 前端软链 wwwroot/{模块} 4) i18n 合并 -# 5) 建表(json2ddl) 6) CRUD 生成(xls2ui) -# 7) 初始化数据 8) 运行挂载(load_{模块}) +# 步骤: +# 0) venv +# 1) 创建 pkgs/,全部模块 git clone 进来(基础共享包 + 6 业务模块;禁止 mv 源码) +# 2) pip install 全部模块(-e,失败即退出) +# 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}" +VENV="${DEPLOY_DIR}/venv" +APP_PORT="${APP_PORT:-9380}" DB_HOST="${DB_HOST:-localhost}" DB_PORT="${DB_PORT:-3306}" DB_NAME="${DB_NAME:-scense}" DB_USER="${DB_USER:-test}" DB_PASSWORD="${DB_PASSWORD:-test123}" +GIT_HTTPS="https://git.opencomputing.cn/yumoqing" -# 基础模块(框架层)与业务模块 -FOUNDATION_MODULES="apppublic sqlor ahserver bricks" -DB_MODULES="appbase rbac" +FOUNDATION_PKGS="apppublic sqlor ahserver rbac xls2ddl appbase bricks-for-python bricks" BUSINESS_MODULES="world scene entity script_engine world_snapshot world_sync" -echo "==> [1/8] sync code" -mkdir -p "${DEPLOY_DIR}/app" "${DEPLOY_DIR}/conf" "${DEPLOY_DIR}/wwwroot" \ - "${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/" +APP_SRC_DIR="$(cd "$(dirname "$0")" && pwd)" +mkdir -p "${DEPLOY_DIR}" -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}" mkdir -p pkgs -for m in ${FOUNDATION_MODULES} ${DB_MODULES} ${BUSINESS_MODULES}; do - if [ ! -d "pkgs/${m}" ]; then - git clone "git@git.opencomputing.cn:org/${m}.git" "pkgs/${m}" || \ - git clone "https://git.opencomputing.cn/org/${m}.git" "pkgs/${m}" || true - fi - 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 +for m in ${FOUNDATION_PKGS} ${BUSINESS_MODULES}; do + if [ ! -d "pkgs/${m}/.git" ]; then + rm -rf "pkgs/${m}" + git clone -q "${GIT_HTTPS}/${m}.git" "pkgs/${m}" || { echo "ERROR: clone ${m} failed" >&2; exit 1; } + echo " cloned ${m}" 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 done -echo "==> [3/8] wwwroot symlinks" -for m in ${DB_MODULES} ${BUSINESS_MODULES}; do - if [ -d "pkgs/${m}/wwwroot" ]; then - ln -sfn "../pkgs/${m}/wwwroot" "wwwroot/${m}" - fi +echo "==> [2/9] pip install all modules" +for m in ${FOUNDATION_PKGS} ${BUSINESS_MODULES}; do + if [ -f "pkgs/${m}/pyproject.toml" ] || [ -f "pkgs/${m}/setup.py" ] || [ -f "pkgs/${m}/setup.cfg" ]; then + "${VENV}/bin/pip" install -q -e "pkgs/${m}" || { echo "ERROR: pip install ${m} failed" >&2; exit 1; } + echo " installed ${m}" + else + echo "WARN: ${m} has no packaging metadata, skipped pip install" + fi 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 +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 -python - <<'PY' || true +"${VENV}/bin/python" - <<'PY' import json, os +ok = 0 for lang in ("zh", "en"): merged = {} 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) merged[k.strip()] = v.strip() 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) -print("i18n merged") + ok += len(merged) +print(f"i18n merged: {ok} keys") PY -echo "==> [5/8] init db (ddl)" -mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASSWORD}" \ - < "${APP_DIR}/scripts/ddl.sql" - -echo "==> [6/8] CRUD generate" +echo "==> [6/9] per-module install: ① json2ddl建表 ② json/build.sh ③ wwwroot软链 ④ 数据导入" for m in ${BUSINESS_MODULES}; do - if [ -d "pkgs/${m}/json" ] && [ -d "pkgs/${m}/models" ]; then - (cd "pkgs/${m}" && xls2ui -m ../models -o "${DEPLOY_DIR}/wwwroot" "${m}" json/*.json || true) - fi + echo " -- ${m}" + # ① 建表:模块 models 是唯一事实源;去掉 DROP 行,重跑不丢数据 + (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 -echo "==> [7/8] init data" -for m in ${BUSINESS_MODULES}; do - if [ -d "pkgs/${m}/data" ]; then - (cd "pkgs/${m}/data" && for f in *.xlsx; do - [ -e "$f" ] && dbloader "${DEPLOY_DIR}" "${DB_NAME}" "$f" || true - done) - fi +echo "==> [7/9] sync app code + runtime dirs" +for d in app conf scripts wwwroot; do + if [ -d "${APP_SRC_DIR}/${d}" ] && [ "$(realpath "${APP_SRC_DIR}/${d}")" != "$(realpath "${DEPLOY_DIR}/${d}")" ]; then + mkdir -p "${DEPLOY_DIR}/${d}" + cp -a "${APP_SRC_DIR}/${d}/." "${DEPLOY_DIR}/${d}/" + fi done +mkdir -p logs files -echo "==> [8/8] restart" -if [ -f "${DEPLOY_DIR}/run.sh" ]; then - bash "${DEPLOY_DIR}/run.sh" restart -else - echo "warning: run.sh not found, start via: nohup python app/scense.py > logs/scense.log 2>&1 &" +echo "==> [8/9] restart service" +bash "${APP_SRC_DIR}/stop.sh" || true +sleep 1 +bash "${APP_SRC_DIR}/start.sh" + +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 - -echo "==> done, app port ${APP_PORT}" +echo "healthz: $(cat /tmp/scense_healthz.json)" +echo "==> deploy OK: app=scense port=${APP_PORT} healthz=200" diff --git a/conf/config.json b/conf/config.json index d53f8f7..75a9cb1 100644 --- a/conf/config.json +++ b/conf/config.json @@ -1,5 +1,9 @@ { - "app": {"name": "scense", "port": 9380, "title": "唤景平台世界模式"}, + "app": { + "name": "scense", + "port": 9380, + "title": "唤景平台世界模式" + }, "password_key": "scense_test_env_key", "logger": { "name": "scense", @@ -14,7 +18,7 @@ "host": "localhost", "port": 3306, "user": "test", - "password": "test123", + "password": "Kj2wTl+LYpPtDSk3B01U7w==", "database": "scense", "charset": "utf8mb4" } @@ -24,18 +28,48 @@ "port": 9380, "root": "$[workdir]$/wwwroot", "processors": [ - [".tmpl", "tmpl"], - [".ui", "bui"], - [".dspy", "dspy"], - [".wss", "ws"] + [ + ".tmpl", + "tmpl" + ], + [ + ".ui", + "bui" + ], + [ + ".dspy", + "dspy" + ], + [ + ".wss", + "ws" + ] + ], + "indexes": [ + "index.ui", + "index.html" ], - "indexes": ["index.ui", "index.html"], "session_max_time": 28800, - "session_issue_time": 7200 + "session_issue_time": 7200, + "paths": [ + [ + "$[workdir]$/wwwroot", + "" + ] + ] }, "modules": [ - "apppublic", "sqlor", "ahserver", "appbase", "rbac", - "world", "scene", "entity", "script_engine", "world_snapshot", "world_sync" + "apppublic", + "sqlor", + "ahserver", + "appbase", + "rbac", + "world", + "scene", + "entity", + "script_engine", + "world_snapshot", + "world_sync" ], "wwwroot": "wwwroot" -} +} \ No newline at end of file diff --git a/scripts/ddl.sql b/scripts/ddl.sql index a71e43a..d0af1c3 100644 --- a/scripts/ddl.sql +++ b/scripts/ddl.sql @@ -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 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='世界同步'; + +-- ========== 基础框架表(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 */; + diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..dd6bd65 --- /dev/null +++ b/start.sh @@ -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 &2 + tail -30 logs/scense.log >&2 || true + exit 1 +fi +echo "scense started: pid=${pid} port=${APP_PORT}" diff --git a/stop.sh b/stop.sh new file mode 100755 index 0000000..d9bd33f --- /dev/null +++ b/stop.sh @@ -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"