--- name: web-application-spec version: 1.0.0 description: 开发「应用脚手架」时必读——应用是唯一部署单元(一个入口 app/{应用名}.py + 一个端口),init() 里定义 get_module_dbname 挂 ServerEnv 再逐个 load_{模块}() 挂载业务模块,含 conf/config.json、build.sh 一键部署。开发「业务模块」(Python 包)时不要用本技能,改用 module-development-spec。 trigger_conditions: - User requests to create a new web application following the specified architecture - Task involves setting up application structure with ahserver, bricks, apppublic, sqlor - Development follows the documented directory structure and dependency requirements --- # Web Application Specification ## Overview This specification defines the standard architecture for web applications built using: - **Frontend**: bricks framework - **Backend**: ahserver application framework - **Foundation**: apppublic and sqlor modules - **Optional Modules**: appbase, rbac, and other developed modules All web applications must follow this exact structure and configuration pattern. ## 框架铁律(不可违反,禁止让 LLM 猜) 1. **应用入口必须用 ahserver**:`app/{应用名}.py` 必须 `from ahserver.webapp import webapp`,末尾 `webapp(init)` 启动。**绝对禁止** Flask / http.server / Django / FastAPI 等任何其它 Web 框架。原因:bricks 的 `.ui` 界面只有 ahserver 的 BricksUIProcessor 能渲染,用 Flask/http.server 会导致模块 `.ui` 界面(DataViewer CRUD 等)无法渲染,根路径 `/` 只剩一段 JSON 元信息——「测试环境界面显示 JSON 就停」的直接根因(2026-08-24 人事项目6 hrs6 实测:develop 用 Flask 写 app/hrs6.py,design 在 architecture.md 写「基础模块 ahserver 直接复用」,实现偏离设计)。 2. **应用必须有主页 UI**:`wwwroot/index.ui` 必须是**完整 bricks widget 树**(顶栏 + 导航菜单 + 主内容区),访问 `/` 渲染出可交互界面。**绝对禁止**占位敷衍(如 index.ui 只写「卡片导航」4 个字)。主页是应用门面,不是 JSON 元信息接口。 ## Required Dependencies ### Core Dependencies (Mandatory) ```bash pip install git+https://git.opencomputing.cn/yumoqing/apppublic pip install git+https://git.opencomputing.cn/yumoqing/sqlor pip install git+https://git.opencomputing.cn/yumoqing/ahserver ``` ### Database Web Application Dependencies (If using database) ```bash pip install git+https://git.opencomputing.cn/yumoqing/appbase pip install git+https://git.opencomputing.cn/yumoqing/rbac ``` ## 应用本地仓库位置 应用本地仓库在机构工作空间 `apps/{应用名}/`(见 project-directory-spec)。应用是唯一部署单元——一个应用一个仓库、一个入口(`app/{应用名}.py`)、一个端口,业务模块通过 `load_{模块}()` 挂到应用这个唯一入口下。 **部署测试前必须给应用仓库设置远程仓库**(`git remote add origin <远程地址>`),否则部署时无法 git pull 拉取最新代码。 ## Application Directory Structure ``` ${appname}/ ├── app/ │ └── ${appname}.py # Main application entry point ├── conf/ │ └── config.json # Application configuration ├── files/ # File storage directory ├── logs/ # Log files directory ├── wwwroot/ │ ├── imgs/ # SVG and image files │ └── bricks/ # Bricks framework distribution ├── build.sh # Initialization and deployment script └── .env # Environment variables ``` ## File Specifications ### app/${appname}.py (Main Application) ```python 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 对应的数据库名。库名由应用统一决定,模块禁止硬编码 DBNAME(见 module-development-spec)。 # 实现方式:应用级「模块名 → 库名」映射,从 appbase params 表 / conf/config.json 读,或直接映射表。 # 例:单库应用所有模块返回同一主库名;多库应用按模块返回各自库名。勿写死 'dbname'/'hrs6'。 return module_dbname_map(m) 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 env.get_module_dbname = get_module_dbname env.password_encode = password_encode load_appbase() load_rbac() load_pybricks() # ── 业务模块导入(关键:一个应用一个入口一个端口,所有模块挂在这里)── from organization.init import load_organization from payroll.init import load_payroll load_organization() load_payroll() if __name__ == '__main__': webapp(init) ``` > **应用对模块的完整导入(关键,build.sh 一键部署必须覆盖)**:一个应用 = 一个入口(`app/${appname}.py`)= 一个端口。业务模块(organization/payroll/recruitment/...)是 Python 包,不是独立服务。应用通过 build.sh 把各模块完整集成进来,步骤如下: > > 1. **代码导入(git pull)**:模块从 git clone 到 `pkgs/{模块名}/`(基础包 apppublic/sqlor/ahserver/rbac/xls2ddl/appbase/bricks 同)。禁止手动复制源码。 > 2. **依赖安装(pip install)**:`pip install -e pkgs/{模块名}`(或 `pip install`)安装所有模块;无 pyproject.toml 的用 PYTHONPATH。 > 3. **前端链接(wwwroot 软链)**:`ln -sf ../pkgs/{模块名}/wwwroot wwwroot/{模块名}`(软链接非 cp,保持同步)。 > 4. **i18n 导入**:merge_i18n.py 合并各模块 `i18n/{zh,en}/msg.txt` → `wwwroot/i18n/{lang}/i18n.json`(i18n_getmsgs.dspy 供 bricks.js 读取)。 > 5. **建表(DDL)**:json2ddl 从模块 `models/*.json` 生成 `mysql.ddl.sql`,执行建表。 > 6. **CRUD 生成**:xls2ui 从模块 `json/*.json` 生成 CRUD(.dspy + .ui)。 > 7. **初始化数据**:`python scripts/init_data.py` 导入模块 `init/data.json`(appcodes 等种子数据)。 > 8. **运行挂载(load_{module}())**:应用 `app/{appname}.py` 的 `init()` 里逐个 `load_{module}()`(见上)。 > > 不要给每个模块单独建 `app.py`、单独占端口、单独起服务——模块不是独立部署单元,应用才是。以上 8 步是应用一键部署正确执行的完整清单,缺一不可。 > **🔴 反模式:基础模块只 import 不调 load_XXX()** > > `appbase` 和 `rbac` **同样有 `load_XXX()`**(`appbase/init.py: load_appbase()`、`rbac/init.py: load_rbac()`),必须像业务模块一样显式调用。只 `import` 包**不等于**已挂载。 > > ```python > # ❌ 错误:只 import 就当成 loaded(rbac 权限/登录、appbase params 全没注册到 ServerEnv) > for name in ["apppublic","sqlor","ahserver","appbase","rbac"]: > pkg = importlib.import_module(name) > status = "loaded" # ← 误报成功,掩盖问题 > > # ✅ 正确:import 后取 loader 并调用 > from appbase.init import load_appbase > from rbac.init import load_rbac > env = ServerEnv() # ← 必须在 load_rbac() 之前 > load_appbase(); load_rbac(); load_pybricks() > ``` > > 症状:应用能起、`/healthz` 也可能 200,但登录必失败(rbac 未注册)、读 params 报错(appbase 未注册),日志却显示基础模块「loaded」。 > > 加载顺序固定:`ServerEnv()` → 基础模块(appbase/rbac/pybricks)→ 业务模块。基础模块晚于业务模块 load,业务模块拿不到权限/配置。 > > (2026-08-25 人事项目7 hrs7 实测:`load_base_modules()` 对 5 个基础模块只 `importlib.import_module` 就标 `status="loaded"`,而同文件的 `load_business_modules()` 却正确地 `getattr(pkg, loader)` + 调用——两套逻辑不一致,基础能力全部缺失。) ### conf/config.json (Configuration) Must include the following sections: - `password_key`: System encryption key - `logger`: Logging configuration with name, level, and file path - `filesroot`: File storage root path using `$[workdir]$` placeholder - `databases`: Database connection configurations with driver and kwargs - `website`: Web server configuration including paths, processors, session settings **Key Configuration Patterns:** - Use `$[workdir]$` placeholder for work directory references - Database names in `databases` section must match `get_module_name()` return values - **Critical: processors must include `.tmpl` → `tmpl`**. The BricksUIProcessor (handler for `.ui` files) internally loads `/bricks/header.tmpl` and other templates. Without `[".tmpl", "tmpl"]`, the app returns 500 `'NoneType' has no attribute 'be_call'` on `/` and `/index.ui`: ```json "processors": [ [".tmpl", "tmpl"], [".ui", "bui"], [".dspy", "dspy"] ] ``` - Website processors must include `.ui` → `bui` and `.dspy` → `dspy` mappings - **Website indexes**: Must include `"index.ui"` for bricks-based apps. Without it, visiting `/` or `/module/` returns 500 "invalid path" because ahserver cannot resolve directory URLs: ```json "indexes": ["index.ui", "index.html"] ``` - **WebSocket support**: Must include `[".wss", "ws"]` in processors list, otherwise `.wss` files are served as static files and WebSocket connections fail. Nginx `/wss/` block strips the prefix via `proxy_pass`, so the backend receives the path without `/wss` (e.g., `/module/file.wss`). ### WebSocket Configuration Details When adding WebSocket (`.wss`) endpoints to a module: 1. **Backend config.json**: Add `[".wss", "ws"]` to `website.processors`: ```json "processors": [ [".wss", "ws"], [".ws", "ws"], [".ui", "bui"], [".dspy", "dspy"] ] ``` 2. **Nginx**: The `/wss/` location block handles WebSocket upgrade and strips the prefix: ```nginx location /wss/ { proxy_pass http://localhost:9180/; # strips /wss prefix proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } ``` 3. **Frontend**: Connect to `/wss/module/file.wss` (with `/wss` prefix): ```javascript var url = protocol + '//' + host + '/wss/module/file.wss'; var ws = new WebSocket(url); // browser auto-sends cookie ``` 4. **RBAC permissions**: Register paths **without** `/wss` prefix (nginx strips it): ```bash python set_role_perm.py "logined" "/module/file.wss" ``` 5. **Common pitfall**: If ahserver logs `handle as a normal file` for a `.wss` request, the `[".wss","ws"]` processor mapping is missing from `config.json`. ahserver only recognizes extensions explicitly listed in `processors`. ### build.sh (Deployment Script) The build script must perform these operations in order: #### A) Module Dependencies Setup 1. **Install xls2ddl tool**: After creating virtual environment, install xls2ddl first: ```bash pip install xls2ddl ``` 2. **Module Installation**: Clone and pip install all required modules 3. **Database DDL Generation**: For modules with `models/` directory: - If models contain `.xlsx` files: ```bash cd models/ xls2ddl mysql . > mysql.ddl.sql mysql -h db -u[user] -p[password] [dbname] < mysql.ddl.sql ``` - If models contain `.json` files: ```bash cd models/ json2ddl mysql . > mysql.ddl.sql mysql -h db -u[user] -p[password] [dbname] < mysql.ddl.sql ``` 4. **Initial Data Import**: For modules with `data/` directory: ```bash dbloader $cdir [dbname] data.xlsx ``` 5. **CRUD Generation**: For modules with `json/` directory: ```bash cd json/ xls2ui -m ../models -o ../wwwroot ${modulename} *.json ``` 6. **wwwroot Symlinking**: Create symbolic links for each module: ```bash ln -s $cdir/pkgs/$m/wwwroot $cdir/wwwroot/$m ``` #### B) Bricks Framework Setup - Execute bricks build.sh and symlink dist directory: ```bash ln -s $cdir/pkgs/bricks/dist $cdir/wwwroot/bricks ``` #### C) System Service Configuration - Create systemd service file with proper user/group context - Generate start.sh and stop.sh scripts with environment loading - Enable and start the service on system boot #### D) Cron Job Management - Add application-specific cron jobs without duplication ### .env (Environment Variables) - Export all runtime environment variables required by the application - Include database credentials, API keys, and system paths ## Build Script Requirements ### Directory Creation - Create `pkgs/` directory for module clones - Create `logs/` directory for application logging ### Module Processing Order 1. Install foundation modules (apppublic, sqlor, ahserver, bricks) 2. **Clone and process database modules** (appbase, rbac): - Clone from Git repositories to pkgs/ directory - Install via pip - Generate DDL from models/ directory (.xlsx or .json files) - Generate CRUD UI from json/ directory using xls2ui 3. Install business modules (accounting, pricing, contract_management, etc.) following the same pattern as database modules ### Database Operations - Process **all modules** that have `models/`, `json/`, or `data/` directories (including appbase and rbac) - Use proper error handling for database operations - Maintain database isolation per module when configured ### Symbolic Links - All module wwwroot directories must be symlinked to application wwwroot - Bricks dist directory must be symlinked to wwwroot/bricks ### Service Management - Create proper systemd service with logging redirection - Implement graceful start/stop scripts - Set appropriate file permissions (chmod +x for scripts) ## Bricks UI Development Pitfalls See `references/ragserver-pitfalls.md` for complete debugging recipes. See `references/sage-module-pitfalls.md` for SQL column names, `params_kw=None`, deployment, and error investigation rules. - Widget `id` at top level (NOT in `options`) - DOM order for `app.` resolution - `.tmpl` processor required in config - `bricks_for_python` import + `load_pybricks()` call - DataViewer response format (`{status, data:{rows,total}}`) - Menu widget target pattern - Float model fields needing `length` + `dec` ### Widget `id` at Top Level (Not in `options`) ```json // CORRECT {"widgettype":"VBox","id":"main_content","options":{"css":"filler"}} // WRONG — id inside options is ignored {"widgettype":"VBox","options":{"id":"main_content","css":"filler"}} ``` ### DOM Order Affects `app.` Resolution Put the content area BEFORE the sidebar in `subwidgets`. Bricks initializes widgets left-to-right. ## Configuration Best Practices ### Security - Store sensitive data (passwords, keys) in encrypted form in config.json - Use environment variables for runtime secrets via .env file - Implement proper file permissions for logs and files directories ### Performance - Configure appropriate client_max_size for file uploads - Set optimal session timeout values (session_max_time, session_issue_time) - Use Redis for session storage in production ### Maintainability - Use consistent naming patterns across all configuration sections - Document custom processor mappings in website.processors - Maintain clear separation between development and production configurations ## Verification Checklist - [ ] `.tmpl` → `tmpl` processor in `website.processors` (required for bricks UI, or `/` returns 500) - [ ] `bricks_for_python` imported and `load_pybricks()` called in `init()` - [ ] `ServerEnv` initialized BEFORE `load_rbac()` / `load_appbase()` - [ ] Website `indexes` config includes `"index.ui"` (required for directory URL resolution) - `session_max_time` and `session_issue_time` in config for Redis session persistence (restart won't lose login) - [ ] All required dependencies are installed from correct Git repositories - [ ] Application directory structure matches specification exactly - [ ] Main application file includes proper module loading pattern - [ ] Configuration file uses correct placeholders and structure - [ ] Build script handles all module types (foundation, database, business) - [ ] Database DDL generation works for all modules with models/ - [ ] CRUD generation executes for all modules with json/ - [ ] Initial data loads correctly for modules with data/ - [ ] All wwwroot symlinks are created properly - [ ] Bricks framework is built and linked correctly - [ ] System service starts and stops gracefully - [ ] Environment variables are loaded from .env file - [ ] Application runs with proper logging and error handling