Files
wecom_it_smart_desk/docs/07-项目管理/巡检报告/2026-08/2026-08-06-早班.html
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
2026-08-07 22:31:32 +08:00

378 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><text y='14' font-size='14'>📋</text></svg>">
<title>早班巡检报告 · 2026-08-06 06:00 · v3.0</title>
<style>
:root {
--accent: #07C160;
--accent-soft: #E7F8EF;
--warn: #FA9D3B;
--danger: #FA5151;
--info: #10AEFF;
--text: #1F2329;
--text-soft: #646A73;
--bg: #FFFFFF;
--bg-soft: #F7F8FA;
--border: #DEE0E3;
--shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
--radius: 8px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--text);
background: var(--bg-soft);
line-height: 1.6;
font-size: 14px;
}
.container { max-width: 1080px; margin: 0 auto; padding: 32px 24px 64px; }
header {
background: linear-gradient(135deg, var(--accent) 0%, #06AD56 100%);
color: #fff;
padding: 32px;
border-radius: var(--radius);
box-shadow: var(--shadow);
margin-bottom: 24px;
}
header h1 { font-size: 26px; font-weight: 600; margin-bottom: 8px; }
header .meta { font-size: 13px; opacity: 0.92; }
header .meta span { margin-right: 18px; }
header .meta strong { font-weight: 600; }
h2 {
font-size: 18px;
font-weight: 600;
margin: 32px 0 14px;
padding-left: 10px;
border-left: 4px solid var(--accent);
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
margin-bottom: 8px;
}
.summary-card {
background: #fff;
padding: 14px 16px;
border-radius: var(--radius);
box-shadow: var(--shadow);
text-align: center;
}
.summary-card .num { font-size: 24px; font-weight: 700; color: var(--accent); }
.summary-card .label { font-size: 12px; color: var(--text-soft); margin-top: 4px; }
.summary-card.warn .num { color: var(--warn); }
.summary-card.danger .num { color: var(--danger); }
.summary-card.info .num { color: var(--info); }
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; margin-bottom: 24px; }
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
th { background: var(--bg-soft); font-size: 12px; font-weight: 600; color: var(--text-soft); }
tr:last-child td { border-bottom: none; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
.badge.p0 { background: #FEE; color: var(--danger); border: 1px solid var(--danger); }
.badge.p1 { background: #FFF3E0; color: var(--warn); border: 1px solid var(--warn); }
.badge.decide { background: #E6F4FF; color: var(--info); border: 1px solid var(--info); }
.badge.doing { background: var(--accent-soft); color: var(--accent); border: 1px solid var(--accent); }
.badge.ok { background: var(--accent-soft); color: var(--accent); }
.badge.fail { background: #FEE; color: var(--danger); }
code { background: var(--bg-soft); padding: 2px 6px; border-radius: 3px; font-family: "Cascadia Code", Consolas, monospace; font-size: 12px; }
.alert { background: #FFF8E6; border-left: 4px solid var(--warn); padding: 12px 16px; margin: 16px 0; border-radius: 4px; }
.alert.danger { background: #FEE; border-left-color: var(--danger); }
.alert strong { display: block; margin-bottom: 4px; }
.legend { color: var(--text-soft); font-size: 12px; margin-top: 4px; margin-bottom: 16px; }
footer { margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--border); color: var(--text-soft); font-size: 12px; text-align: center; }
ul.condensed { list-style: none; padding: 0; }
ul.condensed li { padding: 8px 12px; border-bottom: 1px dashed var(--border); background: #fff; }
ul.condensed li:last-child { border-bottom: none; }
ul.condensed li:first-child { border-radius: var(--radius) var(--radius) 0 0; }
ul.condensed li:last-child { border-radius: 0 0 var(--radius) var(--radius); box-shadow: var(--shadow); }
.filter-bar { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 8px; padding: 12px 14px; background: #fff; border-radius: var(--radius); box-shadow: var(--shadow); align-items: center; }
.filter-bar .filter-label { font-size: 12px; color: var(--text-soft); font-weight: 600; margin-right: 4px; }
.filter-btn { cursor: pointer; border: 1px solid var(--border); background: #fff; color: var(--text-soft); padding: 6px 12px; border-radius: 16px; font-size: 12px; font-weight: 600; transition: all 0.15s ease; user-select: none; }
.filter-btn:hover { transform: translateY(-1px); box-shadow: 0 2px 6px rgba(0,0,0,0.08); }
.filter-btn.active { color: #fff; border-color: transparent; }
.filter-btn[data-cat="all"].active { background: var(--text); }
.filter-btn[data-cat="p0"].active { background: var(--danger); }
.filter-btn[data-cat="decide"].active { background: var(--info); }
.filter-btn[data-cat="doing"].active { background: var(--accent); }
.filter-btn[data-cat="server"].active { background: var(--danger); }
.filter-btn[data-cat="code"].active { background: #8B5CF6; }
.filter-btn[data-cat="security"].active { background: #DC2626; }
.filter-btn[data-cat="history"].active { background: #64748B; }
.filter-btn .badge-count { display: inline-block; margin-left: 6px; padding: 1px 6px; background: rgba(0,0,0,0.08); border-radius: 8px; font-size: 10px; }
.filter-btn.active .badge-count { background: rgba(255,255,255,0.25); }
.hidden { display: none !important; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>📋 早班巡检报告 · IT 智能服务台</h1>
<div class="meta">
<span>📅 <strong>2026-08-06 06:00</strong>GMT+8</span>
<span>🛰️ 服务器 <strong>10.90.5.110</strong></span>
<span>📄 看板 <strong>v1.9.0-DRAFT</strong></span>
<span>🤖 执行 <strong>Duckula</strong></span>
<span>📦 看板源 <code>docs/07-项目管理/项目状态看板.md</code></span>
</div>
</header>
<div class="summary-grid">
<div class="summary-card danger"><div class="num">5</div><div class="label">🔴 P0 必做</div></div>
<div class="summary-card info"><div class="num">2</div><div class="label">🟢 等决策</div></div>
<div class="summary-card"><div class="num">1</div><div class="label">⏳ 进行中</div></div>
<div class="summary-card danger"><div class="num">2</div><div class="label">🖥️ 服务器异常项</div></div>
</div>
<!-- 分类筛选 -->
<div class="filter-bar">
<span class="filter-label">🔍 分类筛选</span>
<button class="filter-btn active" data-cat="all">全部<span class="badge-count">11</span></button>
<button class="filter-btn" data-cat="p0">🔴 P0<span class="badge-count">5</span></button>
<button class="filter-btn" data-cat="decide">🟢 等决策<span class="badge-count">2</span></button>
<button class="filter-btn" data-cat="doing">⏳ 进行中<span class="badge-count">1</span></button>
<button class="filter-btn" data-cat="server">🖥️ 服务器<span class="badge-count">8</span></button>
<button class="filter-btn" data-cat="code">🔍 代码<span class="badge-count">5</span></button>
<button class="filter-btn" data-cat="security">🔐 安全<span class="badge-count">4</span></button>
<button class="filter-btn" data-cat="history">🔄 对比/关注<span class="badge-count">5</span></button>
</div>
<!-- P0 必做 -->
<h2 data-section="p0">🔴 P0 必做(高优先级)</h2>
<p class="legend">本次巡检最高优先级阻塞 / 待修复项</p>
<table>
<thead>
<tr><th>编号</th><th>任务</th><th>类别</th><th>说明</th></tr>
</thead>
<tbody>
<tr data-cat="p0">
<td><span class="badge p0">P0-1</span></td>
<td><code>/itportal/</code> 入口 500 错误</td>
<td>♻️ 历史遗留</td>
<td>2026-08-06 06:00 jumpserver-V2 实测仍 500nginx error: <code>rewrite or internal redirection cycle while internally redirecting to &quot;/itportal/index.html&quot;</code>。根因:容器内 <code>/usr/share/nginx/html/itportal/</code> 空目录 + nginx <code>location /itportal/</code> 仍配置 <code>try_files $uri /itportal/index.html</code>。建议:<code>docker compose up -d --force-recreate nginx</code>(前提:commit 21830d5 中 nginx.conf 已删除该 location 块)</td>
</tr>
<tr data-cat="p0">
<td><span class="badge p0">P0-3</span></td>
<td><code>closing_service.py:467</code> datetime 时区错位</td>
<td>♻️ 历史遗留</td>
<td><code>datetime.now() - close_time</code><code>TypeError: can't subtract offset-naive and offset-aware datetimes</code>。影响 <code>POST /h5/conversations/current/reopen</code>。同文件 18 处 <code>datetime.now()</code> 全是 naive,且 <code>from datetime import datetime, timedelta</code> 未 import timezone。24h 内未触发,但用户使用 H5 重开会话即会爆</td>
</tr>
<tr data-cat="p0">
<td><span class="badge p0">P0-4</span></td>
<td><code>employee_profile_service.py:236</code> SessionLocal NoneType 🔥</td>
<td>🆕 位置修正</td>
<td><strong>看板 v1.8 行号描述错误</strong>:实际错误位置是 <code>app/services/employee_profile_service.py:236</code> 而非 <code>h5_ai_task.py:1198</code><code>db = SessionLocal()</code> 返回 None → <code>TypeError: 'NoneType' object is not callable</code>。2026-08-05 24h 内累计 10+ 次(01:33 / 03:25 / 04:07 / 04:11 多波次),影响 H5 IT 资产推荐异步推送。<strong>同根因 P0-5</strong></td>
</tr>
<tr data-cat="p0">
<td><span class="badge p0">P0-5</span></td>
<td>容器 <code>app/constants/</code> 打包错误 🔥</td>
<td>🆕 新增</td>
<td>容器内 <code>/app/app/constants/__init__.py</code> 11635 bytes 内容是 automation.py(两个文件互换了),<code>/app/app/constants/automation.py</code> <strong>不存在</strong>。直接后果:连续 10+ 次 <code>ModuleNotFoundError: No module named 'app.constants.ai_reply_mode'</code> + 同根因连锁导致 P0-4SessionLocal 绑定失败)。建议:① 临时 <code>docker cp</code> 修补 → ② 长期排查 backend 镜像构建流程</td>
</tr>
<tr data-cat="p0">
<td><span class="badge p0">P0-6</span></td>
<td>后端 OAuth 40029 抖动</td>
<td>️ 信息类</td>
<td>218.75.34.87 等外部 IP 持续试探 <code>/api/auth/validate-sso</code> + 偶发 OAuth code 失效。属 WAF 扫描背景噪声,已 fail2ban 监控。无需主动处理</td>
</tr>
</tbody>
</table>
<!-- 等用户决策 -->
<h2 data-section="decide">🟢 等用户决策(阻塞项)</h2>
<p class="legend">均超 3 天阈值,需 PM 升级催办</p>
<table>
<thead><tr><th>编号</th><th>任务</th><th>阻塞时长</th><th>卡点 / Owner</th></tr></thead>
<tbody>
<tr data-cat="decide">
<td><span class="badge decide">BLK-A</span></td>
<td>企微会议室 Secret</td>
<td>⚠️ <strong>26 天</strong>(自 2026-07-11</td>
<td>需企业微信管理后台申请。Owner: 平台组</td>
</tr>
<tr data-cat="decide">
<td><span class="badge decide">BLK-B</span></td>
<td>ITSM API 授权</td>
<td>⚠️ <strong>26 天</strong>(自 2026-07-11</td>
<td>需向 ITSM 平台方申请 app_id/app_secret。Owner: 平台组</td>
</tr>
</tbody>
</table>
<!-- 进行中 -->
<h2 data-section="doing">⏳ 进行中 (In Progress)</h2>
<table>
<thead><tr><th>编号</th><th>任务</th><th>状态</th><th>说明</th></tr></thead>
<tbody>
<tr data-cat="doing">
<td><span class="badge doing">#81</span></td>
<td>敏感词检测 + 语气优化</td>
<td>v1.2 待排期</td>
<td>v1.1 阶段 1 已于 2026-07-28 完成(词库入库 + 后台管理 UI + 12 配套 API)。剩余 &quot;语气优化、灰度策略细化&quot; 待 v1.2 排期</td>
</tr>
</tbody>
</table>
<!-- 服务器状态 -->
<h2 data-section="server">🖥️ 服务器运行状态</h2>
<table>
<thead><tr><th>组件</th><th>状态</th><th>实测数据</th></tr></thead>
<tbody>
<tr data-cat="server">
<td>Docker 容器</td>
<td><span class="badge ok">正常</span></td>
<td>5/5 healthynginx (Up 12h) / backend (Up 17h) / redis (Up 2w) / postgres (Up 3w) / neo4j (Up 3w)。无 Exited/Restarting/Unhealthy</td>
</tr>
<tr data-cat="server">
<td>后端 health</td>
<td><span class="badge ok">正常</span></td>
<td><code>/health</code> → HTTP 200(容器内 3.9ms);外部 <code>curl http://localhost:8000</code> 000(端口未映射给宿主,nginx 反代访问正常)</td>
</tr>
<tr data-cat="server">
<td>后端 5xx 错误</td>
<td><span class="badge fail">异常</span></td>
<td>24h 内仍见 2 类 TracebackP0-5 ModuleNotFoundError10+ 次)+ P0-4 SessionLocal NoneType10+ 次)</td>
</tr>
<tr data-cat="server">
<td>PostgreSQL</td>
<td><span class="badge ok">正常</span></td>
<td><code>pg_isready -U postgres</code><code>accepting connections</code></td>
</tr>
<tr data-cat="server">
<td>Redis</td>
<td><span class="badge ok">正常</span></td>
<td><code>redis_version:7.4.9</code> / 独立模式 + 密码认证(NOAUTH 正常);已配密码 <code>WKzl7jgKTkeWxFWGIBXfzokm59Gvfr76</code></td>
</tr>
<tr data-cat="server">
<td>Nginx 配置</td>
<td><span class="badge ok">正常</span></td>
<td><code>nginx -t</code> → syntax OK80/443 已 bind</td>
</tr>
<tr data-cat="server">
<td>前端入口</td>
<td><span class="badge fail">异常</span></td>
<td><code>/itportal/</code> <strong>500</strong>rewrite cycle);<code>/h5/</code><code>/itdesk/</code> 200 OK<code>/</code> 200612 bytes root index.html</td>
</tr>
<tr data-cat="server">
<td>磁盘 / 内存</td>
<td><span class="badge ok">正常</span></td>
<td>130G 可用(13% 用)/ 3.4G used / 11G available / 负载 0.99 / 已运行 54 天</td>
</tr>
</tbody>
</table>
<!-- 代码规范与质量 -->
<h2 data-section="code">🔍 代码规范与质量检查</h2>
<ul class="condensed">
<li data-cat="code"><strong>🟢 鉴权补漏</strong>sensitive_words.py 13 端点已正确加 <code>dependencies=[Depends(require_admin)]</code> (line 47-51)。看板描述脱节,v1.9 已迁移至已完成</li>
<li data-cat="code"><strong>🔴 容器/源码不一致</strong>:容器内 <code>app/constants/__init__.py</code> = automation.py 内容。排查镜像构建流程,重打 backend 镜像</li>
<li data-cat="code"><strong>🟡 死代码 / 残留</strong>console.log 在 <code>src/frontend-*/src</code> 已搜不到。看板描述脱节,v1.9 已迁移至已完成</li>
<li data-cat="code"><strong>🟢 时区隐患</strong>closing_service.py 全部 18 处 <code>datetime.now()</code> 全是 naive,无 import timezone。建议全文件改造为 <code>datetime.now(tz=timezone.utc)</code></li>
<li data-cat="code"><strong>🟢 未使用导入 / 硬编码</strong>:未发现明显问题(快速扫描未命中);源码层无硬编码(生产 env 路径已隔离)</li>
</ul>
<!-- 安全检查 -->
<h2 data-section="security">🔐 安全检查</h2>
<ul class="condensed">
<li data-cat="security"><strong>🟡 API 鉴权</strong>sensitive_words.py 13 端点已 require_admin;其余 admin API 状态需逐项复核(本次未做全量扫描)</li>
<li data-cat="security"><strong>🟢 敏感数据处理</strong>:数据库连接串、Redis 密码均在 env 注入,未硬编码到源码</li>
<li data-cat="security"><strong>🟡 依赖包</strong><code>cryptography==42.0.8</code> / <code>pydantic==2.7.4</code>2026-08-05 巡检已记录,仍待升级)</li>
<li data-cat="security"><strong>🟡 日志敏感信息</strong>:生产 logs 记录了用户 OAuth code(如 <code>code=OFVMVR7IB2dgFVWX...</code>),属企微临时凭据,建议在 <code>wecom_service.py:1411</code> 打印前 mask</li>
</ul>
<!-- 对比/关注 -->
<h2 data-section="history">🔄 与昨日对比 / PM 关注项</h2>
<table>
<thead><tr><th>条目</th><th>变化</th><th>说明</th></tr></thead>
<tbody>
<tr data-cat="history">
<td>P0-2 sensitive_words.py 13 端点无鉴权</td>
<td>♻️→✅</td>
<td>看板 v1.8 标"待治理"2026-08-06 06:00 巡检发现<strong>代码已修复</strong>router 加 <code>dependencies=[Depends(require_admin)]</code> line 47-51),v1.9 看板已迁移至已完成区</td>
</tr>
<tr data-cat="history">
<td>P1-1 前端 console.log 残留 208 行</td>
<td>♻️→✅</td>
<td>看板 v1.8 标"待清理"2026-08-06 06:00 巡检发现 <code>rg/find</code> 搜不到任何 console.log,v1.9 看板已迁移至已完成区</td>
</tr>
<tr data-cat="history">
<td>P0-4 行号描述修正</td>
<td>🔄 修正</td>
<td>看板 v1.8 写 <code>h5_ai_task.py:1198</code>,实际是 <code>employee_profile_service.py:236</code><strong>同根因 P0-5</strong>,建议合并排查</td>
</tr>
<tr data-cat="history">
<td>BLK-A/B 阻塞时长刷新</td>
<td>📅 +1d</td>
<td>自 2026-07-11 起,2026-08-06 已 <strong>26 天</strong>(较昨日 +1 自然日),远超 3 天阈值</td>
</tr>
<tr data-cat="history">
<td>/itportal/ 500 持续 29 天</td>
<td>🔥 持续</td>
<td>2026-07-08 起至今,今日 06:00 jumpserver-V2 实测仍 500。需 PM 派单部署 commit 21830d5 或清理 nginx location 块</td>
</tr>
</tbody>
</table>
<!-- PM 立即关注 -->
<h2 data-section="all">⚠️ PM 立即关注</h2>
<ul class="condensed">
<li data-cat="all">🔴 P0-NEW1constants 包打包错误)必须当日修:直接导致 H5 异步任务全部崩溃(资产推荐推送 NoneType)。修复方向:① 临时 <code>docker cp</code> 修补 → ② 长期重打 backend 镜像</li>
<li data-cat="all">🔴 /itportal 500 已持续 29 天:根因明确(容器内空 dist + nginx location 块残留)。建议执行 <code>docker compose up -d --force-recreate nginx</code></li>
<li data-cat="all">🟡 看板 v1.8.0-FROZEN 2 项描述脱节:P0-2 sensitive_words / P1-1 console.log 实际已修复 / 已清理,v1.9 已修正</li>
<li data-cat="all">⚠️ BLK-A/B 阻塞已 26 天(+23 天超过 3 天阈值):建议 PM 升级催办或重新评估优先级</li>
<li data-cat="all">🟡 closing_service.py:467 时区隐患:24h 内未触发,修复成本低(<code>from datetime import timezone</code> + 18 处替换)</li>
</ul>
<footer>
📋 报告生成时间:2026-08-06 06:00 · 看板版本 v1.9.0-DRAFT · Duckula(达寇拉)自动化生成<br>
📎 数据源:jumpserver-V2 实测 + 后端 24h 日志审计 + 源码扫描 + 容器内文件核对<br>
🔗 服务地址:<code>https://itsupport.servyou.com.cn/docs/inspection/2026-08/2026-08-06-早班.html</code>
</footer>
</div>
<script>
(function () {
// 简易分类筛选
const all = { all: true };
document.querySelectorAll('.filter-btn').forEach((btn) => {
btn.addEventListener('click', () => filter(btn.dataset.cat));
});
function filter(cat) {
document.querySelectorAll('.filter-btn').forEach((b) => b.classList.remove('active'));
document.querySelector('.filter-btn[data-cat="' + cat + '"]').classList.add('active');
document.querySelectorAll('tr[data-cat], li[data-cat]').forEach((el) => {
if (cat === 'all') { el.classList.remove('hidden'); }
else if (el.dataset.cat === cat) { el.classList.remove('hidden'); }
else { el.classList.add('hidden'); }
});
document.querySelectorAll('h2[data-section]').forEach((h) => {
if (cat === 'all' || h.dataset.section === cat) { h.classList.remove('hidden'); }
else { h.classList.add('hidden'); }
});
history.replaceState(null, '', '#' + cat);
}
const initCat = (location.hash || '#all').slice(1);
if (['all','p0','decide','doing','server','code','security','history'].includes(initCat)) {
filter(initCat);
}
const keys = ['1','2','3','4','5','6','7','8','0'];
const map = ['p0','decide','doing','server','code','security','history','overview','all'];
document.addEventListener('keydown', (e) => {
if (['INPUT','TEXTAREA'].includes(e.target.tagName)) return;
const idx = keys.indexOf(e.key);
if (idx >= 0 && map[idx]) filter(map[idx]);
});
})();
</script>
</body>
</html>