立项配置数据入库、审计日志数据入库、编辑页UI界面美化、设备管理内容补充、审计日志显示优化
This commit is contained in:
Executable
+172
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass, asdict
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
FRONTEND_SRC = REPO_ROOT / "frontend" / "src"
|
||||
|
||||
FILE_GLOBS = ("**/*.ts", "**/*.tsx", "**/*.vue")
|
||||
CALL_PATTERNS = {
|
||||
"localStorage.getItem": re.compile(r"localStorage\.getItem\(([^)]+)\)"),
|
||||
"localStorage.setItem": re.compile(r"localStorage\.setItem\(([^,)]+)"),
|
||||
"localStorage.removeItem": re.compile(r"localStorage\.removeItem\(([^)]+)\)"),
|
||||
"sessionStorage.getItem": re.compile(r"sessionStorage\.getItem\(([^)]+)\)"),
|
||||
"sessionStorage.setItem": re.compile(r"sessionStorage\.setItem\(([^,)]+)"),
|
||||
"sessionStorage.removeItem": re.compile(r"sessionStorage\.removeItem\(([^)]+)\)"),
|
||||
}
|
||||
|
||||
BANNED_KEY_MARKERS = ("audit_local_",)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Finding:
|
||||
file: str
|
||||
line: int
|
||||
call: str
|
||||
key_expr: str
|
||||
severity: str
|
||||
category: str
|
||||
note: str
|
||||
|
||||
|
||||
def iter_source_files() -> Iterable[Path]:
|
||||
for pattern in FILE_GLOBS:
|
||||
for path in FRONTEND_SRC.glob(pattern):
|
||||
if path.is_file():
|
||||
yield path
|
||||
|
||||
|
||||
def classify(file_path: str, key_expr: str) -> tuple[str, str, str]:
|
||||
normalized = key_expr.replace('"', "").replace("'", "").strip()
|
||||
lowered = normalized.lower()
|
||||
file_lower = file_path.lower()
|
||||
|
||||
if "projectdetail.vue" in file_lower:
|
||||
if "publishedprojectsignaturekey" in lowered:
|
||||
return ("medium", "publish-helper", "发布比对签名缓存,属于辅助数据")
|
||||
if "storagekey" in lowered or "projectdraftstoragekey" in lowered:
|
||||
return ("high", "business-draft", "立项配置草稿本地兜底,需显式提示未落库")
|
||||
|
||||
if "audit_local_" in lowered:
|
||||
return ("high", "audit-data", "审计日志本地缓存,不能作为正式审计数据源")
|
||||
if "ctms_setup_config_draft_" in lowered or "ctms_setup_project_draft_" in lowered:
|
||||
return ("high", "business-draft", "立项配置草稿本地兜底,需显式提示未落库")
|
||||
if "ctms_setup_published_project_sig_" in lowered:
|
||||
return ("medium", "publish-helper", "发布比对签名缓存,属于辅助数据")
|
||||
|
||||
if any(k in lowered for k in ("token_key", "last_login_email_key", "logout_reason_storage_key")):
|
||||
return ("low", "auth-session", "认证/会话数据(非业务主数据)")
|
||||
if any(k in lowered for k in ("ctms_token", "ctms_last_login_email", "credential", "auth")):
|
||||
return ("low", "auth-session", "认证/会话数据(非业务主数据)")
|
||||
if any(k in lowered for k in ("ctms_sidebar_collapsed", "study", "site", "role")):
|
||||
return ("low", "ui-preference", "UI偏好/上下文缓存")
|
||||
|
||||
if "personaltodo" in file_path.lower():
|
||||
return ("low", "feature-cache", "工作台本地待办缓存(非核心主数据)")
|
||||
|
||||
return ("medium", "unknown", "未命中规则,建议人工复核是否为业务关键数据")
|
||||
|
||||
|
||||
def scan_findings() -> list[Finding]:
|
||||
findings: list[Finding] = []
|
||||
for path in iter_source_files():
|
||||
try:
|
||||
content = path.read_text(encoding="utf-8")
|
||||
except UnicodeDecodeError:
|
||||
content = path.read_text(encoding="utf-8", errors="ignore")
|
||||
lines = content.splitlines()
|
||||
rel = str(path.relative_to(REPO_ROOT))
|
||||
for idx, line in enumerate(lines, start=1):
|
||||
for call_name, pattern in CALL_PATTERNS.items():
|
||||
for match in pattern.finditer(line):
|
||||
key_expr = match.group(1).strip()
|
||||
severity, category, note = classify(rel, key_expr)
|
||||
findings.append(
|
||||
Finding(
|
||||
file=rel,
|
||||
line=idx,
|
||||
call=call_name,
|
||||
key_expr=key_expr,
|
||||
severity=severity,
|
||||
category=category,
|
||||
note=note,
|
||||
)
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def markdown_report(findings: list[Finding]) -> str:
|
||||
counts: dict[str, int] = {"high": 0, "medium": 0, "low": 0}
|
||||
for item in findings:
|
||||
counts[item.severity] = counts.get(item.severity, 0) + 1
|
||||
|
||||
lines = [
|
||||
"# 存储落库核查报告",
|
||||
"",
|
||||
f"- 扫描目录: `{FRONTEND_SRC}`",
|
||||
f"- 总发现数: `{len(findings)}`",
|
||||
f"- 高风险: `{counts['high']}` / 中风险: `{counts['medium']}` / 低风险: `{counts['low']}`",
|
||||
"",
|
||||
"## 明细",
|
||||
"",
|
||||
"| 风险 | 分类 | 文件 | 行号 | 调用 | key表达式 | 说明 |",
|
||||
"| --- | --- | --- | --- | --- | --- | --- |",
|
||||
]
|
||||
for item in sorted(findings, key=lambda x: (x.severity, x.file, x.line)):
|
||||
lines.append(
|
||||
f"| {item.severity} | {item.category} | `{item.file}` | {item.line} | `{item.call}` | `{item.key_expr}` | {item.note} |"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Scan frontend storage usage and classify persistence risks.")
|
||||
parser.add_argument("--format", choices=("markdown", "json"), default="markdown")
|
||||
parser.add_argument("--output", default="", help="Optional output file path.")
|
||||
parser.add_argument(
|
||||
"--fail-on-banned",
|
||||
action="store_true",
|
||||
help="Exit with code 2 when banned local key markers are detected.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
findings = scan_findings()
|
||||
|
||||
banned_hits = []
|
||||
if args.fail_on_banned:
|
||||
for item in findings:
|
||||
expr = item.key_expr.lower().replace('"', "").replace("'", "")
|
||||
if any(marker in expr for marker in BANNED_KEY_MARKERS):
|
||||
banned_hits.append(item)
|
||||
|
||||
if args.format == "json":
|
||||
output_text = json.dumps([asdict(item) for item in findings], ensure_ascii=False, indent=2)
|
||||
else:
|
||||
output_text = markdown_report(findings)
|
||||
|
||||
if args.output:
|
||||
out = Path(args.output)
|
||||
if not out.is_absolute():
|
||||
out = REPO_ROOT / out
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(output_text + "\n", encoding="utf-8")
|
||||
else:
|
||||
print(output_text)
|
||||
|
||||
if banned_hits:
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user