refactor: CMS从Sage子模块重构为独立ahserver Web应用

架构变更:
- CMS作为独立进程运行(端口9090),不再嵌入Sage
- 使用ahserver框架,复用rbac模块做认证授权
- 所有模块共享sage数据库(配置在conf/config.json)

新增文件:
- app/cms.py: 独立Web应用主入口(webapp(init))
- app/global_func.py: 全局函数(get_module_dbname/UiWindow等)
- conf/config.json: 应用配置模板(数据库/路径/处理器/Redis)
- start.sh/stop.sh: 进程管理脚本
- pyproject.toml: 顶层Python包配置

路径重构(去掉/entcms前缀):
- 官网首页: /entcms/index.ui → /index.ui
- 管理后台: /entcms/admin.ui → /admin.ui
- API: /entcms/api/xxx.dspy → /api/xxx.dspy
- CRUD: /entcms/cms_content_list → /cms_content_list
- dingdingflow保持/dingdingflow前缀(映射子目录)

config.json路径映射:
- entcms/wwwroot → / (根路径)
- dingdingflow/wwwroot → /dingdingflow
- bricks/dist → /bricks

构建脚本(build.sh):
- 创建独立venv(py3/)
- 安装核心依赖(apppublic/sqlor/ahserver/bricks/rbac等)
- json2ddl生成CMS业务表DDL
- xls2ui生成CRUD UI
- 生成systemd服务文件

load_path.py更新:
- entcms: 所有路径去掉/entcms前缀
- dingdingflow: 保持/dingdingflow前缀
- 查找set_role_perm.py支持CMS和Sage两种环境

init_superuser.py更新:
- 支持CMS独立环境(自动检测py3/conf)
- 创建superuser角色并分配全部权限
This commit is contained in:
yumoqing 2026-05-27 17:20:36 +08:00
parent 52f4632dfc
commit 569c8e6715
18 changed files with 624 additions and 217 deletions

32
.gitignore vendored
View File

@ -1,3 +1,31 @@
*.pyc
# Python
__pycache__/
mysql.ddl.sql
*.py[cod]
*.egg-info/
dist/
build/
# Virtual environment
py3/
pkgs/
# Runtime
logs/
files/
*.pid
cms.service
# DDL generated
*/mysql.ddl.sql
# IDE
.idea/
.vscode/
*.swp
# OS
.DS_Store
Thumbs.db
# Config with credentials
# conf/config.json # Keep template in git

42
app/cms.py Normal file
View File

@ -0,0 +1,42 @@
"""
开元云科技CMS 独立Web应用主入口
启动: py3/bin/python app/cms.py -p 9090 -w $(pwd)
"""
import os, sys
# Ensure app/ is in path for local imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from appPublic.log import MyLogger, info
from appPublic.folderUtils import ProgramPath
from appPublic.jsonConfig import getConfig
from appPublic.registerfunction import RegisterFunction
from bricks_for_python.init import load_pybricks
from ahserver.webapp import webapp
from ahserver.serverenv import ServerEnv
from sqlor.dbpools import DBPools
# CMS业务模块
from entcms.init import load_entcms
from dingdingflow.init import load_dingdingflow
# RBAC认证(复用sage的rbac模块)
from rbac.init import load_rbac
from appbase.init import load_appbase
# 全局函数
from global_func import set_globalvariable
__version__ = '1.0.0'
def init():
rf = RegisterFunction()
set_globalvariable()
load_pybricks()
load_appbase()
load_rbac()
load_entcms()
load_dingdingflow()
if __name__ == '__main__':
webapp(init)

56
app/global_func.py Normal file
View File

@ -0,0 +1,56 @@
"""
CMS全局函数 注册到ServerEnv供.dspy和.ui调用
"""
from ahserver.serverenv import ServerEnv
def get_module_dbname(mname):
"""所有模块共享sage数据库"""
return 'sage'
def UiWindow(title, icon, content, cheight=10, cwidth=15):
return {
"widgettype": "PopupWindow",
"options": {
"author": "cms",
"cwidth": cwidth,
"cheight": cheight,
"title": title,
"content": content,
"icon": icon or entire_url('/bricks/imgs/app.png'),
"movable": True,
"auto_open": True
}
}
def UiError(title="出错", message="出错啦", timeout=5):
return {
"widgettype": "Error",
"options": {
"author": "cms",
"timeout": timeout,
"cwidth": 15,
"cheight": 10,
"title": title,
"message": message
}
}
def UiMessage(title="消息", message="后台消息", timeout=5):
return {
"widgettype": "Message",
"options": {
"author": "cms",
"timeout": timeout,
"cwidth": 15,
"cheight": 10,
"title": title,
"message": message
}
}
def set_globalvariable():
g = ServerEnv()
g.get_module_dbname = get_module_dbname
g.UiError = UiError
g.UiMessage = UiMessage
g.UiWindow = UiWindow

