diff --git a/app_audit/audit_service.py b/app_audit/audit_service.py index e711f1e..9ee182b 100644 --- a/app_audit/audit_service.py +++ b/app_audit/audit_service.py @@ -15,6 +15,7 @@ app_audit/audit_service.py - 审计日志全局模块 工作环境:work_env_set / org_key_gen / remote_bwrap 部署账号:account_create / account_remove / sandbox_run 用户机构:user_create / user_disable / user_delete / org_change + 财务:customer_recharge 审计自身:audit_delete / audit_backup """ @@ -29,6 +30,7 @@ VALID_ACTIONS = { "work_env_set", "org_key_gen", "remote_bwrap", "account_create", "account_remove", "sandbox_run", "user_create", "user_disable", "user_delete", "org_change", + "customer_recharge", "audit_delete", "audit_backup", } diff --git a/app_audit/init.py b/app_audit/init.py index eac85d7..5a87cbc 100644 --- a/app_audit/init.py +++ b/app_audit/init.py @@ -16,9 +16,11 @@ def load_app_audit(): async def _init_audit(app): from sqlor.dbpools import DBPools from appPublic.log import debug + from ahserver.serverenv import ServerEnv try: db = DBPools() - async with db.sqlorContext("pipeline") as sor: + dbname = ServerEnv().get_module_dbname('app_audit') + async with db.sqlorContext(dbname) as sor: # 审计日志表(append-only,全局) await sor.sqlExe( "CREATE TABLE IF NOT EXISTS sd_audit_logs (" diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..d17c59a --- /dev/null +++ b/build.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# app_audit module build script +# 链接 wwwroot 到宿主应用(Sage 或 pipeline)。审计表由 load_app_audit() 启动时自动建。 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# 查找宿主应用根目录(含 wwwroot 目录) +APP_ROOT="" +for candidate in "$SCRIPT_DIR/../.." "$HOME/repos/sage" "$HOME/sage" "$HOME/work/repos/sage"; do + if [ -d "$candidate/wwwroot" ]; then + APP_ROOT="$(cd "$candidate" && pwd)" + break + fi +done + +if [ -z "$APP_ROOT" ]; then + echo "ERROR: host app root not found" + exit 1 +fi + +echo "Host app root: $APP_ROOT" + +# 链接 wwwroot 到宿主应用 +rm -f "$APP_ROOT/wwwroot/app_audit" +ln -sf "$SCRIPT_DIR/wwwroot" "$APP_ROOT/wwwroot/app_audit" + +echo "app_audit module build complete (wwwroot linked)." +echo "提示:Python 包需 pip install . ;RBAC 权限用 scripts/load_path.py 注册。" diff --git a/wwwroot/api/audit.dspy b/wwwroot/api/audit.dspy index 458ae2f..5df19fb 100644 --- a/wwwroot/api/audit.dspy +++ b/wwwroot/api/audit.dspy @@ -2,6 +2,8 @@ # 查看/备份/删除 仅 owner.audit 角色(审计独立性:superuser 也无权访问) # action: list / backup / delete +_dbname = get_module_dbname('app_audit') + user_id = await get_user() if not user_id: return json.dumps({'ok': False, 'error': '未登录'}, ensure_ascii=False) @@ -15,7 +17,7 @@ from app_audit.audit_service import ( # 查 username 和 client_ip username = user_id try: - async with get_sor_context(request._run_ns, 'pipeline') as _sor: + async with get_sor_context(request._run_ns, _dbname) as _sor: _u = await _sor.sqlExe("SELECT username FROM users WHERE id=${u}$", {'u': user_id}) if _u: username = getattr(_u[0], 'username', user_id) or user_id @@ -29,7 +31,7 @@ except Exception: pass # 权限校验:所有 action 仅 owner.audit(superuser 也不行) -async with get_sor_context(request._run_ns, 'pipeline') as _sor: +async with get_sor_context(request._run_ns, _dbname) as _sor: if not await is_audit_role(_sor, user_id): return json.dumps({'ok': False, 'error': '仅 owner.audit 角色可访问审计日志'}, ensure_ascii=False) @@ -44,7 +46,7 @@ if action == 'list': page = (params_kw or {}).get('page', 1) rows = (params_kw or {}).get('rows', 60) try: - async with get_sor_context(request._run_ns, 'pipeline') as sor: + async with get_sor_context(request._run_ns, _dbname) as sor: r = await list_audit_logs(sor, filters, page, rows) return json.dumps(r, ensure_ascii=False) except Exception as e: @@ -60,7 +62,7 @@ elif action == 'backup': } blimit = (params_kw or {}).get('limit', 10000) try: - async with get_sor_context(request._run_ns, 'pipeline') as sor: + async with get_sor_context(request._run_ns, _dbname) as sor: r = await backup_audit_logs(sor, user_id, username, bfilters, blimit, client_ip) return json.dumps(r, ensure_ascii=False) except Exception as e: @@ -69,7 +71,7 @@ elif action == 'backup': elif action == 'delete': before = (params_kw or {}).get('before', '') try: - async with get_sor_context(request._run_ns, 'pipeline') as sor: + async with get_sor_context(request._run_ns, _dbname) as sor: r = await delete_audit_logs(sor, user_id, username, before, client_ip) return json.dumps(r, ensure_ascii=False) except Exception as e: