🌙

确认了项目结构:

  • routes/(HTTP 层:Zod 校验 + 审计)

  • services/(业务逻辑层)

  • lib/(工具/实现层)

  • schemas/(Zod 校验模式)

  • middleware/(认证授权、错误处理)

  • ssh/(SSH/SFTP)

  • repositories/(应该有 serverRepository,刚才列表被截断,但技能提到 repositories/serverRepository.ts)

结构符合"分层架构"雏形。现在写详细解释。用户偏好:通俗、表格、可直接照做的示例。

我来组织:

一、SOLID 五原则

每个:一句话直觉 + 通俗比喻 + web-ops 例子(有实际代码就引用,没有就构造小例子)

S - 单一职责(Single Responsibility) O - 开闭原则(Open/Closed) L - 里氏替换(Liskov Substitution) I - 接口隔离(Interface Segregation) D - 依赖倒置(Dependency Inversion)

二、整洁架构(Clean Architecture)

  • 核心思想:依赖方向朝内(业务核心不依赖框架)

  • 分层:实体/用例/接口适配器/框架

  • 与 web-ops 对照

  • 关键规则:依赖规则(内层不 import 外层)

三、对 web-ops 的实际意义 + 建议

我尽量用 web-ops 的真实代码来举例,比如:

  • S:routes/ 只做 HTTP 编排,services/ 做业务——serverService.ts 的 updateServer 就是业务,routes/servers.ts 的 PUT 就是 HTTP 层

  • O:inspectionService 的 TOOL_DEFS 注册新工具不改调用方——这就是开闭原则的体现!(技能里写了"注册即生效:runAllInspection 用 Object.keys(TOOL_DEFS) 遍历——新工具自动进入全部巡检")这是绝佳的例子!

  • D:lib/alert-rule.ts 的 getAlertRuleConfig 用 prisma 直接读写——依赖具体实现;如果抽象成接口,就是依赖倒置

也要指出 web-ops 现在做得好的和可以改进的(诚实、建设性)。

篇幅控制:用户要"详细解释",所以可以长一点,但要用表格和代码块组织,避免大段文字。用户偏好"干净有条理"。

我直接写,不需要更多工具调用。

好问题——我结合 web-ops项目现有代码来讲,这样不抽象。先快速确认一下项目分层结构,保证举例准确:

确认了项目结构:-routes/(HTTP层:Zod校验+审计)-services/(业务逻辑层)-lib/(工具/实现层)-schemas/(Zod校验模式)-middleware/(认证授权、错误处理)-ssh/(SSH/SFTP)-repositories/(应该有serverRepository,刚才列表被截断,但技能提到repositories/serverRepository.ts)结构符合"分层架构"雏形。现在写详细解释。用户偏好:通俗、表格、可直接照做的示例。我来组织:##一、SOLID五原则每个:一句话直觉+通俗比喻+web-ops例子(有实际代码就引用,没有就构造小例子)S-单一职责(SingleResponsibility)O-开闭原则(Open/Closed)L-里氏替换(LiskovSubstitution)I-接口隔离(InterfaceSegregation)D-依赖倒置(DependencyInversion)##二、整洁架构(CleanArchitecture)-核心思想:依赖方向朝内(业务核心不依赖框架)-分层:实体/用例/接口适配器/框架-与web-ops对照-关键规则:依赖规则(内层不import外层)##三、对web-ops的实际意义+建议我尽量用web-ops的真实代码来举例,比如:-S:routes/只做HTTP编排,services/做业务——serverService.ts的updateServer就是业务,routes/servers.ts的PUT就是HTTP层-O:inspectionService的TOOL_DEFS注册新工具不改调用方——这就是开闭原则的体现!(技能里写了"注册即生效:runAllInspection用Object.keys(TOOL_DEFS)遍历——新工具自动进入全部巡检")这是绝佳的例子!-D:lib/alert-rule.ts的getAlertRuleConfig用prisma直接读写——依赖具体实现;如果抽象成接口,就是依赖倒置也要指出web-ops现在做得好的和可以改进的(诚实、建设性)。篇幅控制:用户要"详细解释",所以可以长一点,但要用表格和代码块组织,避免大段文字。用户偏好"干净有条理"。我直接写,不需要更多工具调用。