261
build.sh
View File

@ -1,87 +1,198 @@
#!/bin/bash
# CMS项目构建脚本
# 构建 entcms + dingdingflow 模块并集成到Sage系统
#!/usr/bin/env bash
# 开元云科技CMS — 独立Web应用构建脚本
# 用法: cd ~/repos/cms && ./build.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "=== CMS项目构建 ==="
echo "Script dir: $SCRIPT_DIR"
cdir=$(pwd)
uname=$(id -un)
gname=$(id -gn)
# 查找Sage根目录
SAGE_ROOT=""
for candidate in "$SCRIPT_DIR/../.." "$HOME/repos/sage" "$HOME/sage"; do
if [ -d "$candidate/wwwroot" ] && [ -d "$candidate/py3/bin" ]; then
SAGE_ROOT="$(cd "$candidate" && pwd)"
break
echo "============================================"
echo " 开元云科技CMS — 独立Web应用构建"
echo "============================================"
# ===========================================
# Step 1: Python虚拟环境
# ===========================================
echo ""
echo "--- Step 1: 创建Python虚拟环境 ---"
if [ ! -d "py3" ]; then
python3 -m venv py3
fi
source py3/bin/activate
# ===========================================
# Step 2: 核心依赖
# ===========================================
echo ""
echo "--- Step 2: 安装核心依赖 ---"
mkdir -p pkgs
# 核心框架包
for m in apppublic sqlor ahserver bricks-for-python xls2ddl
do
echo " install $m..."
cd $cdir/pkgs
if [ ! -d "$m" ]; then
git clone https://git.opencomputing.cn/yumoqing/$m
fi
cd $m
$cdir/py3/bin/pip install . 2>/dev/null || echo " WARN: $m install failed"
done
if [ -z "$SAGE_ROOT" ]; then
echo "ERROR: 找不到Sage根目录"
exit 1
# bricks前端
echo " install bricks..."
cd $cdir/pkgs
if [ ! -d "bricks" ]; then
git clone https://git.opencomputing.cn/yumoqing/bricks
fi
cd bricks/bricks
./build.sh 2>/dev/null || echo " WARN: bricks build skipped"
# bricks符号链接
mkdir -p $cdir/bricks
if [ -d "$cdir/pkgs/bricks/dist" ]; then
rm -f $cdir/bricks
ln -sf $cdir/pkgs/bricks/dist $cdir/bricks
fi
echo "Sage root: $SAGE_ROOT"
PY="$SAGE_ROOT/py3/bin/python"
PIP="$SAGE_ROOT/py3/bin/pip"
# 安装模块
for mod in entcms dingdingflow; do
echo ""
echo "=== 安装 $mod ==="
cd "$SCRIPT_DIR/$mod"
$PIP install -e .
# 生成DDL
if [ -d "models" ] && [ "$(ls models/*.json 2>/dev/null)" ]; then
echo "生成DDL..."
cd models
$PY -c "from sqlor.ddl_template_mysql import DDLTemplate; print(DDLTemplate().generate('.'))" > ../mysql.ddl.sql 2>/dev/null || json2ddl mysql . > ../mysql.ddl.sql 2>/dev/null || echo "DDL generation skipped (json2ddl not available)"
cd ..
# ===========================================
# Step 3: RBAC + AppBase模块(认证依赖)
# ===========================================
echo ""
echo "--- Step 3: 安装RBAC/AppBase模块 ---"
for m in appbase rbac checklang
do
echo " install $m..."
cd $cdir/pkgs
if [ ! -d "$m" ]; then
git clone https://git.opencomputing.cn/yumoqing/$m
fi
# 生成CRUD UI
if [ -d "json" ] && [ "$(ls json/*.json 2>/dev/null)" ]; then
echo "生成CRUD UI..."
cd json
xls2ui -m ../models -o ../wwwroot $mod *.json 2>/dev/null || echo "CRUD UI generation skipped (xls2ui not available)"
cd ..
fi
# 链接wwwroot到Sage
MODULE_WWWROOT="$SAGE_ROOT/wwwroot/$mod"
mkdir -p "$MODULE_WWWROOT/api"
# 链接UI/CSS/JS文件
for f in "$SCRIPT_DIR/$mod/wwwroot"/*.ui "$SCRIPT_DIR/$mod/wwwroot"/*.css "$SCRIPT_DIR/$mod/wwwroot"/*.js; do
[ -f "$f" ] || continue
fname=$(basename "$f")
ln -sf "$f" "$MODULE_WWWROOT/$fname"
done
# 链接api目录下的.dspy文件
for f in "$SCRIPT_DIR/$mod/wwwroot/api"/*.dspy; do
[ -f "$f" ] || continue
fname=$(basename "$f")
ln -sf "$f" "$MODULE_WWWROOT/api/$fname"
done
# 链接生成的CRUD目录
for d in "$SCRIPT_DIR/$mod/wwwroot"/*/; do
[ -d "$d" ] || continue
dname=$(basename "$d")
case "$dname" in api|styles|scripts) continue ;; esac
ln -sf "$d" "$MODULE_WWWROOT/$dname"
done
echo "$mod 安装完成"
cd $m
$cdir/py3/bin/pip install . 2>/dev/null || echo " WARN: $m install failed"
done
# ===========================================
# Step 4: CMS业务模块
# ===========================================
echo ""
echo "=== 构建完成 ==="
echo "请执行以下步骤完成集成:"
echo "1. 编辑 $SAGE_ROOT/app/sage.py 添加模块导入"
echo "2. 编辑 $SAGE_ROOT/build.sh 添加模块到安装循环"
echo "3. 执行 RBAC 权限配置"
echo "4. 重启Sage服务"
echo "--- Step 4: 安装CMS业务模块 ---"
# entcms模块
echo " install entcms..."
cd $cdir/entcms
$cdir/py3/bin/pip install . 2>/dev/null || echo " WARN: entcms install failed"
# dingdingflow模块
echo " install dingdingflow..."
cd $cdir/dingdingflow
$cdir/py3/bin/pip install . 2>/dev/null || echo " WARN: dingdingflow install failed"
# ===========================================
# Step 5: 数据库DDL(CMS业务表)
# ===========================================
echo ""
echo "--- Step 5: 生成数据库DDL ---"
# entcms表DDL
if [ -d "$cdir/entcms/models" ]; then
cd $cdir/entcms/models
echo " 生成 entcms DDL..."
$cdir/py3/bin/json2ddl mysql . > $cdir/entcms/mysql.ddl.sql 2>/dev/null || echo " WARN: json2ddl failed for entcms"
echo " DDL已生成: entcms/mysql.ddl.sql"
fi
# dingdingflow表DDL
if [ -d "$cdir/dingdingflow/models" ]; then
cd $cdir/dingdingflow/models
echo " 生成 dingdingflow DDL..."
$cdir/py3/bin/json2ddl mysql . > $cdir/dingdingflow/mysql.ddl.sql 2>/dev/null || echo " WARN: json2ddl failed for dingdingflow"
echo " DDL已生成: dingdingflow/mysql.ddl.sql"
fi
# ===========================================
# Step 6: CRUD UI生成
# ===========================================
echo ""
echo "--- Step 6: 生成CRUD UI ---"
# entcms CRUD
if [ -d "$cdir/entcms/json" ]; then
cd $cdir/entcms/json
echo " 生成 entcms CRUD UI..."
for f in *.json; do
[ -f "$f" ] || continue
echo " $f"
$cdir/py3/bin/xls2ui -m ../models -o ../wwwroot entcms $f 2>/dev/null || echo " WARN: xls2ui failed for $f"
done
fi
# dingdingflow CRUD
if [ -d "$cdir/dingdingflow/json" ]; then
cd $cdir/dingdingflow/json
echo " 生成 dingdingflow CRUD UI..."
for f in *.json; do
[ -f "$f" ] || continue
echo " $f"
$cdir/py3/bin/xls2ui -m ../models -o ../wwwroot dingdingflow $f 2>/dev/null || echo " WARN: xls2ui failed for $f"
done
fi
# ===========================================
# Step 7: 日志和文件目录
# ===========================================
echo ""
echo "--- Step 7: 创建运行时目录 ---"
mkdir -p $cdir/logs
mkdir -p $cdir/files
# ===========================================
# Step 8: systemd服务文件
# ===========================================
echo ""
echo "--- Step 8: 生成systemd服务文件 ---"
cat > $cdir/cms.service <<EOF
[Unit]
Description=KaiYuan Cloud CMS Web Application
After=network.target
[Service]
User=$uname
Group=$gname
Type=forking
WorkingDirectory=$cdir
ExecStart=$cdir/start.sh
ExecStop=$cdir/stop.sh
StandardOutput=append:$cdir/logs/cms.log
StandardError=append:$cdir/logs/cms.log
SyslogIdentifier=cms
[Install]
WantedBy=multi-user.target
EOF
echo " cms.service 已生成"
# ===========================================
# Done
# ===========================================
cd $cdir
echo ""
echo "============================================"
echo " 构建完成!"
echo "============================================"
echo ""
echo "后续步骤:"
echo " 1. 编辑 conf/config.json 填入数据库密码"
echo " 2. 执行DDL创建CMS业务表:"
echo " mysql -h HOST -u USER -pPASS sage < entcms/mysql.ddl.sql"
echo " mysql -h HOST -u USER -pPASS sage < dingdingflow/mysql.ddl.sql"
echo " 3. 加载RBAC权限:"
echo " py3/bin/python entcms/scripts/load_path.py"
echo " py3/bin/python dingdingflow/scripts/load_path.py"
echo " 4. 初始化超级用户:"
echo " py3/bin/python scripts/init_superuser.py"
echo " 5. 启动应用:"
echo " ./start.sh"
echo ""
echo "访问地址: http://localhost:9090/"
echo "管理后台: http://localhost:9090/admin.ui"

