yumoqing ff667c58cd feat: 环境信息唯一事实源 + 应用脚手架 QC 硬门禁 + 基础模块 load 反模式
1) project-directory-spec 新增第八章「部署环境信息(env/)」:
   - 唯一事实源 projects/{项目名}/env/{test,prod}.json;禁止 apps/ 下放部署凭据
     (人事项目7 实测三份 env 矛盾:项目 9187 / 应用 9288+postgresql / 实际 mariadb)
   - 固定字段 schema;db.engine 必须与 DDL 方言一致
   - 铁律:环境信息由人提供,agent 不得编造;禁「待明确」占位当交付;缺字段冒泡 need_info
   - PM 在 need_info 未答结时不得 review_rollback(回退上游解决不了外部输入缺口)

2) qc/review-develop 新增第六章「应用脚手架硬门禁」(原技能只有模块检查,
   应用脚手架零检查项,Flask 因此两次过审):
   - 6.1 入口必须 ahserver,grep 命中 Flask/FastAPI/Django/jsonify 直接 reject
   - 6.2 基础模块 appbase/rbac 必须真正调 load_XXX(),只 import 标 loaded 属反模式
   - 6.3 端口三处一致 6.4 DDL 方言与 engine 一致 6.5 依赖完整性

3) web-application-spec 补「基础模块只 import 不调 load_XXX()」反模式警示
2026-08-25 21:27:10 +08:00

17 KiB
Raw Blame History

name version description trigger_conditions
web-application-spec 1.0.0 开发「应用脚手架」时必读——应用是唯一部署单元(一个入口 app/{应用名}.py + 一个端口init() 里定义 get_module_dbname 挂 ServerEnv 再逐个 load_{模块}() 挂载业务模块,含 conf/config.json、build.sh 一键部署。开发「业务模块」Python 包)时不要用本技能,改用 module-development-spec。
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. 应用入口必须用 ahserverapp/{应用名}.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.pydesign 在 architecture.md 写「基础模块 ahserver 直接复用」,实现偏离设计)。

  2. 应用必须有主页 UIwwwroot/index.ui 必须是完整 bricks widget 树(顶栏 + 导航菜单 + 主内容区),访问 / 渲染出可交互界面。绝对禁止占位敷衍(如 index.ui 只写「卡片导航」4 个字)。主页是应用门面,不是 JSON 元信息接口。

Required Dependencies

Core Dependencies (Mandatory)

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)

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)

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 installpip 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.txtwwwroot/i18n/{lang}/i18n.jsoni18n_getmsgs.dspy 供 bricks.js 读取)。
  5. 建表DDLjson2ddl 从模块 models/*.json 生成 mysql.ddl.sql,执行建表。
  6. CRUD 生成xls2ui 从模块 json/*.json 生成 CRUD.dspy + .ui
  7. 初始化数据python scripts/init_data.py 导入模块 init/data.jsonappcodes 等种子数据)。
  8. 运行挂载load_{module}():应用 app/{appname}.pyinit() 里逐个 load_{module}()(见上)。

不要给每个模块单独建 app.py、单独占端口、单独起服务——模块不是独立部署单元,应用才是。以上 8 步是应用一键部署正确执行的完整清单,缺一不可。

🔴 反模式:基础模块只 import 不调 load_XXX()

appbaserbac 同样有 load_XXX()appbase/init.py: load_appbase()rbac/init.py: load_rbac()),必须像业务模块一样显式调用。只 import不等于已挂载。

# ❌ 错误:只 import 就当成 loadedrbac 权限/登录、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 .tmpltmpl. 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:
    "processors": [
        [".tmpl", "tmpl"],
        [".ui", "bui"],
        [".dspy", "dspy"]
    ]
    
  • Website processors must include .uibui and .dspydspy 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:
    "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:

    "processors": [
        [".wss", "ws"],
        [".ws", "ws"],
        [".ui", "bui"],
        [".dspy", "dspy"]
    ]
    
  2. Nginx: The /wss/ location block handles WebSocket upgrade and strips the prefix:

    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):

    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):

    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:
    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:
      cd models/
      xls2ddl mysql . > mysql.ddl.sql
      mysql -h db -u[user] -p[password] [dbname] < mysql.ddl.sql
      
    • If models contain .json files:
      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:
    dbloader $cdir [dbname] data.xlsx
    
  5. CRUD Generation: For modules with json/ directory:
    cd json/
    xls2ui -m ../models -o ../wwwroot ${modulename} *.json
    
  6. wwwroot Symlinking: Create symbolic links for each module:
    ln -s $cdir/pkgs/$m/wwwroot $cdir/wwwroot/$m
    

B) Bricks Framework Setup

  • Execute bricks build.sh and symlink dist directory:
    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
  • 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.<id> 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)

// 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.<id> 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

  • .tmpltmpl 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