项目结构确认了。下面用 web-ops 的真实代码来讲 SOLID 和整洁架构——这样你既能理解概念,又能看到自己项目里哪些地方已经在做、哪些可以改进。


一、SOLID 五原则

SOLID 是面向对象设计的五个原则的首字母缩写,核心目的就一句话:让代码好改、好加、好维护,改一处不炸十处

字母原则一句话直觉
S 单一职责 一个类/函数只干一件事
O 开闭原则 加功能不改旧代码(对扩展开放,对修改关闭)
L 里氏替换 子类能顶替父类,不搞特殊
I 接口隔离 接口别又大又全,按需拆分
D 依赖倒置 依赖抽象,不依赖具体实现

S —— 单一职责(Single Responsibility)

直觉:一个类/函数只有一个"为什么而变"的理由。

通俗比喻:厨师只负责炒菜,服务员只负责上菜,收银员只负责收钱。如果让厨师同时管收银,客人一多,菜也糊了、钱也算错。

web-ops 现状:你项目已经做对了。看分层:

routes/servers.ts      → 只做 HTTP 编排(收请求、Zod 校验、调 service、回响应)
services/serverService → 只做业务逻辑(查库、加密、审计)
lib/alert-rule.ts     → 只做告警规则这一个业务
ssh/sftp-manager.ts   → 只做 SFTP

反面教材(要避免的)是:一个函数里既拼 SQL、又发邮件、又写日志、又算业务——改任何一处都得动它。

示例——项目里好的做法:

// routes/servers.ts —— 职责:HTTP 层,不做业务
serverRouter.put('/servers/:id', requireRole('OPS_ADMIN'), validate(UpdateServerSchema),
 async (req, res, next) => {
   const s = await serverService.updateServer(...);  // 业务在 service
   res.json({ code: 0, data: s, message: '服务器更新成功' });
});

// services/serverService.ts —— 职责:业务层,不知道 HTTP 是什么
async updateServer(id, input, userId, ip) {
 ...
 data.encryptedSshPassword = input.sshPassword ? encryptPII(input.sshPassword) : undefined;
 const updated = await serverRepository.updateServer(id, data);
 audit({ userId, action: AuditAction.SERVER_UPDATED, ... });
 return safe;
}

⚠️ 单一职责的"度":不是函数越短越好,而是"变化的原因唯一"。比如 serverService.updateServer 会因"业务规则变化"而变,routes 的 PUT 会因"API 契约变化"而变——它们分开了,改 API 不动业务,改业务不动 API。


O —— 开闭原则(Open/Closed)

直觉:加新功能时,新增代码,而不是修改旧代码

通俗比喻:插座。加一个新电器(扩展),不用拆墙改插座(修改),插上就行。

web-ops 现状:你的巡检工具模块就是教科书级例子!看技能里记的:

// inspectionService.ts —— 加新巡检工具只需:
// 1. 写 XXX_COMMAND + parseXxx()
// 2. 注册进 TOOL_DEFS(白名单)+ TOOL_NAMES(中文名)
// 3. 前端 tools.tsx 加 UI 入口

// runAllInspection 用 Object.keys(TOOL_DEFS) 遍历 —— 新工具自动进入全部巡检

加一个"检查防火墙状态"工具,完全不用改 runAllInspection 的代码——这就是对扩展开放、对修改关闭。改法如果是"在 runAllInspection 里加一行 if 判断",那就是违反 O 的坏味道,每加一个工具都要动核心函数,迟早改出 bug。

反面例子(很多项目这样写,要避免):

// ❌ 坏味道:加新通知渠道要改这个函数
function sendNotify(type) {
 if (type === 'wecom') { ... }
 else if (type === 'dingtalk') { ... }
 else if (type === 'email') { ... }   // 每加一种就要来这里加 else if
}