80
conf/config.json Normal file
View File

@ -0,0 +1,80 @@
{
"password_key": "CHANGE_ME_16_CHARS",
"logger": {
"name": "cms",
"levelname": "info",
"logfile": "$[workdir]$/logs/cms.log"
},
"filesroot": "$[workdir]$/files",
"databases": {
"sage": {
"driver": "aiomysql",
"async_mode": true,
"coding": "utf8",
"dbname": "sage",
"kwargs": {
"user": "test",
"db": "sage",
"password": "ENCRYPTED_PASSWORD_HERE",
"host": "localhost"
}
}
},
"website": {
"paths": [
[
"$[workdir]$/entcms/wwwroot",
""
],
[
"$[workdir]$/dingdingflow/wwwroot",
"/dingdingflow"
],
[
"$[workdir]$/bricks",
"/bricks"
]
],
"host": "0.0.0.0",
"port": 9090,
"coding": "utf-8",
"session_redis": {
"url": "redis://127.0.0.1:6379/0"
},
"indexes": [
"index.ui",
"index.html",
"index.tmpl"
],
"processors": [
[
".xlsxds",
"xlsxds"
],
[
".sqlds",
"sqlds"
],
[
".tmpl",
"tmpl"
],
[
".dspy",
"dspy"
],
[
".ui",
"bui"
],
[
".md",
"md"
]
]
},
"langMapping": {
"zh-Hans-CN": "zh-cn",
"en-US": "en"
}
}

