feat(scense): scense 初始化提交(元景项目-初始迭代,开发产线产出)
This commit is contained in:
commit
93dadab835
9
.env
Normal file
9
.env
Normal file
@ -0,0 +1,9 @@
|
||||
# 唤景平台世界模式(scense)测试环境配置
|
||||
APP_NAME=scense
|
||||
APP_PORT=9380
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=scense
|
||||
DB_USER=test
|
||||
DB_PASSWORD=test123
|
||||
DEPLOY_ENV=test
|
||||
57
app/scense.py
Normal file
57
app/scense.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""唤景平台世界模式(scense)——应用唯一入口。
|
||||
|
||||
一个应用 = 一个入口 = 一个端口(9380)。
|
||||
init() 里先挂 ServerEnv,再挂载基础模块 + 6 个业务模块。
|
||||
"""
|
||||
from ahserver.webapp import webapp
|
||||
from ahserver.serverenv import ServerEnv
|
||||
|
||||
import bricks_for_python # registers bui processor
|
||||
from bricks_for_python.init import load_pybricks # registers UiWindow etc.
|
||||
from appbase.init import load_appbase
|
||||
from rbac.init import load_rbac
|
||||
|
||||
|
||||
def get_module_dbname(m):
|
||||
"""返回模块 m 对应的数据库名。
|
||||
|
||||
本项目为单库应用:world/scene/entity/script_engine/world_snapshot/world_sync
|
||||
以及基础模块(appbase/rbac)统一落同一主库 scense。
|
||||
"""
|
||||
return "scense"
|
||||
|
||||
|
||||
def password_encode(s):
|
||||
if s is None:
|
||||
return ""
|
||||
from ahserver.globalEnv import password_encode as _orig
|
||||
return _orig(s)
|
||||
|
||||
|
||||
def init():
|
||||
env = ServerEnv() # MUST be before load_rbac / load_appbase
|
||||
env.get_module_dbname = get_module_dbname
|
||||
env.password_encode = password_encode
|
||||
|
||||
load_appbase()
|
||||
load_rbac()
|
||||
load_pybricks()
|
||||
|
||||
# ── 业务模块导入(一个入口一个端口,所有模块挂在这里)──
|
||||
from world.init import load_world
|
||||
from scene.init import load_scene
|
||||
from entity.init import load_entity
|
||||
from script_engine.init import load_script_engine
|
||||
from world_snapshot.init import load_world_snapshot
|
||||
from world_sync.init import load_world_sync
|
||||
|
||||
load_world()
|
||||
load_scene()
|
||||
load_entity()
|
||||
load_script_engine()
|
||||
load_world_snapshot()
|
||||
load_world_sync()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
webapp(init)
|
||||
114
build.sh
Normal file
114
build.sh
Normal file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# =============================================================================
|
||||
# 唤景平台世界模式(scense)一键部署脚本 —— 测试环境
|
||||
# 部署目录 ~/scense_app,应用端口 9380,数据库 localhost:3306/scense (test/test123)
|
||||
#
|
||||
# 按 web-application-spec 的 8 步完整集成:
|
||||
# 1) 代码导入(git clone 模块到 pkgs/) 2) 依赖安装(pip install -e)
|
||||
# 3) 前端软链 wwwroot/{模块} 4) i18n 合并
|
||||
# 5) 建表(json2ddl) 6) CRUD 生成(xls2ui)
|
||||
# 7) 初始化数据 8) 运行挂载(load_{模块})
|
||||
# =============================================================================
|
||||
|
||||
APP_NAME="scense"
|
||||
APP_PORT=9380
|
||||
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
DEPLOY_DIR="${DEPLOY_DIR:-$HOME/scense_app}"
|
||||
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}"
|
||||
|
||||
# 基础模块(框架层)与业务模块
|
||||
FOUNDATION_MODULES="apppublic sqlor ahserver bricks"
|
||||
DB_MODULES="appbase rbac"
|
||||
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/"
|
||||
|
||||
echo "==> [2/8] clone + install modules"
|
||||
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
|
||||
else
|
||||
export PYTHONPATH="${DEPLOY_DIR}/pkgs/${m}:${PYTHONPATH:-}"
|
||||
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
|
||||
done
|
||||
if [ -d "pkgs/bricks/dist" ]; then
|
||||
ln -sfn "../pkgs/bricks/dist" "wwwroot/bricks"
|
||||
fi
|
||||
|
||||
echo "==> [4/8] i18n merge"
|
||||
mkdir -p wwwroot/i18n/zh wwwroot/i18n/en
|
||||
python - <<'PY' || true
|
||||
import json, os
|
||||
for lang in ("zh", "en"):
|
||||
merged = {}
|
||||
for m in ("apppublic","sqlor","ahserver","appbase","rbac","world","scene","entity","script_engine","world_snapshot","world_sync"):
|
||||
p = f"pkgs/{m}/i18n/{lang}/msg.txt"
|
||||
if os.path.isfile(p):
|
||||
for line in open(p, encoding="utf-8"):
|
||||
line = line.rstrip("\n")
|
||||
if "=" in line:
|
||||
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")
|
||||
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"
|
||||
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
|
||||
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
|
||||
done
|
||||
|
||||
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 &"
|
||||
fi
|
||||
|
||||
echo "==> done, app port ${APP_PORT}"
|
||||
41
conf/config.json
Normal file
41
conf/config.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"app": {"name": "scense", "port": 9380, "title": "唤景平台世界模式"},
|
||||
"password_key": "scense_test_env_key",
|
||||
"logger": {
|
||||
"name": "scense",
|
||||
"level": "INFO",
|
||||
"file": "$[workdir]$/logs/scense.log"
|
||||
},
|
||||
"filesroot": "$[workdir]$/files",
|
||||
"databases": {
|
||||
"scense": {
|
||||
"driver": "mysql",
|
||||
"kwargs": {
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"user": "test",
|
||||
"password": "test123",
|
||||
"database": "scense",
|
||||
"charset": "utf8mb4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"website": {
|
||||
"port": 9380,
|
||||
"root": "$[workdir]$/wwwroot",
|
||||
"processors": [
|
||||
[".tmpl", "tmpl"],
|
||||
[".ui", "bui"],
|
||||
[".dspy", "dspy"],
|
||||
[".wss", "ws"]
|
||||
],
|
||||
"indexes": ["index.ui", "index.html"],
|
||||
"session_max_time": 28800,
|
||||
"session_issue_time": 7200
|
||||
},
|
||||
"modules": [
|
||||
"apppublic", "sqlor", "ahserver", "appbase", "rbac",
|
||||
"world", "scene", "entity", "script_engine", "world_snapshot", "world_sync"
|
||||
],
|
||||
"wwwroot": "wwwroot"
|
||||
}
|
||||
8
scripts/ddl.sql
Normal file
8
scripts/ddl.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE DATABASE IF NOT EXISTS scense DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
USE scense;
|
||||
CREATE TABLE IF NOT EXISTS world (id BIGINT NOT NULL AUTO_INCREMENT COMMENT '世界ID', code VARCHAR(64) NOT NULL, name VARCHAR(128) NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'active', description VARCHAR(512) DEFAULT NULL, 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_world_code (code)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='世界';
|
||||
CREATE TABLE IF NOT EXISTS scene (id BIGINT NOT NULL AUTO_INCREMENT, world_id BIGINT NOT NULL, code VARCHAR(64) NOT NULL, name VARCHAR(128) NOT NULL, scene_type VARCHAR(32) DEFAULT 'normal', status VARCHAR(16) NOT NULL DEFAULT 'active', config_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), UNIQUE KEY uk_scene_code (code), KEY idx_scene_world (world_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='场景';
|
||||
CREATE TABLE IF NOT EXISTS entity (id BIGINT NOT NULL AUTO_INCREMENT, scene_id BIGINT NOT NULL, entity_type VARCHAR(32) NOT NULL, name VARCHAR(128) NOT NULL, attrs_json TEXT, 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), KEY idx_entity_scene (scene_id)) 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_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='世界同步';
|
||||
59
wwwroot/index.ui
Normal file
59
wwwroot/index.ui
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "root",
|
||||
"options": {"css": "filler"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "TopBar",
|
||||
"id": "topbar",
|
||||
"options": {"title": "唤景平台世界模式", "user": "app.user"}
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"id": "body",
|
||||
"options": {"css": "filler"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "main_content",
|
||||
"options": {"css": "filler"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "NavMenu",
|
||||
"id": "nav_menu",
|
||||
"options": {
|
||||
"items": [
|
||||
{"label": "世界", "target": "app.main_content", "url": "/world/list"},
|
||||
{"label": "场景", "target": "app.main_content", "url": "/scene/list"},
|
||||
{"label": "实体", "target": "app.main_content", "url": "/entity/list"},
|
||||
{"label": "脚本引擎", "target": "app.main_content", "url": "/script_engine/list"},
|
||||
{"label": "世界快照", "target": "app.main_content", "url": "/world_snapshot/list"},
|
||||
{"label": "世界同步", "target": "app.main_content", "url": "/world_sync/list"}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "sidebar",
|
||||
"options": {"css": "sidebar", "width": 220},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Card",
|
||||
"id": "side_card",
|
||||
"options": {"title": "模块导航"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Label",
|
||||
"id": "side_label",
|
||||
"options": {"text": "世界 / 场景 / 实体 / 脚本 / 快照 / 同步"}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user