你项目的 lib/notify-webhook.tsdetectType + buildBody 按类型分发,如果渠道种类继续变多,将来可以考虑注册表模式(Map<type, builder>),彻底实现 O。


L —— 里氏替换(Liskov Substitution)

直觉:子类/替代实现,必须能无痛替换父类/原实现,调用方不用改任何代码。

通俗比喻:公司说"派一辆车来接你"。来的可以是奔驰、五菱宏光、还是货车——只要它能开、能载你到目的地,你就满意。如果来的是"坦克",把路压坏了(子类行为超出父类契约),这就是违反 L。

web-ops 现状:你的 Redis 降级就是 L 的体现

// lib/redis.ts —— 没配 Redis 时直接返回 MemoryRedis
// 调用方根本不知道(也不关心)用的是真 Redis 还是内存版
function getRedis() {
 if (!process.env.REDIS_URL) return new MemoryRedis();  // 替代实现
 return realRedis;
}

调用 blacklistToken() 的地方,不管底层是 Redis 还是内存,行为一致——替换无痛。这就是 L。

违反 L 的例子:某处代码假设"一定返回 null 表示无数据",但内存版返回 undefined——替换实现破坏了调用方的假设。替换实现必须遵守原契约(入参范围、返回值类型、抛错行为)。


I —— 接口隔离(Interface Segregation)

直觉:别让调用方依赖它用不到的接口。

通俗比喻:一个"全能遥控器"有 50 个按钮,你只需要开关机。接口太胖,人人被迫依赖一堆没用到的按钮。

web-ops 现状:你的 checkServerReadAccessrequireServerAccess 分离就是 I 的思路——读取权限和写权限分开用,路由按需挂载,而不是一个"全权限检查函数"处处调用。

另一个例子:UpdateServerSchema 只暴露该更新的字段(name/ip/sshPort/sshUser/sshPassword/os/groupId),而不是把整个数据库字段全塞给前端——接口最小化

要避免的:一个 UserService 同时提供 createUser / deleteUser / changePassword / sendEmail / exportReport / calcSalary……所有功能都塞进一个类,任何调用方都依赖整坨。


D —— 依赖倒置(Dependency Inversion)

直觉高层策略不依赖低层细节;两者都依赖抽象。通俗说:面向接口编程,不面向具体实现编程

通俗比喻:手机充电。你的手机(高层)不关心电来自插座还是充电宝(低层),它只依赖"USB-C 接口"这个抽象。没有这个抽象,手机就得针对"插座充电、充电宝充电、车载充电"各写一套。

web-ops 现状:部分做到了,部分没做到:

做到的地方routes 依赖 serverService(业务抽象),不直接依赖 prisma。路由层换数据库实现时不用改。

可改进的地方:看 lib/session-config.ts

// 现在:直接依赖具体实现 prisma
import { prisma } from './prisma';
export async function getSessionConfig() {
 const row = await prisma.sessionConfig.findUnique({ where: { id: 1 } });
 ...
}

如果将来想换成别的存储(比如内存/配置文件),所有调用 getSessionConfig 的地方都要改——因为依赖的是具体实现。理想形态是定义接口:

// 抽象接口(高层依赖它)
export interface SessionConfigStore {
 get(): Promise<SessionConfigData>;
 save(data: SessionConfigData): Promise<SessionConfigData>;
}

// 具体实现(实现接口)
export const prismaSessionConfigStore: SessionConfigStore = {
 get: () => prisma.sessionConfig.findUnique({ where: { id: 1 } }),
 ...
};

⚠️ 但注意:中小项目全搞接口抽象会过度设计("接口爆炸")。务实做法:只有一个实现的先不抽象,等出现第二个实现(或测试需要 mock)再抽——这就是"敏捷的 D"。


二、整洁架构(Clean Architecture)

核心思想

整洁架构(Robert C. Martin 提出)就一条铁律:

依赖只能从外向内。内层(业务核心)永远不知道外层(框架/数据库/HTTP)的存在。

经典洋葱图,从内到外:

┌─────────────────────────────────────┐
│ Entity(实体/业务核心)             │ ← 最内层:纯业务规则,不依赖任何框架
│ Use Cases(用例/应用服务)           │ ← 业务操作:登录、创建服务器、巡检
│ Interface Adapters(接口适配器)     │ ← 把外部世界翻译成业务语言
│ Frameworks & Drivers(框架驱动层)   │ ← Express、Prisma、MySQL、SSH
└─────────────────────────────────────┘

依赖规则:箭头永远朝内。外层可以依赖内层,内层绝不允许依赖外层

用一句话记:"数据库不知道业务,业务不知道 Express;Express 知道业务,业务不知道数据库。"


和 web-ops 的对照

你的项目已经有接近整洁架构的分层了:

整洁架构层web-ops 对应举例
Frameworks(框架) routes/ + app.ts routes/servers.ts 处理 HTTP
Interface Adapters(适配) schemas/ + middleware/ Zod 校验、authenticate、error-handler
Use Cases(业务用例) services/ serverService.updateServer
Entities(业务实体) lib/ 里的领域逻辑 + Prisma model alert-rule.tscustom-task-scheduler.ts

再精确一点,你的依赖方向:routes → services → lib/repositories → prisma,箭头是单向朝内的——这符合整洁架构精神。

client (React) 
  ↓ HTTP /api/v1
routes (Express 路由,只做编排)
  ↓
services (业务逻辑)
  ↓
lib / repositories (领域实现、数据访问)
  ↓
Prisma / MySQL / SSH2 / Redis

整洁架构三个关键收益(在 web-ops 的具体体现)

收益 1:改框架不动业务

expressfastify,只改 routes/ 层,services/ 一行不动。

收益 2:业务可测试

lib/alert-rule.tscheckMetricsAlerts 是纯业务逻辑,可以脱离 HTTP/数据库直接测——你们验证脚本 npx tsx 直测 services/lib 就是这个思路(技能里写的"不卡在登录,写临时脚本直测服务层"就是整洁架构带来的测试红利)。

收益 3:关注点隔离

middleware/error-handler.ts 负责"把错误翻译成 HTTP 状态码"——业务层只管抛 NotFoundError('服务器不存在')不用知道 HTTP 是什么。这正是"接口适配器"的职责。


三、对 web-ops 的落地建议(务实版)

⚠️ 诚实提醒:完全照搬整洁架构会过度设计。web-ops 是单库中小型平台,关键是把 SOLID 的"度"把握好。

建议理由工作量
保持现有分层,别打散 routes/services/lib 已符合单向依赖,不要为了"更整洁"拆出一堆 abstract class 0
新功能默认按现有模式落地 新模块 = routes(校验+审计)+ services(业务)+ lib(实现),照抄现有模块 0(照惯例)
services 层别直接 import prisma 散落 现在有些 service 直接 prisma.xxx,将来统一走 repository 层(如 serverRepository)会更干净——但非紧急
开闭原则重点用在"多分支分发" 通知渠道、巡检工具、文件类型处理这类会不断加种类的地方,用注册表/策略模式(Map<type, handler>)而不是 else if 小~中
别把审计/日志/权限逻辑写进业务函数 审计应像现在一样在 routes 层或 service 尾部统一调,不要在 createServer 中间散落 0(保持现状)

四、一句话总结

概念一句话
SOLID 五个设计原则,目标 = 代码好加功能、好改、好测
整洁架构 一个分层规则:业务核心在中间,依赖只朝内
两者关系 SOLID 是"怎么把类/函数写好",整洁架构是"怎么把系统分层"——SOLID 是砖,整洁架构是图纸
web-ops 现状 分层骨架已符合,SOLID 里 S/O 做得最好(routes/services 分离 + 巡检工具注册表),D 有提升空间(services 直接依赖 prisma)

 

posted on 2026-08-05 19:41  星火撩原  阅读(6)  评论(0)    收藏  举报

本站已运行:0
🌙 夜间模式
🌙
🌙