View File

@ -1,24 +1,28 @@
"""
dingdingflow RBAC权限配置 企业类型: owner
角色: superuser(继承全部), webmaster(提交审批), reviewer(审批管理),
supervisor(审批配置)
CMS独立部署dingdingflow路径保持/dingdingflow前缀
用法: cd ~/repos/sage && ./py3/bin/python ~/repos/cms/dingdingflow/scripts/load_path.py
用法: cd ~/repos/cms && py3/bin/python dingdingflow/scripts/load_path.py
"""
import os, sys, subprocess
def find_sage_root():
for c in [os.path.expanduser("~/repos/sage"), os.path.expanduser("~/sage")]:
if os.path.isdir(os.path.join(c, "wwwroot")) and os.path.isdir(os.path.join(c, "py3")):
return c
return None
def find_app_root():
script_dir = os.path.dirname(os.path.abspath(__file__))
return os.path.dirname(os.path.dirname(script_dir))
sage_root = find_sage_root()
app_root = find_app_root()
sage_root = None
for c in [os.path.expanduser("~/repos/sage"), os.path.expanduser("~/sage")]:
if os.path.isdir(os.path.join(c, "py3", "bin")):
sage_root = c
break
if not sage_root:
print("ERROR: Cannot find Sage root"); sys.exit(1)
sage_root = app_root
py = os.path.join(sage_root, "py3", "bin", "python")
sp = os.path.join(sage_root, "set_role_perm.py")
py = os.path.join(app_root, "py3", "bin", "python")
sp = os.path.join(sage_root, "set_role_perm.py") if os.path.exists(os.path.join(sage_root, "set_role_perm.py")) else None
if not sp:
print("ERROR: 找不到set_role_perm.py"); sys.exit(1)
def run(role, paths):
for p in paths:
@ -30,7 +34,6 @@ any_paths = [
"/dingdingflow/menu.ui",
]
# webmaster: 提交审批
webmaster_paths = [
"/dingdingflow",
"/dingdingflow/index.ui",
@ -39,7 +42,6 @@ webmaster_paths = [
"/dingdingflow/api/dd_approvals_list.dspy",
]
# reviewer: 审批管理(查看全部 + 更新审批状态)
reviewer_paths = [
"/dingdingflow",
"/dingdingflow/index.ui",
@ -48,7 +50,6 @@ reviewer_paths = [
"/dingdingflow/api/dd_approvals_update.dspy",
]
# supervisor: 审批配置管理 + 全部审批记录
supervisor_paths = [
"/dingdingflow",
"/dingdingflow/index.ui",
@ -66,12 +67,8 @@ supervisor_paths = [
]
print("=== dingdingflow RBAC权限配置 ===")
print(f"\n--- any (匿名/钉钉回调) ---")
run("any", any_paths)
print(f"\n--- owner.webmaster ---")
run("owner.webmaster", webmaster_paths)
print(f"\n--- owner.reviewer ---")
run("owner.reviewer", reviewer_paths)
print(f"\n--- owner.supervisor ---")
run("owner.supervisor", supervisor_paths)
print("\n完成")

View File

@ -29,9 +29,9 @@
}
},
"editable": {
"new_data_url": "{{entire_url('../api/cms_categories_create.dspy')}}",
"update_data_url": "{{entire_url('../api/cms_categories_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/cms_categories_delete.dspy')}}"
"new_data_url": "{{entire_url('api/cms_categories_create.dspy')}}",
"update_data_url": "{{entire_url('api/cms_categories_update.dspy')}}",
"delete_data_url": "{{entire_url('api/cms_categories_delete.dspy')}}"
}
}
}

