You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
31 KiB
432 lines
31 KiB
|
14 hours ago
|
<!doctype html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8" />
|
||
|
|
<title>Architecture review — crm-backend-matt(第二轮 · 2026-09-06)</title>
|
||
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
||
|
|
<script type="module">
|
||
|
|
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
||
|
|
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
|
||
|
|
</script>
|
||
|
|
<style>
|
||
|
|
.seamline { border-left: 2px dashed #64748b; }
|
||
|
|
.deepbox { background: linear-gradient(135deg, #0f172a, #1e293b); }
|
||
|
|
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body class="bg-stone-50 text-slate-900 font-sans">
|
||
|
|
<main class="max-w-6xl mx-auto px-8 py-10 space-y-10">
|
||
|
|
|
||
|
|
<!-- ================= HEADER ================= -->
|
||
|
|
<header class="space-y-3">
|
||
|
|
<div class="flex items-end justify-between flex-wrap gap-2">
|
||
|
|
<h1 class="text-3xl font-serif font-bold">Architecture review — crm-backend-matt</h1>
|
||
|
|
<div class="text-sm text-slate-500">第二轮 · 2026-09-06 · /improve-codebase-architecture</div>
|
||
|
|
</div>
|
||
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
||
|
|
<span class="bg-white border border-slate-300 rounded px-2 py-1">实心盒 = module</span>
|
||
|
|
<span class="bg-white border border-dashed border-slate-400 rounded px-2 py-1">虚线 = seam</span>
|
||
|
|
<span class="bg-red-50 border border-red-300 text-red-700 rounded px-2 py-1">红 = 泄漏 / 重复</span>
|
||
|
|
<span class="deepbox text-white rounded px-2 py-1">深色厚盒 = deep module</span>
|
||
|
|
</div>
|
||
|
|
<div class="bg-slate-100 border border-slate-200 rounded-lg px-4 py-3 text-sm text-slate-600 leading-relaxed">
|
||
|
|
<b>本轮扫描范围</b>:热点 = crm-opportunity(票 01–06 整改 / 新建字段补齐)与 crm-customer(收尾 + 上午刚落地的两项深化)。
|
||
|
|
今晨报告的 #1(workspace 口径)/ #2(H2 schema 单一事实源)<b>已落地在工作区(未提交)</b>,本轮不再列入;#4(preference 三栈)维持挂起。
|
||
|
|
词汇表:module / interface / seam / adapter / deep / shallow / locality / leverage(/codebase-design)。
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<!-- ================= CANDIDATE 1 ================= -->
|
||
|
|
<section>
|
||
|
|
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||
|
|
<div class="bg-emerald-700 text-white px-8 py-5 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div class="text-xs text-emerald-200 mb-1">候选 #1 · 热点模块 · 对称缺口</div>
|
||
|
|
<h2 class="text-2xl font-bold">商机 oplog:四处手写构造,缺一个 writer module</h2>
|
||
|
|
</div>
|
||
|
|
<div class="flex gap-2 items-center">
|
||
|
|
<span class="bg-emerald-500 text-white text-sm px-3 py-1 rounded-lg font-bold">Strong</span>
|
||
|
|
<span class="bg-white/20 text-white text-xs px-2 py-1 rounded-lg">in-process</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-6 space-y-6">
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">涉及文件</h3>
|
||
|
|
<ul class="text-xs mono space-y-1 text-slate-700">
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../intake/impl/OpportunityIntakeImpl.java · writeInitialOplog(初始 ROW_ADD)</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../service/impl/OpportunitySubServiceImpl.java · L284 / L616 writeRowLog(子表 ROW_* / FIELD_CHANGE)</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../state/impl/OpportunityTransitionImpl.java · L334(STATUS_FLOW / STAGE_FLOW)</li>
|
||
|
|
<li class="bg-red-50 border border-red-200 text-red-700 rounded px-2 py-1">crm-opportunity/.../domain/entity/OpportunityOplog.java · 实体注释自认「写入点由统一审计切面驱动(接线归后续)」</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../service/impl/OpportunityDetailServiceImpl.java · edit —— <b>0 处 oplog</b>(字段级审计未接线)</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">Before / After</h3>
|
||
|
|
<div class="grid md:grid-cols-2 gap-4">
|
||
|
|
<div class="border-2 border-red-300 rounded-xl p-4 bg-red-50/40">
|
||
|
|
<div class="text-xs font-bold text-red-700 mb-3">BEFORE · 4 个写入点各持一份构造规则</div>
|
||
|
|
<div class="space-y-2 text-xs mono">
|
||
|
|
<div class="flex items-center gap-2">
|
||
|
|
<div class="bg-white border border-red-300 rounded px-2 py-1.5 flex-1">IntakeImpl<br/><span class="text-red-600 text-[10px]">手写 new + 5 行 setter</span></div>
|
||
|
|
<div class="text-red-500">→</div>
|
||
|
|
</div>
|
||
|
|
<div class="flex items-center gap-2">
|
||
|
|
<div class="bg-white border border-red-300 rounded px-2 py-1.5 flex-1">SubServiceImpl ×2<br/><span class="text-red-600 text-[10px]">bizRef 截断 50 字 + MANUAL</span></div>
|
||
|
|
<div class="text-red-500">→</div>
|
||
|
|
</div>
|
||
|
|
<div class="flex items-center gap-2">
|
||
|
|
<div class="bg-white border border-red-300 rounded px-2 py-1.5 flex-1">TransitionImpl<br/><span class="text-red-600 text-[10px]">第三份 kind/logType 搭配知识</span></div>
|
||
|
|
<div class="text-red-500">→</div>
|
||
|
|
</div>
|
||
|
|
<div class="bg-white border-2 border-red-400 rounded px-2 py-1.5 text-center text-red-700">OpportunityOplogMapper(4 个入口直插)</div>
|
||
|
|
<div class="text-center text-red-700 font-bold text-[11px] mt-1">格式规则 = interface 之外的隐式知识 × 4 份<br/>edit 路径漏接(审计断档)</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="border-2 border-emerald-300 rounded-xl p-4 bg-emerald-50/40">
|
||
|
|
<div class="text-xs font-bold text-emerald-700 mb-3">AFTER · 一个 deep module 收编</div>
|
||
|
|
<div class="space-y-2 text-xs mono">
|
||
|
|
<div class="grid grid-cols-3 gap-1 text-center">
|
||
|
|
<div class="bg-white border border-slate-300 rounded px-1 py-1">Intake</div>
|
||
|
|
<div class="bg-white border border-slate-300 rounded px-1 py-1">Sub ×N</div>
|
||
|
|
<div class="bg-white border border-slate-300 rounded px-1 py-1">Transition</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-slate-400">↓ 一行调用 record(...)</div>
|
||
|
|
<div class="deepbox text-white rounded-lg px-3 py-3">
|
||
|
|
<div class="text-[10px] uppercase tracking-wider text-slate-300">deep module</div>
|
||
|
|
<div class="font-bold">OpportunityOplogWriter</div>
|
||
|
|
<div class="text-[10px] text-slate-300 mt-1">小 interface:rowAdd / rowDelete / fieldChange / flow(cmd)</div>
|
||
|
|
<div class="text-[10px] text-slate-400 mt-1 border-t border-slate-600 pt-1">实现吸收:构造、kind/logType 搭配、bizRef 截断、opTime/opUser/opSource、mapper</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-emerald-700 font-bold text-[11px]">edit 字段级 diff 接线 = 加一个调用点,不再加一份构造知识</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="grid md:grid-cols-2 gap-6 text-sm">
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">问题(deletion test:通过)</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">商机审计日志的构造与格式规则散在 4 个写入点。lead 有 <span class="mono text-xs">LeadHistoryRecorder</span>、customer 有 <span class="mono text-xs">CustomerOplogWriter</span>(票 04 从 writeOplog 抽公共)——三域同款概念,唯独商机缺 writer module。假想删掉它,构造规则会散回 4 处 → 复杂度会集中,说明这个 seam 值得存在。且主表 edit 完全不写 oplog:字段级审计是实体注释里登记过的欠账,每多一个写入点,欠账的接线成本就涨一截。</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">方案(plain English)</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">抽 <span class="mono text-xs">OpportunityOplogWriter</span>:小 interface(按记录形态 3–4 个方法),实现吸收全部构造/格式规则与 mapper 细节;4 个写入点变薄调用。edit 路径的字段级 diff 是否本期接线是产品拍板,另立票——writer 先立 seam,接线只是加调用点。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">收益</h3>
|
||
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">locality:格式规则一处改,四处生效</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">leverage:1 个 interface,4+ 个调用点</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">interface 即测试面:格式规则一次单测</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">调用点测试改 verify(writer),不再 captor 全字段</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">三域对称齐平(lead / customer 已验证路径)</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<!-- ================= CANDIDATE 2 ================= -->
|
||
|
|
<section>
|
||
|
|
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||
|
|
<div class="bg-emerald-700 text-white px-8 py-5 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div class="text-xs text-emerald-200 mb-1">候选 #2 · 平台契约漂移</div>
|
||
|
|
<h2 class="text-2xl font-bold">自定义视图操作符:一份三方契约,两个模块各写一套,已漂移</h2>
|
||
|
|
</div>
|
||
|
|
<div class="flex gap-2 items-center">
|
||
|
|
<span class="bg-emerald-500 text-white text-sm px-3 py-1 rounded-lg font-bold">Strong</span>
|
||
|
|
<span class="bg-white/20 text-white text-xs px-2 py-1 rounded-lg">seam + adapter</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-6 space-y-6">
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">涉及文件</h3>
|
||
|
|
<ul class="text-xs mono space-y-1 text-slate-700">
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../enums/SavedViewOperator.java(6 操作符)+ query/impl/OpportunitySavedViewFilter.java</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-customer/.../enums/CustomerSavedViewOperator.java(8 操作符,多 in / notIn)+ query/CustomerSavedViewFilter.java</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-preference/.../dto/SavedViewCondition.java(平台只存不校验,两模块均已依赖)</li>
|
||
|
|
<li class="bg-red-50 border border-red-200 text-red-700 rounded px-2 py-1">.scratch 追踪项:「自定义检索推广到其他菜单」→ 线索 = 第 3 个消费方,在路上</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">Before / After</h3>
|
||
|
|
<div class="grid md:grid-cols-2 gap-4">
|
||
|
|
<div class="border-2 border-red-300 rounded-xl p-4 bg-red-50/40">
|
||
|
|
<div class="text-xs font-bold text-red-700 mb-2">BEFORE · 词汇表两处定义,已经分叉</div>
|
||
|
|
<pre class="mermaid text-xs">
|
||
|
|
flowchart TD
|
||
|
|
FE["前端检索面板(operator code 的渲染方)"] --> PREF["crm-preference · filter_json 存储(不校验)"]
|
||
|
|
PREF --> OPF["OpportunitySavedViewFilter<br/>SavedViewOperator ×6"]
|
||
|
|
PREF --> CUF["CustomerSavedViewFilter<br/>CustomerSavedViewOperator ×8(多 in/notIn)"]
|
||
|
|
PREF -.->|"spin-out 在案:第 3 个消费方(线索)"| LEAD["?(将要复制哪一套?)"]
|
||
|
|
OPF --> W1["wrapper SQL 片段"]
|
||
|
|
CUF --> W2["结构化条件 → XML SQL"]
|
||
|
|
</pre>
|
||
|
|
<div class="text-center text-red-700 font-bold text-[11px] mt-1">同一契约 6 vs 8 操作符 —— 漂移已发生,且各持一份注入防护</div>
|
||
|
|
</div>
|
||
|
|
<div class="border-2 border-emerald-300 rounded-xl p-4 bg-emerald-50/40">
|
||
|
|
<div class="text-xs font-bold text-emerald-700 mb-2">AFTER · 机制下沉,字段池与输出 adapter 留在业务方</div>
|
||
|
|
<pre class="mermaid text-xs">
|
||
|
|
flowchart TD
|
||
|
|
POOL1["商机字段池 Map(业务知识)"] --> KIT
|
||
|
|
POOL2["客户字段池 Map(业务知识)"] --> KIT
|
||
|
|
POOL3["线索字段池 Map(未来)"] --> KIT
|
||
|
|
KIT["crm-preference · SavedViewOperatorKit(deep module)<br/>操作符词汇表 + 语义翻译 + 白名单注入防护 + 未知跳过"]
|
||
|
|
KIT --> AD1["adapter:wrapper 片段(商机)"]
|
||
|
|
KIT --> AD2["adapter:结构化条件(客户)"]
|
||
|
|
</pre>
|
||
|
|
<div class="text-center text-emerald-700 font-bold text-[11px] mt-1">第 3 个消费方 = 供一份字段池 + 一个输出 adapter</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="grid md:grid-cols-2 gap-6 text-sm">
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">问题</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">operator 词汇表(eq/ne/contains/notContains/isEmpty/isNotEmpty/in/notIn)是三方契约:前端渲染、crm-preference 存储、业务模块翻译成 SQL。现在它由两个业务模块各自定义——客户侧已比商机侧多出 in/notIn,<b>漂移是事实不是假设</b>;注入防护(列名白名单 + 值参数化)也各持一份。两个真实实现 + 一个在途 = seam 已坐实。</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">方案(plain English)</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">操作符机制下沉为 crm-preference 内的 deep module(条件模型的家,业务模块已依赖它):词汇表、语义翻译、valueless 处理、白名单注入防护、未知跳过全部收进实现;业务方只保留字段池 Map(列名知识)与各自的输出 adapter(wrapper 片段 vs 结构化条件——输出形态差异真实存在,正是 adapter 该待的位置)。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="rounded-lg bg-amber-50 border-l-4 border-amber-500 px-4 py-3 text-sm text-amber-800">
|
||
|
|
<b>拍板提示(非冲突,需确认)</b>:crm-preference CONTEXT 有「本次只做到够用的最小通用深度」的拍板,SavedViewService javadoc 亦载明「字段池合法性归业务方」。本候选<b>不动三栈 CRUD、不移动字段池归属</b>,只收操作符机制;上轮 #4 的重开条件(第 4 种偏好)未触发,但「同一偏好的第 3 个消费方已排队」是另一回事——建议在 grilling 里先拍这一条。
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">收益</h3>
|
||
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">词汇表单一定义,漂移止血</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">注入防护一处加固,全平台生效</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">leverage:第 3 消费方边际成本 ≈ 一份字段池</span>
|
||
|
|
<span class="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2 py-1 rounded">两套操作符单测合并为一个测试面</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<!-- ================= CANDIDATE 3 ================= -->
|
||
|
|
<section>
|
||
|
|
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||
|
|
<div class="bg-amber-600 text-white px-8 py-5 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div class="text-xs text-amber-100 mb-1">候选 #3 · 两实现 = 真 seam,但收敛面要克制</div>
|
||
|
|
<h2 class="text-2xl font-bold">待发通知:两套同构 pending-notice 栈,口径靠注释对齐</h2>
|
||
|
|
</div>
|
||
|
|
<div class="flex gap-2 items-center">
|
||
|
|
<span class="bg-amber-500 text-white text-sm px-3 py-1 rounded-lg font-bold">Worth exploring</span>
|
||
|
|
<span class="bg-white/20 text-white text-xs px-2 py-1 rounded-lg">in-process</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-6 space-y-6">
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">涉及文件</h3>
|
||
|
|
<ul class="text-xs mono space-y-1 text-slate-700">
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../job/OpportunityMaintenanceJob.java · writePendingNotices / buildNotice / buildPayload / invalidatePendingReminds / isOn(≈100 行)</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-customer/.../job/CustomerReminderJob.java · writeNextDueNotice / buildNotice / buildPayload / invalidateStaleAnchorNotices / matchesCurrentAnchor / isOn(≈120 行)</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">两张同构表 opportunity_pending_notice / customer_pending_notice + 各自 Mapper</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">Before / After(横剖:同一条领域规则带,两处各织一遍)</h3>
|
||
|
|
<div class="grid md:grid-cols-2 gap-4">
|
||
|
|
<div class="border-2 border-red-300 rounded-xl p-4 bg-red-50/40">
|
||
|
|
<div class="text-xs font-bold text-red-700 mb-3">BEFORE</div>
|
||
|
|
<div class="space-y-3 text-xs mono">
|
||
|
|
<div>
|
||
|
|
<div class="font-bold text-slate-700 mb-1">OpportunityMaintenanceJob</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-0.5 bg-white rounded mb-0.5">回收推导(域内,该留)</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-0.5 bg-white rounded mb-0.5">幂等占位(biz,plan)任意状态永不重写</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-0.5 bg-white rounded mb-0.5">pending 批量置失效</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-0.5 bg-white rounded mb-0.5">payload Jackson 失败告警存 null · isOn</div>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<div class="font-bold text-slate-700 mb-1">CustomerReminderJob(注释:「对称商机 writePendingNotices 幂等口径」「对齐商机侧」)</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-0.5 bg-white rounded mb-0.5">提醒链推导(域内,该留)</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-0.5 bg-white rounded mb-0.5">幂等占位 + anchor 保守比对</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-0.5 bg-white rounded mb-0.5">pending 置失效</div>
|
||
|
|
<div class="border-l-4 border-red-400 pl-2 py-0.5 bg-white rounded mb-0.5">payload 失败存 null · isOn</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="border-2 border-emerald-300 rounded-xl p-4 bg-emerald-50/40">
|
||
|
|
<div class="text-xs font-bold text-emerald-700 mb-3">AFTER</div>
|
||
|
|
<div class="space-y-3 text-xs mono">
|
||
|
|
<div class="grid grid-cols-2 gap-2">
|
||
|
|
<div class="bg-white border border-slate-300 rounded p-2">商机 Job<br/><span class="text-[10px] text-slate-500">只剩推导 + 薄 adapter</span></div>
|
||
|
|
<div class="bg-white border border-slate-300 rounded p-2">客户 Job<br/><span class="text-[10px] text-slate-500">只剩链推导 + 薄 adapter</span></div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-slate-400">↓ 小 interface</div>
|
||
|
|
<div class="deepbox text-white rounded-lg px-3 py-3">
|
||
|
|
<div class="text-[10px] uppercase tracking-wider text-slate-300">deep module(crm-base)</div>
|
||
|
|
<div class="font-bold">PendingNoticeWriter</div>
|
||
|
|
<div class="text-[10px] text-slate-300 mt-1">writeIfAbsent(biz, type, plan, payload) / invalidatePending(ids, type)</div>
|
||
|
|
<div class="text-[10px] text-slate-400 mt-1 border-t border-slate-600 pt-1">实现吸收:占位幂等 / 失效语义 / payload 序列化告警兜底</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-emerald-700 font-bold text-[11px]">「任意状态占位永不重写」从注释约定变成代码</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="grid md:grid-cols-2 gap-6 text-sm">
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">问题</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">同一套持久化语义(幂等占位、失效、payload 兜底)在两个 Job 里各写一遍,一致性靠注释互相点名维持——两个真实实现已经证明 seam 存在,而第三个域(如线索提醒)再长出来时是第三份手写。</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">方案(plain English)与保留意见</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">crm-base 抽 <span class="mono text-xs">PendingNoticeWriter</span> deep module,两个 Job 各持薄 adapter(自家 mapper + notice 类型码)。<b>收敛面必须克制</b>:只收持久化语义,不收推导(链式 vs 回收推导是真差异,留域内)。表结构不同 → adapter 承载差异,机制只有一份。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">收益</h3>
|
||
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">口径从注释变代码</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">locality:幂等规则一处修</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">第 3 域边际成本降</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">两套口径守恒断言合一个测试面</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<!-- ================= CANDIDATE 4 ================= -->
|
||
|
|
<section>
|
||
|
|
<div class="bg-white rounded-2xl shadow-lg overflow-hidden">
|
||
|
|
<div class="bg-amber-600 text-white px-8 py-5 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div class="text-xs text-amber-100 mb-1">候选 #4 · 诚实标注:拆分换 locality,不是深化</div>
|
||
|
|
<h2 class="text-2xl font-bold">IOpportunitySubService:21 方法宽 interface,七个 Tab 子域一锅</h2>
|
||
|
|
</div>
|
||
|
|
<div class="flex gap-2 items-center">
|
||
|
|
<span class="bg-amber-500 text-white text-sm px-3 py-1 rounded-lg font-bold">Worth exploring</span>
|
||
|
|
<span class="bg-white/20 text-white text-xs px-2 py-1 rounded-lg">in-process</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-6 space-y-6">
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">涉及文件</h3>
|
||
|
|
<ul class="text-xs mono space-y-1 text-slate-700">
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../service/IOpportunitySubService.java · 21 个方法 / 7 个子域</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../service/impl/OpportunitySubServiceImpl.java · 599 行</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/src/test/.../OpportunitySubServiceImplTest.java · 613 行</li>
|
||
|
|
<li class="bg-slate-50 rounded px-2 py-1">crm-opportunity/.../controller/OpportunitySubController.java · 全部 Tab 端点</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">Before / After(质量图:interface 面积 vs implementation 面积)</h3>
|
||
|
|
<div class="grid md:grid-cols-2 gap-4">
|
||
|
|
<div class="border-2 border-red-300 rounded-xl p-4 bg-red-50/40">
|
||
|
|
<div class="text-xs font-bold text-red-700 mb-3">BEFORE · interface 几乎与 implementation 一样宽(shallow 极端形态)</div>
|
||
|
|
<div class="flex items-end gap-4 h-56 px-4">
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1">
|
||
|
|
<div class="w-full bg-red-400 border-2 border-red-500 rounded-t" style="height:150px"></div>
|
||
|
|
<div class="text-[10px] text-red-700 font-bold">interface · 21 方法</div>
|
||
|
|
</div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1">
|
||
|
|
<div class="w-full bg-slate-700 border-2 border-slate-800 rounded-t" style="height:180px"></div>
|
||
|
|
<div class="text-[10px] text-slate-700 font-bold">impl · 599 行 / 测试 613 行</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-red-700 font-bold text-[11px] mt-2">改「工作计划」要在客户关联 / 团队 / 跟进的代码与测试里穿行</div>
|
||
|
|
</div>
|
||
|
|
<div class="border-2 border-emerald-300 rounded-xl p-4 bg-emerald-50/40">
|
||
|
|
<div class="text-xs font-bold text-emerald-700 mb-3">AFTER · 按子域拆:每个小 interface 背一小块内聚实现</div>
|
||
|
|
<div class="flex items-end gap-2 h-56 px-2">
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:40px"></div><div class="text-[9px] text-slate-600">客户关联</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:30px"></div><div class="text-[9px] text-slate-600">跟进</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:25px"></div><div class="text-[9px] text-slate-600">勘察</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:25px"></div><div class="text-[9px] text-slate-600">附件</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:45px"></div><div class="text-[9px] text-slate-600">团队</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:35px"></div><div class="text-[9px] text-slate-600">工作计划</div></div>
|
||
|
|
<div class="flex flex-col items-center gap-1 flex-1"><div class="w-full bg-emerald-500 rounded-t" style="height:15px"></div><div class="text-[9px] text-slate-600">日志读</div></div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center text-emerald-700 font-bold text-[11px] mt-2">每片仍是小实现 —— 赢的是 locality,不是 depth</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="grid md:grid-cols-2 gap-6 text-sm">
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">问题(deletion test:不过,所以它不是 deep module)</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">删掉这个聚合 interface,21 个签名只会搬家不会消失——它是 grab-bag,不是 deep module。真正的摩擦是 navigability:商机是当前最热的整改区(票 01–06 / 新建字段),每次动一个 Tab 都要面对 599 行 impl、613 行全子域 mock 的测试文件;合并冲突面也是全模块共享的。</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2">方案(plain English)</h3>
|
||
|
|
<p class="text-slate-700 leading-relaxed">按 Tab 子域拆 service(每域 2–4 方法的小 interface + 自家 impl),测试按域分文件;controller 端点不动(或随后按 Tab 拆)。可先拆最活跃的两个域(客户关联 / 工作计划)验证收益再推广。<b>注意与候选 #1 的顺序</b>:先立 OplogWriter seam,拆分时各域薄调用它,避免拆分过程再复制一份构造知识。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<h3 class="font-bold mb-2 text-sm">收益</h3>
|
||
|
|
<div class="flex flex-wrap gap-2 text-xs">
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">locality:子域改动封闭</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">测试按域分文件,mock 面缩到域内</span>
|
||
|
|
<span class="bg-amber-50 text-amber-700 border border-amber-200 px-2 py-1 rounded">热点模块合并冲突面缩小</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<!-- ================= CANDIDATE 5 (上轮遗留引用) ================= -->
|
||
|
|
<section>
|
||
|
|
<div class="bg-white rounded-2xl shadow border border-slate-200 overflow-hidden">
|
||
|
|
<div class="bg-slate-500 text-white px-8 py-4 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<div class="text-xs text-slate-300 mb-1">候选 #5 · 上轮遗留(今晨 #3,未动,仍然成立)</div>
|
||
|
|
<h2 class="text-xl font-bold">crm-rule 六个权限种子 Initializer 合并为声明式单 runner</h2>
|
||
|
|
</div>
|
||
|
|
<span class="bg-slate-400 text-white text-sm px-3 py-1 rounded-lg font-bold">Worth exploring</span>
|
||
|
|
</div>
|
||
|
|
<div class="px-8 py-5 text-sm text-slate-700 leading-relaxed space-y-2">
|
||
|
|
<p>6 个 <span class="mono text-xs">*PermissionInitializer</span>(含一组 @Order 撞号)各 ~45 行仪式调 <span class="mono text-xs">PermissionSeeder.seedModule</span>(ADR-0016 已立的 seam)。合并为一个 Initializer + List<descriptor>,顺序即列表顺序;第 7 个规则菜单边际成本从 45 行降到 1 行。保留意见不变:javadoc 里的「为什么挂这个目录」决策注释须随数据走。</p>
|
||
|
|
<p class="text-xs text-slate-500">今晨 #4(preference 三栈)维持挂起、重开条件(第 4 种偏好)未触发,本轮不重列。</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<!-- ================= TOP RECOMMENDATION ================= -->
|
||
|
|
<section>
|
||
|
|
<div class="bg-gradient-to-r from-emerald-700 to-emerald-600 text-white rounded-2xl shadow-lg px-8 py-8">
|
||
|
|
<div class="text-xs text-emerald-200 mb-2">TOP RECOMMENDATION</div>
|
||
|
|
<h2 class="text-2xl font-bold mb-3">先做 #1:商机 OpportunityOplogWriter</h2>
|
||
|
|
<p class="leading-relaxed text-emerald-50 max-w-4xl">
|
||
|
|
理由:(a) 深化路径已在两个兄弟模块各验证一次(<span class="mono text-xs">LeadHistoryRecorder</span> / <span class="mono text-xs">CustomerOplogWriter</span> 各 ~55 行,抽完后调用点全部变薄)——这是照抄已赢的棋,不是新设计;(b) 改动面窄、<b>零产品决策</b>(4 处收编即可收工,edit 字段级审计接线另立产品票,立了 seam 之后它只是一个新调用点);(c) 商机是当前最热的整改区,审计写入点还会继续增多——seam 每早立一天,欠账的接线成本就少涨一截;(d) 测试面立竿见影:格式规则一次单测,调用点从 captor 全字段断言换成 verify(writer)。
|
||
|
|
随后顺序:#2(先拍「操作符机制归属」这一条,再做)、#3(设计收敛面后做)、#4 在 #1 之后拆(避免拆分复制构造知识)、#5 随时小票。
|
||
|
|
</p>
|
||
|
|
<div class="mt-4 text-xs text-emerald-200">建议顺序:#1 → #2(需拍板)→ #3 → #4(在 #1 后)· #5 独立小票</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<footer class="bg-slate-900 text-slate-400 text-xs">
|
||
|
|
<div class="max-w-6xl mx-auto px-8 py-6">
|
||
|
|
生成于 2026-09-06 · /improve-codebase-architecture(第二轮)· 词汇表:/codebase-design · 排除项:今晨已落地未提交的两项深化
|
||
|
|
</div>
|
||
|
|
</footer>
|
||
|
|
|
||
|
|
</main>
|
||
|
|
</body>
|
||
|
|
</html>
|