From 5781fb45ffad4793cf7b87aab461a366bc8b04b2 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 11 Sep 2026 18:29:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20inject=5Fproject=5Ffilter?= =?UTF-8?q?=E5=8D=87=E7=BA=A7v2=E2=80=94=E2=80=94=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=94=B9=E4=BC=9A=E8=AF=9D=E7=BA=A7=E8=A7=A3?= =?UTF-8?q?=E6=9E=90get=5Fsession=5Fproject=5Fid(session=E4=BC=98=E5=85=88?= =?UTF-8?q?+=E4=BA=A7=E7=BA=BF=E9=9A=94=E7=A6=BB),=E6=97=A0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=8A=A5=E9=94=99=E6=96=87=E6=A1=88=E6=94=B9'?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E6=88=96=E6=96=B0=E5=BB=BA=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?';=E4=BC=A0=E5=BA=94=E7=94=A8=E7=BB=9F=E4=B8=80wwwroot=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E5=A4=9A=E6=A8=A1=E5=9D=97;=E6=8A=95=E6=A0=87/?= =?UTF-8?q?=E5=95=86=E6=9C=BA=E7=94=9F=E6=88=90=E7=9B=AE=E5=BD=95git?= =?UTF-8?q?=E8=B7=9F=E8=B8=AA=E4=B8=8D=E8=B5=B0=E6=B3=A8=E5=85=A5(?= =?UTF-8?q?=E6=BA=90=E6=96=87=E4=BB=B6=E5=B7=B2=E6=89=8B=E6=94=B9=E6=8F=90?= =?UTF-8?q?=E4=BA=A4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sh | 6 ++- scripts/inject_project_filter.py | 92 +++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/build.sh b/build.sh index 4b53258..01b7b06 100644 --- a/build.sh +++ b/build.sh @@ -193,9 +193,11 @@ for d in sd_projects sd_iterations; do done done -# 6b. Inject session-current-project filter into generated get dspys (迭代/Bug/测试用例 用会话当前项目,无项目报错) +# 6b. Inject session-current-project filter into generated get dspys +# (开发/投标/商机产线功能页用会话当前项目过滤,无项目提示选择或新建; +# v2: 会话级解析 get_session_project_id,传应用统一 wwwroot 覆盖全部模块) if [ -f "$cdir/scripts/inject_project_filter.py" ]; then - "$cdir/py3/bin/python" "$cdir/scripts/inject_project_filter.py" "$cdir/wwwroot/pipeline-sdlc" || echo " WARN: inject_project_filter.py failed" + "$cdir/py3/bin/python" "$cdir/scripts/inject_project_filter.py" "$cdir/wwwroot" || echo " WARN: inject_project_filter.py failed" fi diff --git a/scripts/inject_project_filter.py b/scripts/inject_project_filter.py index cf53bf0..f84d976 100644 --- a/scripts/inject_project_filter.py +++ b/scripts/inject_project_filter.py @@ -1,43 +1,62 @@ #!/usr/bin/env python3 """给生成的 get_*.dspy 注入「会话当前项目强制过滤」(xls2ui 生成后 post-processing)。 -背景:迭代/Bug/测试用例 三个功能页面要「用会话当前项目,不选项目,没项目报错」。 -这些页面是 xls2ui 从 json/ + models/ 生成的(不入 git),无法在 json 源里表达 -「后端强制按 current_project_id 过滤」这类自定义逻辑,故在生成后注入: +背景:产线功能页面要「用会话当前项目过滤,没项目提示选择或新建项目」 +(2026-09-11 用户要求,覆盖开发/投标/商机产线)。 +这些页面是 xls2ui 从 json/ + models/ 生成的,无法在 json 源里表达 +「后端强制按当前项目过滤」这类自定义逻辑,故在生成后注入: -1. 在 ns = params_kw.copy() 之后注入「读 current_project_id + 无项目报错」代码; +1. 在 ns = params_kw.copy() 之后注入「读会话当前项目 + 无项目报错」代码; 2. SQL 里 where 1=1 [[filterstr]] 之前插入项目过滤条件(直接字段或子查询)。 +v2(2026-09-11): +- 项目解析从「全局 pipeline_agent_settings」升级为 get_session_project_id + (会话级 pipeline_session_settings 优先 → 全局兜底,含悬空自愈 + 产线隔离), + session_id/pipeline_id 从请求参数取(agent_menu_open 已透传给内嵌页面)。 +- PROJECT_FILTERS 泛化:sd_*(开发)+ bid_*(投标)+ opp_approvals(商机)。 + 幂等:已含 _cur_pid 的文件跳过;找不到锚点(ns 初始化 / where 1=1)跳过并告警。 -用法:py3/bin/python scripts/inject_project_filter.py +用法:py3/bin/python scripts/inject_project_filter.py [ ...] """ import sys import os -# 表 -> 项目过滤 SQL 片段(插在 where 1=1 [[filterstr]] 的 1=1 之后) +# 表 -> (所在模块 wwwroot 子目录, 项目过滤 SQL 片段) +# SQL 片段插在 where 1=1 [[filterstr]] 的 1=1 之后; +# 间接关联表用子查询回 project(scope 列约定:sd_* 业务表用各自归属列)。 +# ⚠️ 只放「生成目录不入 git」的模块(pipeline-sdlc 的 .gitignore 忽略 wwwroot/sd_iterations/ 等)—— +# 投标/商机的生成目录是 git 跟踪的,注入会弄脏服务器工作树(pull 静默 abort 跑旧代码), +# 那些表的项目过滤直接改源文件提交(bid_*/opp_approvals 的 get_*.dspy 已手写会话级过滤)。 PROJECT_FILTERS = { - "sd_iterations": "project_id = ${_cur_pid}$", - "sd_bugs": "iteration_id IN (SELECT id FROM sd_iterations WHERE project_id = ${_cur_pid}$)", - "sd_test_cases": ( - "plan_id IN (SELECT id FROM sd_test_plans WHERE iteration_id IN " - "(SELECT id FROM sd_iterations WHERE project_id = ${_cur_pid}$))" - ), + # ── 开发产线(pipeline-sdlc,生成目录 git-ignored)── + "sd_iterations": ("pipeline-sdlc", "project_id = ${_cur_pid}$"), + "sd_bugs": ("pipeline-sdlc", + "iteration_id IN (SELECT id FROM sd_iterations WHERE project_id = ${_cur_pid}$)"), + "sd_test_cases": ("pipeline-sdlc", + "plan_id IN (SELECT id FROM sd_test_plans WHERE iteration_id IN " + "(SELECT id FROM sd_iterations WHERE project_id = ${_cur_pid}$))"), } INJECT_CODE = ''' -# 会话当前项目强制过滤(auto-injected):读 current_project_id,无项目报错 +# 会话当前项目强制过滤(auto-injected v2):会话级解析(session 优先→全局兜底),无项目报错 _uid = await get_user() if not _uid: _uid = 'user-01' -_dbname = get_module_dbname('pipeline_sdlc') -async with DBPools().sqlorContext(_dbname) as _sor: - _prec = await _sor.sqlExe("SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": _uid}) - await _sor.sqlExe("COMMIT", {}) - _cur_pid = getattr(_prec[0], 'current_project_id', '') if _prec else '' +_sid = (params_kw or {}).get('session_id', '') or '' +_pl = (params_kw or {}).get('pipeline_id', '') or '' +_cur_pid = '' +try: + from pipeline_service.workspace import get_session_project_id + _dbname = get_module_dbname('pipeline_sdlc') + async with DBPools().sqlorContext(_dbname) as _sor: + _cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or '' +except Exception: + _cur_pid = '' if not _cur_pid: - return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":20,"cheight":9,"message":"请先在会话中切换项目"}} + return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}} ns['_cur_pid'] = _cur_pid +ns['project_id'] = _cur_pid ''' @@ -47,9 +66,12 @@ def process_file(path, filter_sql): changed = False - # 1. 注入读 current_project_id(幂等:已注入则跳过) + # 1. 注入读 current_project_id(幂等:已注入则跳过;旧版 v1 注入也重做) if "_cur_pid" in content: - print(f" skip {os.path.basename(path)}: 已注入") + if "get_session_project_id" in content: + print(f" skip {os.path.basename(path)}: 已注入(v2)") + return False + print(f" WARN {path}: 含旧版 v1 注入(全局指针),需人工核对升级") return False if "ns = params_kw.copy()" not in content: print(f" WARN {path}: 未找到 ns = params_kw.copy(),跳过") @@ -59,11 +81,13 @@ def process_file(path, filter_sql): changed = True # 2. SQL 加项目过滤 - if "where 1=1 [[filterstr]]" not in content: + if "where 1=1 [[filterstr]]" not in content and "where 1=1 [[filterstr]]" not in content: print(f" WARN {path}: 未找到 where 1=1 [[filterstr]],跳过 SQL 过滤") else: content = content.replace("where 1=1 [[filterstr]]", "where " + filter_sql + " [[filterstr]]", 1) + content = content.replace("where 1=1 [[filterstr]]", + "where " + filter_sql + " [[filterstr]]", 1) changed = True with open(path, "w", encoding="utf-8") as f: @@ -76,15 +100,23 @@ def main(): if len(sys.argv) < 2: print(__doc__) sys.exit(1) - wwwroot = sys.argv[1] + wwwroots = sys.argv[1:] ok = 0 - for tbl, fs in PROJECT_FILTERS.items(): - path = os.path.join(wwwroot, tbl, "get_" + tbl + ".dspy") - if not os.path.isfile(path): - print(f" missing {path}") - continue - if process_file(path, fs): - ok += 1 + for tbl, (mod, fs) in PROJECT_FILTERS.items(): + found = False + for wr in wwwroots: + # wwwroot 传的是应用统一 wwwroot(含各模块 symlink)或模块自身 wwwroot 均可 + for cand in (os.path.join(wr, mod, tbl), os.path.join(wr, tbl)): + path = os.path.join(cand, "get_" + tbl + ".dspy") + if os.path.isfile(path): + if process_file(path, fs): + ok += 1 + found = True + break + if found: + break + if not found: + print(f" missing get_{tbl}.dspy(模块 {mod},wwwroots={wwwroots})") print(f"inject_project_filter: {ok} 个文件已处理")