View File

@ -89,9 +89,9 @@
}
},
"editable": {
"new_data_url": "{{entire_url('../api/cms_content_create.dspy')}}",
"update_data_url": "{{entire_url('../api/cms_content_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/cms_content_delete.dspy')}}"
"new_data_url": "{{entire_url('api/cms_content_create.dspy')}}",
"update_data_url": "{{entire_url('api/cms_content_update.dspy')}}",
"delete_data_url": "{{entire_url('api/cms_content_delete.dspy')}}"
}
}
}

View File

@ -85,9 +85,9 @@
}
},
"editable": {
"new_data_url": "{{entire_url('../api/cms_leads_create.dspy')}}",
"update_data_url": "{{entire_url('../api/cms_leads_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/cms_leads_delete.dspy')}}"
"new_data_url": "{{entire_url('api/cms_leads_create.dspy')}}",
"update_data_url": "{{entire_url('api/cms_leads_update.dspy')}}",
"delete_data_url": "{{entire_url('api/cms_leads_delete.dspy')}}"
}
}
}

View File

@ -84,9 +84,9 @@
}
},
"editable": {
"new_data_url": "{{entire_url('../api/cms_sections_create.dspy')}}",
"update_data_url": "{{entire_url('../api/cms_sections_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/cms_sections_delete.dspy')}}"
"new_data_url": "{{entire_url('api/cms_sections_create.dspy')}}",
"update_data_url": "{{entire_url('api/cms_sections_update.dspy')}}",
"delete_data_url": "{{entire_url('api/cms_sections_delete.dspy')}}"
}
}
}

View File

@ -55,9 +55,9 @@
}
},
"editable": {
"new_data_url": "{{entire_url('../api/cms_site_config_create.dspy')}}",
"update_data_url": "{{entire_url('../api/cms_site_config_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/cms_site_config_delete.dspy')}}"
"new_data_url": "{{entire_url('api/cms_site_config_create.dspy')}}",
"update_data_url": "{{entire_url('api/cms_site_config_update.dspy')}}",
"delete_data_url": "{{entire_url('api/cms_site_config_delete.dspy')}}"
}
}
}

View File

@ -1,25 +1,34 @@
"""
entcms RBAC权限配置 企业类型: owner
角色: superuser(继承全部), webmaster(内容管理), reviewer(审核),
supervisor(主管), customer-support(客服)
匿名: any (公开页面)
CMS独立部署路径不带/entcms前缀
用法: cd ~/repos/sage && ./py3/bin/python ~/repos/cms/entcms/scripts/load_path.py
用法: cd ~/repos/cms && py3/bin/python entcms/scripts/load_path.py
"""
import os, sys, subprocess
def find_sage_root():
for c in [os.path.expanduser("~/repos/sage"), os.path.expanduser("~/sage")]:
if os.path.isdir(os.path.join(c, "wwwroot")) and os.path.isdir(os.path.join(c, "py3")):
return c
return None
def find_app_root():
"""查找CMS应用根目录"""
script_dir = os.path.dirname(os.path.abspath(__file__))
# scripts/ -> entcms/ -> cms root
return os.path.dirname(os.path.dirname(script_dir))
sage_root = find_sage_root()
app_root = find_app_root()
# 查找Sage的set_role_perm.pyRBAC工具
sage_root = None
for c in [os.path.expanduser("~/repos/sage"), os.path.expanduser("~/sage")]:
if os.path.isdir(os.path.join(c, "py3", "bin")):
sage_root = c
break
if not sage_root:
print("ERROR: Cannot find Sage root"); sys.exit(1)
# 使用CMS自己的py3
sage_root = app_root
py = os.path.join(sage_root, "py3", "bin", "python")
sp = os.path.join(sage_root, "set_role_perm.py")
py = os.path.join(app_root, "py3", "bin", "python")
sp = os.path.join(sage_root, "set_role_perm.py") if os.path.exists(os.path.join(sage_root, "set_role_perm.py")) else None
if not sp:
print("ERROR: 找不到set_role_perm.py请确保Sage或CMS已构建")
sys.exit(1)
def run(role, paths):
for p in paths:
@ -28,105 +37,97 @@ def run(role, paths):
# ─── anonymous (any) — 公开页面 + 公开API ───
any_paths = [
"/entcms/index.ui",
"/entcms/news.ui",
"/entcms/news_detail.ui",
"/entcms/cases.ui",
"/entcms/products.ui",
"/entcms/cms_styles.css",
"/entcms/cms_scripts.js",
"/entcms/menu.ui",
"/entcms/api/submit_lead.dspy",
"/entcms/api/get_config.dspy",
"/entcms/api/get_published_content.dspy",
"/entcms/api/get_content_detail.dspy",
"/entcms/api/get_sections.dspy",
"/index.ui",
"/news.ui",
"/news_detail.ui",
"/cases.ui",
"/products.ui",
"/cms_styles.css",
"/cms_scripts.js",
"/menu.ui",
"/api/submit_lead.dspy",
"/api/get_config.dspy",
"/api/get_published_content.dspy",
"/api/get_content_detail.dspy",
"/api/get_sections.dspy",
]
# ─── webmaster — 内容/分类/栏目/配置/线索 全部CRUD ───
webmaster_paths = [
"/entcms",
"/entcms/admin.ui",
"/admin.ui",
# 内容
"/entcms/cms_content_list", "/entcms/cms_content_list/%",
"/entcms/api/cms_content_create.dspy",
"/entcms/api/cms_content_update.dspy",
"/entcms/api/cms_content_delete.dspy",
"/entcms/api/cms_content_list.dspy",
"/cms_content_list", "/cms_content_list/%",
"/api/cms_content_create.dspy",
"/api/cms_content_update.dspy",
"/api/cms_content_delete.dspy",
"/api/cms_content_list.dspy",
# 分类
"/entcms/cms_categories_list", "/entcms/cms_categories_list/%",
"/entcms/api/cms_categories_create.dspy",
"/entcms/api/cms_categories_update.dspy",
"/entcms/api/cms_categories_delete.dspy",
"/entcms/api/cms_categories_list.dspy",
"/entcms/api/category_options.dspy",
"/cms_categories_list", "/cms_categories_list/%",
"/api/cms_categories_create.dspy",
"/api/cms_categories_update.dspy",
"/api/cms_categories_delete.dspy",
"/api/cms_categories_list.dspy",
"/api/category_options.dspy",
# 栏目
"/entcms/cms_sections_list", "/entcms/cms_sections_list/%",
"/entcms/api/cms_sections_create.dspy",
"/entcms/api/cms_sections_update.dspy",
"/entcms/api/cms_sections_delete.dspy",
"/entcms/api/cms_sections_list.dspy",
"/cms_sections_list", "/cms_sections_list/%",
"/api/cms_sections_create.dspy",
"/api/cms_sections_update.dspy",
"/api/cms_sections_delete.dspy",
"/api/cms_sections_list.dspy",
# 站点配置
"/entcms/cms_site_config_list", "/entcms/cms_site_config_list/%",
"/entcms/api/cms_site_config_create.dspy",
"/entcms/api/cms_site_config_update.dspy",
"/entcms/api/cms_site_config_delete.dspy",
"/entcms/api/cms_site_config_list.dspy",
"/cms_site_config_list", "/cms_site_config_list/%",
"/api/cms_site_config_create.dspy",
"/api/cms_site_config_update.dspy",
"/api/cms_site_config_delete.dspy",
"/api/cms_site_config_list.dspy",
# 线索管理
"/entcms/cms_leads_list", "/entcms/cms_leads_list/%",
"/entcms/api/cms_leads_create.dspy",
"/entcms/api/cms_leads_update.dspy",
"/entcms/api/cms_leads_delete.dspy",
"/entcms/api/cms_leads_list.dspy",
"/cms_leads_list", "/cms_leads_list/%",
"/api/cms_leads_create.dspy",
"/api/cms_leads_update.dspy",
"/api/cms_leads_delete.dspy",
"/api/cms_leads_list.dspy",
# 审批
"/entcms/api/submit_content_approval.dspy",
"/api/submit_content_approval.dspy",
]
# ─── reviewer — 查看内容 + 审批(只改status) ───
reviewer_paths = [
"/entcms",
"/entcms/admin.ui",
"/entcms/cms_content_list", "/entcms/cms_content_list/%",
"/entcms/api/cms_content_list.dspy",
"/entcms/api/cms_content_update.dspy", # 仅更新status字段
"/entcms/api/category_options.dspy",
"/admin.ui",
"/cms_content_list", "/cms_content_list/%",
"/api/cms_content_list.dspy",
"/api/cms_content_update.dspy",
"/api/category_options.dspy",
]
# ─── supervisor — 查看全部 + 审批配置 + 线索管理 ───
supervisor_paths = [
"/entcms",
"/entcms/admin.ui",
# 只读
"/entcms/cms_content_list", "/entcms/cms_content_list/%",
"/entcms/cms_categories_list", "/entcms/cms_categories_list/%",
"/entcms/cms_sections_list", "/entcms/cms_sections_list/%",
"/entcms/cms_site_config_list", "/entcms/cms_site_config_list/%",
# 列表API(只读)
"/entcms/api/cms_content_list.dspy",
"/entcms/api/cms_categories_list.dspy",
"/entcms/api/cms_sections_list.dspy",
"/entcms/api/cms_site_config_list.dspy",
"/entcms/api/category_options.dspy",
# 线索全权
"/entcms/cms_leads_list", "/entcms/cms_leads_list/%",
"/entcms/api/cms_leads_create.dspy",
"/entcms/api/cms_leads_update.dspy",
"/entcms/api/cms_leads_delete.dspy",
"/entcms/api/cms_leads_list.dspy",
# 审批
"/entcms/api/submit_content_approval.dspy",
"/admin.ui",
"/cms_content_list", "/cms_content_list/%",
"/cms_categories_list", "/cms_categories_list/%",
"/cms_sections_list", "/cms_sections_list/%",
"/cms_site_config_list", "/cms_site_config_list/%",
"/api/cms_content_list.dspy",
"/api/cms_categories_list.dspy",
"/api/cms_sections_list.dspy",
"/api/cms_site_config_list.dspy",
"/api/category_options.dspy",
"/cms_leads_list", "/cms_leads_list/%",
"/api/cms_leads_create.dspy",
"/api/cms_leads_update.dspy",
"/api/cms_leads_delete.dspy",
"/api/cms_leads_list.dspy",
"/api/submit_content_approval.dspy",
]
# ─── customer-support — 线索查看和更新 ───
support_paths = [
"/entcms",
"/entcms/admin.ui",
"/entcms/cms_leads_list", "/entcms/cms_leads_list/%",
"/entcms/api/cms_leads_list.dspy",
"/entcms/api/cms_leads_update.dspy",
"/admin.ui",
"/cms_leads_list", "/cms_leads_list/%",
"/api/cms_leads_list.dspy",
"/api/cms_leads_update.dspy",
]
print("=== entcms RBAC权限配置 ===")
print("=== CMS RBAC权限配置 ===")
print(f"\n--- any (匿名用户) ---")
run("any", any_paths)
print(f"\n--- owner.webmaster (内容管理员) ---")

