前端分层架构实战:DDD 与 Clean Architecture 在大型业务系统中的落地路径与项目实践

引言

在某电商后台管理系统的迭代中,我们曾陷入典型的前端业务膨胀困境:修改 “订单拦截规则” 的状态校验逻辑时,需要同时调整 5 个关联组件的代码 —— 业务逻辑散落在组件的 setupmethods 中,耦合严重;后续扩展至小程序端时,核心业务逻辑无法复用,需重新编写 60% 的代码;新成员接手时,需花 1 周才能理清 “拦截规则从查询到展示” 的全链路逻辑。
这些问题的核心是 “业务逻辑与技术实现的耦合”领域驱动设计(DDD)与整洁架构(Clean Architecture) 为解决这些问题提供了思路 —— 通过分层解耦,将 “稳定的业务规则” 与 “多变的技术工具(框架、UI 组件)” 分离,让前端系统具备长期可维护性与可扩展性
本文结合实际项目实践,详解这两种架构在前端的落地路径。

一、前端 DDD 分层架构:从理论到实际场景

在前端语境下,DDD 分层架构可映射为更具体的代码组织模式,各层对应前端开发中的实际职责

层级前端落地场景核心职责
UI 层Vue / React 组件、UI 库(如 Element Plus渲染用户界面、响应输入操作,是用户与系统交互的入口与结果载体
控制层组件交互逻辑(如 VueComposition APIReact 的自定义 Hooks管理事件流、绑定视图与业务模型、解析数据,承担 “用户操作→业务逻辑” 的调度
领域层TypeScript 业务模型、业务函数封装业务规则:用 “实体 / 值对象” 定义业务概念,用 “服务” 实现业务操作
基础层工具库(axios、localStorage 等)提供 API 请求、持久化存储、导出工具等通用技术能力,支撑上层业务

各层代码示例

1. UI 层(纯展示与交互触发)
<!-- 拦截池列表组件 --><template><el-table :data="ruleList" border><el-table-column prop="name" label="规则名称"/><el-table-column label="操作"><template #default="scope"><el-button @click="handleEdit(scope.row.id)">编辑</el-button></template></el-table-column></el-table></template><script setup lang="ts">import{ useInterceptionPoolController }from'./controller';const{ ruleList, getRuleList, loading }=useInterceptionPoolController();const emit =defineEmits(['openEditModal']);// 仅触发业务操作,不处理逻辑consthandleEdit=(id:string)=>emit('openEditModal', id);// 组件挂载时触发查询onMounted(()=>getRuleList());</script>
2. 控制层(交互与业务的衔接)
// 拦截池交互逻辑(Vue Composition API)import{ ref }from'vue';import{ InterceptionPoolUseCase }from'./usecase';exportfunctionuseInterceptionPoolController(){const loading =ref(false);const usecase =newInterceptionPoolUseCase();// 调度业务逻辑,处理视图状态constgetRuleList=async()=>{ loading.value =true;await usecase.getList(); loading.value =false;};return{ loading, getRuleList, ruleList: usecase.ruleList };}
3. 领域层(核心业务规则)
// 实体:拦截规则(封装业务规则)exportclassInterceptionRule{constructor(public id:string,public name:string,public condition:string){// 业务规则:规则名称不能为空if(!name.trim())thrownewError('规则名称不可为空');}}// 服务:拦截池业务操作exportclassInterceptionPoolService{// 业务逻辑:过滤已过期的规则filterExpiredRules(rules: InterceptionRule[]): InterceptionRule[]{return rules.filter(rule => rule.condition.includes('expire:false'));}}
4. 基础层(通用技术能力)
// API请求工具import axios from'axios';exportclassApiRepository{asyncget<T>(url:string, params: Record<string,any>):Promise<T>{const res =await axios.get(url,{ params });return res.data;}}

二、架构方案选型:平衡成本与收益

针对前端 DDD 的落地方案,结合项目规模和技术栈(Vue + React),做了量化对比:

方案类型核心优势核心劣势前端适配性改造成本
Vue / React + Remesh遵循 DDD、支持 CQRS / 事件驱动代码繁琐、异步交互复杂、需升级框架 / 适配中(需框架适配)高(框架升级 + 思维重构)
Vue + BLL 架构事件驱动、无需升级框架UI 与业务逻辑易混淆高(无需升级)中(需适配事件驱动思维)
Vue / React + Clean Architecture框架无关、易测试、分层清晰存在模板代码、学习曲线较陡高(轻量适配)低(轻量改造,核心逻辑复用)

最终选型Vue / React + Clean Architecture —— 既适配当前技术栈,又能以轻量改造成本落地,同时规避长期维护风险
这个方案需接入 React 组件库,可参考 Vue 项目渐进式迁移 React:组件库接入与跨框架协同技术方案

三、Clean Architecture 设计理念

Clean Architecture 核心是 “业务逻辑内聚,外部依赖外移”,其环形分层结构在前端中可映射为更具体的职责,确保核心业务不受技术工具的约束:

在这里插入图片描述


这张图的核心规则是 “内层不依赖外层”:从内到外依次是 “Entities(核心业务)→ Use Cases(应用逻辑)→ Repositories / Presenters(接口适配)→ 最外层的 Device / DB / API / UI(框架与工具)”,确保核心业务逻辑不被外部工具绑定

3.1 核心原则

  • 框架无关:业务逻辑不依赖 “Vue 的 ref”“React 的 setState” 等,仅在 UI 层使用框架能力
  • 可测试:核心业务逻辑(如 InterceptionRule 的名称校验)可脱离组件,用 Jest 独立测试
  • 多端 / 存储适配:扩展至小程序时仅需替换 UI 层,核心逻辑 100% 复用;存储方案可从 localStorage 切换为 IndexedDB,无需修改业务代码

3.2 架构层次

层次前端实现内容代码示例
实体层(Entities)TypeScript 业务模型 + 规则class InterceptionRule { /* 业务规则校验 */ }
用例层(Use Cases)业务操作函数async getInterceptionRules() { /* 调用API+业务逻辑 */ }
接口适配器层(Repositories / Presenters)数据格式转换、API 封装const mapRule = (raw: RawRule) => ({ id: raw.rule_id, name: raw.rule_name })
框架驱动层(Device / DB / API / UI)Vue / React 组件、UI 库、axios<el-table :data="ruleList" />

3.3 数据流向

前端请求的全链路流程:

用户操作组件 → Controller(交互调度) → UseCase(业务逻辑) → Repository(API 请求) → 接口数据 → UseCase(业务处理) → UI 组件(渲染) 

各层依赖关系遵循 “外层依赖内层”

View ← Presenter ← UseCase ← Repositories ← Model 

四、目录结构设计

以下是 “拦截池” 业务模块的目录结构,严格对应 Clean Architecture 的分层,确保各层职责不越界

├── modules │ ├── exception-flow # 业务场景:异常流程 │ │ ├── interception-pool # 模块名:拦截池 │ │ │ ├── components # UI 层:模块内组件(对应框架驱动层) │ │ │ ├── model # 实体层:业务模型(Entity / Service) │ │ │ │ ├── interception-pool.ts # 拦截规则实体 + 服务 │ │ │ ├── repository # 接口适配器层:API 封装 + 数据转换 │ │ │ │ ├── interception-pool.ts # API 请求方法 │ │ │ │ ├── types.ts # 接口类型定义 │ │ │ ├── usecase # 用例层:业务操作(对应图 2) │ │ │ │ ├── interception-pool.ts # 通用用例 │ │ │ │ ├── interception-pool-for-sip.ts # Sip 仓扩展用例 │ │ │ │ ├── usecase-factory.ts # 用例工厂(映射场景) │ │ │ ├── controller.ts # 控制层:交互逻辑调度 │ │ │ ├── index.vue # 模块入口:组装组件与逻辑 │ │ │ ├── README.md # 模块使用文档

4.1 Model 层(实体层)

对应 Clean Architecture实体层Entities),是核心业务规则的载体,区分 “接口实体(Entity)” 和 “UI 模型(Model)” 两类数据类型

  • Entity:后端接口返回的原始数据类型,与后端协议强绑定
  • Model:前端 UI 组件使用的数据类型,可根据展示需求调整字段格式 / 命名
代码示例:Model 层设计
// model/interception-pool.ts// 1. Entity:后端接口返回的原始类型(与API协议一致)exportinterfaceInterceptionRuleEntity{ rule_id:string;// 后端字段:规则ID rule_name:string;// 后端字段:规则名称 rule_condition:string;// 后端字段:规则条件 expire_flag:string;// 后端字段:过期标识}// 2. Model:前端UI使用的类型(适配组件展示)exportinterfaceInterceptionRuleModel{ id:string;// 前端字段:规则ID(统一命名) name:string;// 前端字段:规则名称 condition:string;// 前端字段:规则条件 isExpired:boolean;// 前端字段:是否过期(布尔值,便于UI判断)}// 3. 数据转换函数(Entity → Model)exportconst mapRuleEntityToModel =(entity: InterceptionRuleEntity): InterceptionRuleModel =>({ id: entity.rule_id, name: entity.rule_name, condition: entity.rule_condition, isExpired: entity.expire_flag ==='true'});
核心价值

通过类型分离与转换,隔离后端协议变更对前端 UI 的影响 —— 若后端字段 rule_id 改为 id,仅需修改 mapRuleEntityToModel 函数,无需调整 UI 组件

4.2 Repository 层(接口适配器层)

对应 Clean Architecture接口适配器层,是前端与后端 API 的 “桥梁”,核心职责是封装 API 请求逻辑,屏蔽接口细节对上层的影响

  • 封装 API 请求方法(GET / POST / PUT / DELETE
  • 处理请求参数格式化(如:分页参数、时间格式)
  • 统一处理接口异常(如:401 / 500 错误)
  • 转换接口返回数据(Entity → Model
代码示例:Repository 层设计
// repository/interception-pool.tsimport axios from'axios';import{ InterceptionRuleEntity, InterceptionRuleModel, mapRuleEntityToModel }from'../model/interception-pool';// 接口参数类型exportinterfaceQueryInterceptionRulesParams{ pageNum:number; pageSize:number; type?:string;// 规则类型(如 sip 仓/普通仓)}exportclassInterceptionPoolRepository{// 基础URLprivate baseUrl ='/api/interception-rules';// 查询拦截规则列表asyncqueryRules(params: QueryInterceptionRulesParams):Promise<InterceptionRuleModel[]>{try{// 1. 格式化请求参数(统一分页参数命名)const formattedParams ={ page_num: params.pageNum, page_size: params.pageSize, type: params.type };// 2. 发送API请求const res =await axios.get<InterceptionRuleEntity[]>(this.baseUrl,{ params: formattedParams });// 3. 转换数据格式(Entity → Model)return res.data.map(mapRuleEntityToModel);}catch(error){// 4. 统一异常处理console.error('查询拦截规则失败:', error);thrownewError('查询拦截规则失败,请重试');}}// 创建拦截规则asynccreateRule(rule: Omit<InterceptionRuleModel,'id'>):Promise<void>{// 转换Model → Entity(适配后端接口)const entity ={ rule_name: rule.name, rule_condition: rule.condition, expire_flag: rule.isExpired ?'true':'false'};await axios.post(this.baseUrl, entity);}}
核心价值

UseCase 层无需关心 API 的具体路径、参数格式,仅需调用 Repository 的方法,实现 “业务逻辑与接口细节解耦”

4.3 UseCase 层:复用与扩展的设计

对应 Clean Architecture用例层,该层采用 “抽象接口 + 继承复用” 的设计模式(符合开闭原则),通过 “定义契约 - 封装共性 - 扩展个性” 的分层逻辑,实现业务逻辑的复用与场景扩展的解耦

4.3.1 类图与设计模式解析
在这里插入图片描述

类图对应 “接口抽象 + 继承复用” 的设计,各元素的核心职责如下

  • InterceptionPoolUseCase(抽象接口):定义拦截池业务的核心能力契约,规范所有用例必须实现的方法
  • DefaultUseCase(通用实现):继承抽象接口,封装所有场景的共性逻辑(如:参数格式化、基础数据查询、通用业务规则)
  • SipWhUseCase(扩展实现):继承通用实现,重写个性化逻辑(如:Sip 仓的特殊参数、专属业务规则)
4.3.2 分层设计细节
1. 抽象接口:定义核心能力契约

抽象接口是 UseCase 层的 “能力清单”,确保所有用例都实现统一的核心方法,避免场景扩展时出现能力缺失

// usecase/interception-pool.ts// 对应类图中的「InterceptionPoolUseCase」抽象接口exportinterfaceInterceptionPoolUseCase{/** * 处理请求参数(共性/个性参数格式化) * @param params 前端传入的原始参数 * @returns 格式化后的接口请求参数 */processParams(params:any):any;/** * 获取拦截规则列表(核心业务操作) * @param params 前端传入的查询参数 * @returns 处理后的 UI 模型列表 */getList(params:any):Promise<InterceptionRuleModel[]>;}

核心价值:通过接口约束,保证所有拦截池场景(普通仓 / Sip 仓 / 临时仓)都具备 “参数处理 + 列表查询” 的核心能力,统一业务操作的调用方式

2. 通用 UseCase:封装共性逻辑

DefaultUseCase 对应类图中的通用实现,负责封装所有场景共享的逻辑,避免重复代码

// 对应类图中的「DefaultUseCase」exportclassDefaultInterceptionPoolUseCaseimplementsInterceptionPoolUseCase{// 对应类图中的「+repository: Repository」:依赖Repository层protected repository: InterceptionPoolRepository;constructor(){this.repository =newInterceptionPoolRepository();}/** * 通用参数处理:格式化分页/通用筛选条件 * 对应类图中的「+processParams」 */processParams(params:any):any{// 共性逻辑:统一分页参数命名(前端page→后端page_num)return{ page_num: params.page ||1, page_size: params.pageSize ||10, keyword: params.keyword ||''};}/** * 通用列表查询:参数处理→调用接口→数据转换→基础业务逻辑 * 对应类图中的「+getList」 */asyncgetList(params:any):Promise<InterceptionRuleModel[]>{try{// 步骤1:调用通用参数处理const formattedParams =this.processParams(params);// 步骤2:调用Repository层获取接口数据const ruleModels =awaitthis.repository.queryRules(formattedParams);// 步骤3:通用业务逻辑:过滤空名称的无效规则return ruleModels.filter(rule => rule.name.trim());}catch(error){// 共性异常处理:统一业务层面的错误提示console.error('获取拦截规则列表失败(通用逻辑):', error);thrownewError('查询规则失败,请检查网络后重试');}}}
共性逻辑封装点
  • 参数格式化:统一分页参数、通用筛选条件的处理
  • 接口调用:复用 Repository 层的查询逻辑
  • 基础业务规则:过滤无效数据、统一异常提示
3. 扩展 UseCase:实现个性化场景

SipWhUseCase 对应类图中的扩展实现,继承通用 UseCase 的共性逻辑,重写个性化方法,适配特定场景(如 Sip 仓)

// 对应类图中的「SipWhUseCase」exportclassSipWhUseCaseextendsDefaultInterceptionPoolUseCase{/** * 重写参数处理:添加 Sip 仓专属筛选条件 * 对应类图中的「+ processParams」(重写) */processParams(params:any):any{// 复用父类的通用参数处理逻辑const baseParams =super.processParams(params);// 个性化逻辑:强制添加「type: 'sip'」的筛选条件return{...baseParams, rule_type:'sip'// Sip 仓专属参数};}/** * 重写列表查询:补充 Sip 仓专属业务逻辑 * 对应类图中的「+ getList」(扩展) */asyncgetList(params:any):Promise<InterceptionRuleModel[]>{try{// 复用父类的「参数处理 → 接口调用 → 基础过滤」逻辑const baseRules =awaitsuper.getList(params);// 个性化业务逻辑:过滤 Sip 仓的跨境规则return baseRules.filter(rule =>!rule.condition.includes('cross_border: true'));}catch(error){// 个性化异常提示:区分 Sip 仓场景的错误console.error('获取 Sip 仓拦截规则失败:', error);thrownewError('Sip 仓规则查询失败,请联系管理员');}}}
扩展逻辑点
  • 重写 processParams:添加 Sip 仓专属参数 rule_type: 'sip'
  • 扩展 getList:在通用逻辑基础上,新增 “过滤跨境规则” 的个性化业务
4. 用例工厂:场景与 UseCase 的映射

为了让上层(控制层)无需关心 UseCase 的实例化细节,通过用例工厂实现 “场景 → UseCase” 的自动映射

// usecase/usecase-factory.tsexportclassInterceptionPoolUseCaseFactory{/** * 根据场景类型创建对应的UseCase实例 * @param scene 业务场景(default/sip) * @returns 对应场景的UseCase实例 */staticcreate(scene:'default'|'sip'): InterceptionPoolUseCase {switch(scene){case'sip':returnnewSipWhUseCase();// 场景对应Sip仓UseCasedefault:returnnewDefaultInterceptionPoolUseCase();// 默认对应通用UseCase}}}
4.3.3 实际业务场景中的调用

在控制层(Controller)中,通过用例工厂选择对应场景的 UseCase,实现业务逻辑的 “按需调用”

// controller.tsimport{ InterceptionPoolUseCaseFactory }from'./usecase/usecase-factory';exportfunctionuseInterceptionPoolController(scene:'default'|'sip'='default'){const loading =ref(false);// 通过工厂获取对应场景的UseCase实例const usecase = InterceptionPoolUseCaseFactory.create(scene);const ruleList =ref<InterceptionRuleModel[]>([]);constgetRuleList=async(params:any)=>{ loading.value =true;try{// 调用UseCase的getList方法(不同场景自动适配逻辑) ruleList.value =await usecase.getList(params);}finally{ loading.value =false;}};return{ loading, ruleList, getRuleList };}
场景调用示例
  • 普通仓页面:useInterceptionPoolController('default') → 调用通用 UseCase
  • Sip 仓页面:useInterceptionPoolController('sip') → 调用 Sip 仓扩展 UseCase
4.3.4 设计核心价值

这种 “抽象接口 + 通用继承 + 扩展重写” 的设计,完美适配大型前端系统的业务迭代需求

  1. 高复用性:共性逻辑(参数格式化、基础查询)仅需写一次,所有场景复用
  2. 易扩展性:新增场景(如 “临时拦截池”)时,只需新建 TempInterceptionPoolUseCase 继承DefaultUseCase,重写个性化方法即可
  3. 可维护性:业务逻辑分层清晰,通用逻辑的修改(如分页参数变更)仅需改 DefaultUseCase,就会同步到所有扩展场景
  4. 符合开闭原则:扩展新场景时 “不修改原有代码,只新增代码”,降低迭代风险

4.4 Presenter 层:前端的简化实现

Presenter 层是 Clean Architecture领域层与 UI 层的专属展示适配器,核心定位是做 “业务数据到展示数据” 的最后一步转换,但前端因组件化、展示逻辑与视图强耦合的特性,未单独抽离物理层做实现,而是采用 “职责保留、逻辑分散” 的轻量化落地方式。
本节重点明确 Presenter 层的原生核心职责、与 Repository 层的本质区别,并详解前端场景下的简化实现方案。

4.4.1 原生定位与前端简化的核心原因
1. Clean Architecture 中的原生定位

Clean Architecture 环形分层中,Presenter 层属于接口适配器层的展示侧实现,位于 UseCase 层与 UI 层之间,核心职责是将 UseCase 层处理后的通用业务 Model 转换为完全贴合 UI 展示的 View Model,并屏蔽所有业务逻辑对 UI 层的影响,是纯展示维度的适配层

2. 前端未单独抽离实现的核心原因

结合前端组件化开发的特性,单独创建 Presenter 物理层(如新建 presenter 文件夹)会造成层级冗余、逻辑跳转成本高,因此采用轻量化实现,核心原因有三点

  • 展示逻辑与 UI 强耦合:前端展示适配不仅是字段转换,还包含 “根据字段判断样式 / 显隐 / 按钮状态” 等与组件紧密绑定的逻辑,抽离后需频繁传参,降低开发效率
  • 数据适配粒度差异:后端接口到前端通用 Model 的转换是全局统一的结构适配,而 ModelView Model 的转换是组件专属的展示适配,分散在组件内更贴合前端开发习惯
  • 避免过度设计:多数中大型前端项目中,展示适配逻辑轻量且分散,单独抽离层会增加模板代码,违背 “轻量改造” 的架构初衷
4.4.2 与 Repository 层(接口适配器层)的核心区别

Presenter 层与 Repository 层虽同属适配器范畴,均承担 “数据转换” 职责,但二者的适配目标、转换阶段、职责边界有本质区别,也是前端架构中需明确的核心分层原则,具体区别如下表所示

对比维度4.2 Repository 层(接口适配器层)4.4 Presenter 层(展示适配器层)
核心适配目标后端接口原始数据 → 前端通用业务 Model前端通用业务 Model → 前端组件专属 View Model
数据转换阶段“接口请求后,业务逻辑处理前” 的转换“业务逻辑处理后,UI 渲染前” 的最终转换
数据处理维度结构 / 命名适配(如后端 rule_id → 前端 id)、数据类型转换(如字符串标识 → 布尔值)、屏蔽后端协议差异展示格式适配(如时间戳 → YYYY-MM-DD)、展示字段拼接(如姓名 + 工号)、贴合 UI 组件的个性化数据处理
职责边界聚焦 “前端与后端的接口通信解耦”,转换后的数据可在全项目多组件复用聚焦 “业务逻辑与 UI 展示的解耦”,转换后的数据仅在当前组件 / 页面复用
依赖关系依赖后端接口协议,向上为 UseCase 层提供统一数据依赖前端 UI 组件需求,向下从 UseCase 层获取业务数据
异常处理包含接口请求异常、数据格式异常的统一处理无异常处理,仅做纯数据格式 / 展示逻辑的转换
核心区分
  • Repository 层解决 “后端数据怎么适配前端业务逻辑” 的问题,转换后的 Model 是前端业务层的通用数据
  • Presenter 层解决 “前端业务数据怎么适配 UI 组件展示” 的问题,转换后的 View Model 是仅服务于渲染的专属数据
4.4.3 前端简化落地方案(职责保留 + 逻辑分散)

前端未单独抽离 Presenter 物理层,但完整保留其 “展示适配、解耦业务与 UI” 的核心职责,采用 “轻量工具函数 + 逻辑分散至对应层” 的落地方式,根据展示适配的复杂度,分为三种实现场景,均以 “拦截池” 业务为例做代码示例

场景 1:轻量展示适配(主流)→ 直接嵌入 UI 层 / 控制层

适用于单组件专属、逻辑简单的展示适配(如字段格式化、简单状态判断),直接在 UI 层(Vue / React 组件)或控制层中实现,是前端最常用的方式,代码示例(Vue 组件 UI 层实现)

<!-- components/InterceptionRuleList.vue 拦截池规则列表组件 --><template><el-table :data="viewRuleList" border stripe><el-table-column prop="id" label="规则ID" width="100"/><el-table-column prop="name" label="规则名称"><!-- 展示适配:根据是否过期标红 --><template #default="scope"><span :class="{ 'text-red-500': scope.row.isExpired }">{{ scope.row.name }}</span></template></el-table-column><!-- 展示适配:将原始条件字符串格式化为易读文本 --><el-table-column label="规则条件"><template #default="scope">{{formatCondition(scope.row.condition)}}</template></el-table-column><el-table-column prop="createTimeFormat" label="创建时间" width="180"/></el-table></template><script setup lang="ts">import{ ref, computed }from'vue';import{ useInterceptionPoolController }from'../controller';// 从控制层获取UseCase处理后的通用业务Modelconst{ ruleList }=useInterceptionPoolController('default');// Presenter层核心职责:Model → View Model(轻量展示适配,嵌入UI层)// 1. 时间戳格式化:业务Model中是时间戳,View Model中是格式化后的字符串const viewRuleList =computed(()=> ruleList.value.map(rule =>({...rule,// 继承通用业务Model的所有字段 createTimeFormat:newDate(rule.createTime).toLocaleString('zh-CN')// 展示专属字段})));// 2. 规则条件格式化:将后端原始字符串转换为易读文本(纯展示逻辑,与业务无关)const formatCondition =(condition:string):string=>{if(!condition)return'无规则条件';// 仅做展示格式转换,不涉及任何业务逻辑判断return condition.replace(/&/g,',').replace(/=/g,':');};</script><style scoped>.text-red-500{ color: #ef4444;}</style>
场景 2:复杂展示适配 → 抽离独立 Presenter 工具函数

适用于适配逻辑复杂、需多次复用的场景(如多组件展示同一类数据、复杂的展示字段拼接 / 计算),不单独建层,而是在对应模块下创建轻量 Presenter 工具函数,实现展示适配逻辑的复用

步骤 1:创建模块内 Presenter 工具函数(非物理层)
// interception-pool/presenter-utils.ts 仅做展示适配的工具函数(Presenter层职责)import{ InterceptionRuleModel }from'../model/interception-pool';// 定义组件专属的View Model(仅服务于渲染,无任何业务字段)exportinterfaceInterceptionRuleViewModel{ id:string; name:string; nameTag:string;// 展示专属:规则名称+过期标签(如“测试规则[已过期]”) conditionDesc:string;// 展示专属:格式化后的规则条件 createTime:string;// 展示专属:格式化后的创建时间 operateBtnStatus:boolean;// 展示专属:操作按钮是否禁用(根据过期状态)}// Presenter核心方法:Model → View Model(纯展示适配,无业务逻辑)exportconst mapRuleModelToViewModel =(model: InterceptionRuleModel): InterceptionRuleViewModel =>{// 展示适配1:名称拼接过期标签const nameTag = model.isExpired ?`${model.name}[已过期]`: model.name;// 展示适配2:规则条件格式化const conditionDesc = model.condition ? model.condition.replace(/&/g,',').replace(/=/g,':'):'无规则条件';// 展示适配3:格式化创建时间const createTime =newDate(model.createTime).toLocaleString('zh-CN');// 展示适配4:根据过期状态判断操作按钮是否禁用const operateBtnStatus = model.isExpired;return{ id: model.id, name: model.name, nameTag, conditionDesc, createTime, operateBtnStatus };};
步骤 2:UI 层引入工具函数实现渲染
<!-- components/InterceptionRuleList.vue --><template><el-table :data="viewRuleList" border stripe><el-table-column prop="nameTag" label="规则名称"/><el-table-column prop="conditionDesc" label="规则条件"/><el-table-column prop="createTime" label="创建时间"/><el-table-column label="操作"><template #default="scope"><el-button :disabled="scope.row.operateBtnStatus"@click="handleEdit(scope.row.id)">编辑</el-button></template></el-table-column></el-table></template><script setup lang="ts">import{ ref, computed }from'vue';import{ useInterceptionPoolController }from'../controller';// 引入Presenter工具函数import{ mapRuleModelToViewModel, InterceptionRuleViewModel }from'./presenter-utils';const{ ruleList }=useInterceptionPoolController('default');// 统一做Model → View Model的转换const viewRuleList =computed<InterceptionRuleViewModel[]>(()=> ruleList.value.map(model =>mapRuleModelToViewModel(model)));</script>
步骤 3:跨模块通用展示适配 → 抽离公共 Presenter 工具

适用于全项目多模块复用的展示适配逻辑(如时间格式化、状态码转中文、金额单位转换),可在src/common/ 下创建公共 Presenter 工具,实现全局复用,本质仍是无物理层的工具化实现

代码示例(全局公共 Presenter 工具)
// src/common/presenter/format-utils.ts 全局展示适配工具/** * 时间戳格式化(全局通用展示适配) * @param timestamp 时间戳(业务Model通用字段) * @param format 格式化类型 * @returns 格式化后的时间字符串(View Model展示字段) */exportconst formatTimestamp =(timestamp:number, format:'date'|'datetime'='datetime'):string=>{if(!timestamp)return'-';const date =newDate(timestamp);const y = date.getFullYear();const m =(date.getMonth()+1).toString().padStart(2,'0');const d = date.getDate().toString().padStart(2,'0');const h = date.getHours().toString().padStart(2,'0');const min = date.getMinutes().toString().padStart(2,'0');return format ==='date'?`${y}-${m}-${d}`:`${y}-${m}-${d}${h}:${min}`;};/** * 布尔值状态转中文(全局通用展示适配) * @param status 布尔值(业务Model通用字段) * @param trueText 为true时的展示文本 * @param falseText 为false时的展示文本 * @returns 中文状态(View Model展示字段) */exportconst formatBoolStatus =(status:boolean, trueText:string, falseText:string):string=>{return status ? trueText : falseText;};
4.4.4 简化实现的核心原则与价值
1. 核心原则:保留职责,不硬套层级

前端简化实现 Presenter 层的核心是 “不追求物理层的抽离,只保证职责的独立与解耦”,需遵循三个原则

  • 展示适配与业务逻辑完全分离Presenter 层的所有代码仅做展示格式转换,不包含任何业务判断 / 规则处理(如不能在 Presenter 中过滤 “过期规则”,该逻辑属于 UseCase 层业务逻辑)
  • 适配逻辑贴近 UI:展示适配的代码尽可能在 “使用它的 UI 层 / 组件” 附近,减少跨层跳转的维护成本
  • 单一职责:每个 Presenter 工具函数仅负责一类数据的展示转换,避免逻辑混杂
2. 核心价值

即使未单独抽离物理层,Presenter 层的简化实现仍能发挥 Clean Architecture 赋予的核心价值

  • 解耦业务与展示:业务逻辑(UseCase 层)仅维护通用 Model,无需关心 UI 如何展示,UI 层修改展示格式时,不影响任何业务代码
  • 降低维护成本:展示适配逻辑集中管理(或贴近 UI),修改展示需求(如时间格式从 YYYY-MM-DD 改为 MM/DD/YYYY)时,仅需调整 Presenter 适配逻辑,不涉及组件其他代码
  • 提升组件复用性:通用业务 Model 可通过不同的 Presenter 适配逻辑,在不同组件中展示为不同的 View Model,实现 “一份业务数据,多端 / 多组件不同展示”

五、总结

通过 Vue / React + Clean Architecture 方案落地实现了

  1. 业务 - 框架解耦:实现核心业务逻辑与 Vue / React 框架的解耦,确保技术栈切换时实现 100% 代码复用
  2. 维护效率提升:业务规则变更仅需修改领域层代码,维护时间大幅缩短
  3. 跨端复用率PC 后台的拦截池逻辑在小程序中 100% 复用,新终端开发周期显著压缩
  4. 测试覆盖率:核心业务逻辑单元测试覆盖率提高线上 bug 率明显下降

该方案的核心价值是 “适配业务复杂度”—— 既避免了 “照搬后端架构” 的过度设计,又通过分层解耦解决了大型前端系统的维护痛点,为长期迭代提供了坚实的架构基础

Read more

Java开发新变革!飞算JavaAI深度剖析与实战指南

Java开发新变革!飞算JavaAI深度剖析与实战指南

摘要:文章通过分析Java开发领域的现状和挑战,引出了飞算JavaAI这一创新工具。它能显著提升开发效率,降低重复编码工作,并保障代码质量。文章详细介绍了飞算JavaAI的核心功能,包括自然语言输入、全流程自动化和完整工程源码输出等,并通过电商系统和企业级项目等实战案例展示了其强大性能。与其他工具的对比进一步凸显了飞算JavaAI的优势,使其成为Java开发者提升效率和创新能力的强大助力。 目录 引言:Java 开发的新时代曙光 一、飞算 JavaAI:Java 开发的变革者 (一)定义与定位 (二)核心功能深度解读 (三)独特优势彰显实力 二、飞算 JavaAI 注册使用流程详解 (一)注册流程图文并茂 (二)首次使用引导 (三)常见问题与解决 编辑 三、实战案例:飞算 JavaAI 大展身手 (一)电商系统开发实战 (二)企业级项目应用案例 (三)经典代码案例 四、

By Ne0inhk
Java 常见Exception全面解析:出现场景、错误排查与代码修正实战

Java 常见Exception全面解析:出现场景、错误排查与代码修正实战

文章目录 * 课程导言 * 适用对象 * 学习目标 * 课程安排 * 教学方式 * 第一部分:Java异常体系回顾(约10分钟) * 1.1 异常是什么? * 1.2 Java异常体系结构 * 1.3 异常信息解读 * 第二课时(上):运行时异常深度剖析(约30分钟) * 2.1 NullPointerException(空指针异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法流程图 * 代码修正与预防 * 2.2 ArrayIndexOutOfBoundsException(数组下标越界异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法 * 代码修正与预防 * 2.3 ClassCastException(类型转换异常) * 现象描述 * 出现场景 * 堆栈分析示例 * 排查方法 * 代码修正与预防 * 2.

By Ne0inhk
别再乱用 ArrayList 了!这 4 个隐藏坑,90% 的 Java 开发者都踩过

别再乱用 ArrayList 了!这 4 个隐藏坑,90% 的 Java 开发者都踩过

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 坑 1:遍历删除元素,触发 ConcurrentModificationException * 坑的表现 * 踩坑场景 * 底层原因(通俗解释) * 错误/正确代码对比 * 错误代码 * 正确代码(3 种方案) * 坑 2:初始容量设置不当,导致频繁扩容,性能损耗 * 坑的表现 * 踩坑场景 * 底层原因(通俗解释) * 错误/正确代码对比 * 错误代码 * 正确代码 * 扩展建议 * 坑 3:空指针/索引越界,忽略索引范围或元素为空 * 坑的表现 * 踩坑场景 * 底层原因(通俗解释) * 错误/

By Ne0inhk
豆包新模型与 PromptPilot 实操体验测评,AI 辅助创作的新范式探索

豆包新模型与 PromptPilot 实操体验测评,AI 辅助创作的新范式探索

摘要:在 AI 技术飞速发展的当下,各类大模型及辅助工具层出不穷,为开发者和创作者带来了全新的体验。2025 年 7 月 30 日厦门站的火山方舟线下 Meetup,为我们提供了近距离接触豆包新模型与 PromptPilot 的机会。本次重点体验了实验任务二中的 PromptPilot 操作实践,通过实际操作,对这两款工具的性能、特点及应用前景有了较为深入的认识,现将体验心得与测评分享如下。 1.体验背景与工具简介 1.1 体验背景 本次体验源于火山方舟线下 Meetup 的开发者实践活动,主要围绕豆包新模型和 PromptPilot 展开。豆包新模型作为一款先进的大语言模型,在自然语言理解、生成等方面进行了优化升级,旨在为用户提供更精准、流畅的交互体验。而 PromptPilot 则是一款辅助 Prompt 设计的工具,能够帮助用户更高效地生成符合需求的提示词,提升与大模型交互的效果。 在实验任务二中,PromptPilot 提供了 3

By Ne0inhk