View File

@ -47,7 +47,7 @@
"actiontype": "urlwidget",
"target": "app.sage_main_content",
"options": {
"url": "{{entire_url('/entcms/cms_content_list')}}"
"url": "{{entire_url('/cms_content_list')}}"
},
"mode": "replace"
}
@ -102,7 +102,7 @@
"actiontype": "urlwidget",
"target": "app.sage_main_content",
"options": {
"url": "{{entire_url('/entcms/cms_sections_list')}}"
"url": "{{entire_url('/cms_sections_list')}}"
},
"mode": "replace"
}
@ -157,7 +157,7 @@
"actiontype": "urlwidget",
"target": "app.sage_main_content",
"options": {
"url": "{{entire_url('/entcms/cms_categories_list')}}"
"url": "{{entire_url('/cms_categories_list')}}"
},
"mode": "replace"
}
@ -212,7 +212,7 @@
"actiontype": "urlwidget",
"target": "app.sage_main_content",
"options": {
"url": "{{entire_url('/entcms/cms_leads_list')}}"
"url": "{{entire_url('/cms_leads_list')}}"
},
"mode": "replace"
}
@ -267,7 +267,7 @@
"actiontype": "urlwidget",
"target": "app.sage_main_content",
"options": {
"url": "{{entire_url('/entcms/cms_site_config_list')}}"
"url": "{{entire_url('/cms_site_config_list')}}"
},
"mode": "replace"
}

View File

@ -6,37 +6,37 @@
{
"name": "cms_content_list",
"label": "内容管理",
"url": "{{entire_url('/entcms/cms_content_list')}}",
"url": "{{entire_url('/cms_content_list')}}",
"target": "app.sage_main_content"
},
{
"name": "cms_sections_list",
"label": "栏目管理",
"url": "{{entire_url('/entcms/cms_sections_list')}}",
"url": "{{entire_url('/cms_sections_list')}}",
"target": "app.sage_main_content"
},
{
"name": "cms_categories_list",
"label": "内容分类",
"url": "{{entire_url('/entcms/cms_categories_list')}}",
"url": "{{entire_url('/cms_categories_list')}}",
"target": "app.sage_main_content"
},
{
"name": "cms_leads_list",
"label": "商机线索",
"url": "{{entire_url('/entcms/cms_leads_list')}}",
"url": "{{entire_url('/cms_leads_list')}}",
"target": "app.sage_main_content"
},
{
"name": "cms_site_config_list",
"label": "站点配置",
"url": "{{entire_url('/entcms/cms_site_config_list')}}",
"url": "{{entire_url('/cms_site_config_list')}}",
"target": "app.sage_main_content"
},
{
"name": "public_site",
"label": "官网预览",
"url": "{{entire_url('/entcms/index.ui')}}",
"url": "{{entire_url('/index.ui')}}",
"target": "app.sage_main_content"
}
]

17
pyproject.toml Normal file
View File

@ -0,0 +1,17 @@
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "kaiyuan-cms"
version = "1.0.0"
description = "开元云科技企业官网CMS系统 — 独立Web应用"
requires-python = ">=3.8"
dependencies = [
"sqlor",
"bricks_for_python",
]
[tool.setuptools.packages.find]
where = ["."]
include = ["entcms*", "dingdingflow*"]

View File

@ -1,15 +1,41 @@
"""
初始化超级用户
用法: cd ~/repos/sage && ./py3/bin/python ~/repos/cms/scripts/init_superuser.py [username] [password]
用法:
CMS环境: py3/bin/python scripts/init_superuser.py [username] [password]
Sage环境: cd ~/repos/sage && ./py3/bin/python ~/repos/cms/scripts/init_superuser.py [username] [password]
默认: admin / admin123
"""
import os, sys, asyncio
# 自动检测运行环境
def find_workdir():
"""查找应用根目录"""
script_dir = os.path.dirname(os.path.abspath(__file__))
cms_root = os.path.dirname(script_dir)
# 检查是否是CMS独立环境
if os.path.isdir(os.path.join(cms_root, "py3", "bin")) and \
os.path.isfile(os.path.join(cms_root, "conf", "config.json")):
return cms_root
# 检查Sage环境
for c in [os.path.expanduser("~/repos/sage"), os.path.expanduser("~/sage")]:
if os.path.isdir(os.path.join(c, "py3", "bin")):
return c
return None
workdir = find_workdir()
if not workdir:
print("ERROR: 找不到CMS或Sage应用目录")
sys.exit(1)
# 确保Python路径
sys.path.insert(0, os.path.join(workdir, "py3", "lib"))
os.chdir(workdir)
from sqlor.dbpools import DBPools
from appPublic.jsonConfig import getConfig
from appPublic.uniqueID import getID
# Sage环境导入password_encode
sys.path.insert(0, os.path.expanduser("~/repos/sage"))
from appPublic.password import password_encode
async def main():
@ -23,7 +49,6 @@ async def main():
existing = await sor.R('users', {'username': username})
if existing:
print(f"用户 {username} 已存在 (id={existing[0]['id']})")
# 更新密码
await sor.U('users', {
'id': existing[0]['id'],
'passwd': password_encode(password)
@ -59,8 +84,7 @@ async def main():
users = await sor.R('users', {'username': username})
uid = users[0]['id']
# 查找或创建用户-角色关联 (userrole表)
# 检查userrole表是否存在
# 分配角色
try:
ur = await sor.R('userrole', {'userid': uid, 'roleid': role_id})
if not ur:
@ -75,6 +99,24 @@ async def main():
except Exception as e:
print(f"注意: userrole表操作异常: {e}")
print("可能需要手动分配角色")
# 给superuser分配全部权限
try:
all_perms = await sor.R('permission', {})
for perm in all_perms:
existing_rp = await sor.R('rolepermission', {
'roleid': role_id,
'permid': perm['id']
})
if not existing_rp:
await sor.C('rolepermission', {
'id': getID(),
'roleid': role_id,
'permid': perm['id']
})
print(f"已将全部 {len(all_perms)} 条权限分配给 owner.superuser")
except Exception as e:
print(f"注意: 权限分配异常: {e}")
print(f"\n登录信息:")
print(f" 用户名: {username}")

11
start.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/bash
# CMS独立应用启动脚本
cd "$(dirname "$0")"
WORKDIR="$(pwd)"
PIDFILE="$WORKDIR/cms.pid"
echo "启动 CMS Web Application (port 9090)..."
$WORKDIR/py3/bin/python $WORKDIR/app/cms.py -p 9090 -w $WORKDIR &
echo $! > $PIDFILE
echo "CMS started (PID: $(cat $PIDFILE))"
exit 0

22
stop.sh Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/bash
# CMS独立应用停止脚本
cd "$(dirname "$0")"
PIDFILE="$(pwd)/cms.pid"
if [ -f "$PIDFILE" ]; then
PID=$(cat "$PIDFILE")
if kill -0 "$PID" 2>/dev/null; then
echo "停止 CMS (PID: $PID)..."
kill "$PID"
sleep 2
if kill -0 "$PID" 2>/dev/null; then
kill -9 "$PID"
fi
echo "CMS已停止"
else
echo "CMS进程已不存在"
fi
rm -f "$PIDFILE"
else
echo "未找到PID文件"
fi