引言

架构本身并无绝对的对错之分,关键在于是否契合当下的场景。 一个个人作品项目的架构,没必要一上来就照着企业级分布式系统去搭,它只需要在可预见的范围内做出合理的选择,并留好演进的路径。

上一篇完成了环境搭建、PRD 制定以及计划安排。这一篇,将深入探讨这套系统的核心骨架,包括如何进行分层设计、权限控制以及状态转换。这些设计决策不是 Trae 单方面拍板的,而是我确定了基本方向之后,Trae 参与讨论、给出建议、帮我落地,几轮推敲之后才最终定下来的。

第一部分:整体架构——前后端怎么分、各层怎么划

1.1 后端四层:Controller → Service → Mapper → DB

Trae SOLO 搭建项目骨架时,采用经典的 MVC 四层架构。它帮我绘制目录结构,并将每一层的职责边界写入项目的 Rule 文件,以确保后续代码生成严格遵循该约定。不花哨、不创新,但每一层都做了严格隔离:Controller → Service → Mapper → DB,详见下文图 [前后端分层架构图]。

每一层都有明确的职责边界:

允许的行为 禁止的行为
Controller 参数校验、调用 Service、返回 Result 直接操作 Mapper、执行业务逻辑
Service 业务编排、事务控制、调用 Mapper 直接处理 HTTP 请求/响应
Mapper 数据库操作、SQL 映射 编写业务逻辑、跨表事务编排

分层这事很常见,但难就难在一直都严格按分层规则来做。 在审查时,从没发现 Controller 偷偷写 SQL,也没发现 Service 直接去操作 HttpServletRequest,因为分层规范一开始就写进了规则里,Trae 每次生成代码都会自动照着这个规则来。

后端包结构按照业务模块组织:

模块 核心实体 职责
modules/system SysUser, SysRole, SysMenu 用户、角色、菜单管理
modules/org HrEmployee, OrgDepartment 组织架构、员工管理、状态机
modules/attendance LeaveType, LeaveQuota, AttPunchRecord, AttDailyResult 假期类型、假期额度、打卡、日考勤、月考勤汇总
modules/salary SalaryItem, SalaryRecord 薪资科目、核算、公式引擎
modules/recruitment RecruitmentCandidate, Position 候选人管理、招聘看板

再加上 common 包下的统一返回体 Result<T>、全局异常处理、Excel 导入导出、安全过滤器等公共组件,一个标准的 Spring Boot 企业级项目骨架就出来了。

1.2 前端四层:表现层 → 业务逻辑层 → 数据访问层 → 基础设施层

前端分层方面,Trae 最初给出的是五层方案,即 Views → Components → API → Stores → Utils,呈线性排列。乍一看职责分得很细,但实际写代码的时候发现不对劲:Views 和 Components 本质上都属于表现层,API 负责数据,Stores 管理状态,二者归根结底都是业务逻辑的载体。硬拆成五层不仅未使架构更清晰,反而让依赖方向显得牵强。 比如那条 Views → Components → API → Stores → Utils 的链,Components 什么时候会直接调 API 了?API 和 Stores 之间明明应该是 Services 调 API、Stores 存状态,不是什么线性上下游。

于是我把它纠正回了经典四层,这是前端架构中最基础也最经得起推敲的模型:表现层 → 业务逻辑层 → 数据访问层 → 基础设施层,详见下文图 [前后端分层架构图]。

四层之间的依赖方向清晰、不绕弯:

允许的行为 禁止的行为
表现层 页面布局、UI 呈现、调用业务逻辑层 直接调 API、写业务逻辑
业务逻辑层 业务编排、数据转换、状态管理、调 API 层 直接操作 DOM、处理 HTTP 拦截
数据访问层 HTTP 请求封装、对接后端接口 写业务逻辑、操作状态
基础设施层 纯工具函数、指令、拦截器、配置 写业务逻辑、持有状态

Trae 为何会给出五层方案?这实际上反映了 AI 的一种典型倾向,即倾向于将概念细化,认为层越多越专业。但架构不是乐高,不是拆得越散越好。Views 和 Components 本来就是表现层的两种粒度,Services 和 Stores 本来就是业务逻辑层的两种载体,四层已经足够把职责边界划清楚,再多就是过度设计了。 这件事上,我做了减法。

前端路由结构直接映射业务模块:

路由 对应模块 页面数
/dashboard 数据看板 1
/org/* 组织人事(员工/部门/岗位/组织架构) 8
/attendance/* 考勤管理(打卡/请假/加班/班次/日结果/月汇总/日历) 8
/salary/* 薪酬管理(科目/模板/核算/工资单) 5
/recruitment/* 招聘管理(职位/候选人/看板) 3
/system/* 系统管理(用户/角色/设置) 3

前后端分层架构图

1.3 我为什么选了这套分层?

前后端均采用了最为经典的方案,未涉及 DDD、微服务及 CQRS。这是我和 Trae 讨论之后,根据项目规范和业界最佳实践一起定下来的。

核心考量很简单:这是一个个人作品项目,不需要复杂架构来撑场面。 够用、清晰、可扩展,这三条就够了。

Trae 最初给出的方案更为复杂,建议引入 Redis 作为缓存,RabbitMQ 作为消息队列,理由是 这是企业级项目的标配。但我把它砍掉了。一个单实例部署的 HRM 演示项目,引入 Redis 和 MQ 只会增加部署复杂度,对实际价值几乎没有提升。架构不是越全越好,而是在当前场景下做出最合适的选择,同时为未来的演进留好空间。

所以在设计阶段,我和 Trae 一起梳理了可能的扩展路线,比如后续如果需要缓存,在哪里加 Redis 最合适;如果需要异步处理,哪些业务适合引入消息队列。这些预留接口不会增加当前的代码量,但确保了未来需要升级时,不会因为架构限制而束手束脚。

回过头来看,许多项目之所以后期变得混乱,并非架构选择有误,而是前期未做好规划与边界定义。 哪怕是一个简单的系统,前期把分层规则、命名规范、职责边界写清楚,后面就能避免大量返工和偏离轨道。Trae 在这里的价值不是想出了这些规则,而是帮我把规则写进了项目的 Rule 文件,并且在后续每一次代码生成中严格执行了它们。

第二部分:权限系统——四层设计,分阶段落地

架构搭建好后,权限系统就是保障 HRM 系统安全的关键,我和 Trae 讨论确定了一套四层设计且分阶段落地的权限体系。一个普通员工不应该看到全公司薪资,一个部门经理不能随意修改组织架构。最后确定的权限体系是完整的四层设计:API 认证、路由过滤、组件隐藏、数据隔离。

但在实际开发中,我并没有一次性全部实现。先做能立刻保护系统的(API 层),再做改善体验的(路由+组件层),最后做最复杂但对 MVP 价值最小的(数据权限层)。 下面按实现的优先级,从已完成的到还在设计中的,逐层展开。

四层权限控制完整流程图

2.1 第一优先级·已完成:API 层 — JWT + 拦截器 + Spring Security

此乃整个安全体系的基石,若无它,后续几层便如空中楼阁般无从谈起。整个后端安全体系基于 Spring Security + JWT 无状态认证

请求流程:
浏览器
→ [axios 请求拦截器:自动附加 Bearer Token]
→ [Spring Security Filter Chain]
→ [JwtAuthenticationFilter:解析 Token,设置 SecurityContext]
→ [Controller:@Tag + @Operation 注解标注的接口]
→ [Service 层业务逻辑]
→ [响应拦截器:code≠200 → Message.error + 401→强制登出]

关键组件:

组件 文件 职责
JWT 生成/验证 JwtTokenProvider.java 签发含 userId/username/roles 的 Token
JWT 过滤器 JwtAuthenticationFilter.java 每次请求从 Header 提取 Bearer Token,解析后注入 SecurityContext
安全配置 SecurityConfig.java 禁用 CSRF、启用 CORS、STATELESS 会话、BCrypt 密码加密、放行登录/健康检查/Swagger
前端拦截器 request.ts axios 请求拦截自动加 Token,响应拦截统一处理 401 → 强制登出跳转

SecurityConfig 中放行的路径:

// 登录路径,允许所有用户访问
.requestMatchers("/api/auth/login").permitAll()
// 健康检查路径,用于监控系统状态,允许所有访问
.requestMatchers("/actuator/**").permitAll()
// Swagger相关路径,用于展示API文档,允许所有访问
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
// 其他所有请求,均需经过认证
.anyRequest().authenticated()

401 处理链路:后端返回 401 后,前端request.ts响应拦截器捕获该状态,调用userStore.logout()清空本地状态,随后通过router.push('/login')跳转至登录页。

FilterChain 顺序设计:在SecurityConfig中,Filter 的排列顺序至关重要。JwtAuthenticationFilter置于UsernamePasswordAuthenticationFilter之前,以保证 JWT 无状态认证优先于表单登录。同时,将SecurityContextHolder的策略设为MODE_INHERITABLETHREADLOCAL,为后续异步处理预留空间。整个 FilterChain 职责明确,仅从 Token 中提取身份并注入 SecurityContext 后放行,不涉及权限与角色校验,边界清晰。

状态:✅ 已完整实现。这是首要实现的部分,没有它,后续设计就无法开展。

设计预留:JWT 的扩展空间。 当前 JWT 中只存储了 userIdusernameroles 三个字段,但 JwtTokenProvider.generateToken() 接受 Map<String, Object> 作为额外 claims,未来可随时向 Token 注入 dataScopedeptId 等字段,无需改动生成和验证逻辑。架构不是一步到位,而是在关键节点留好扩展口。

2.2 第二优先级·基础版已完成:路由层 — beforeEach 守卫

前端在router/index.ts中定义了路由守卫,逻辑如下:若访问路径为/login,直接放行;若不是且用户无 token,则强制跳转至登录页,否则放行。对应的代码如下:

router.beforeEach((to, _from, next) => {
  const userStore = useUserStore()
  if (to.path === '/login') {
    next()
  } else {
    if (!userStore.token) {
      next('/login')
    } else {
      next()
    }
  }
})

当前版本限制: 目前侧边栏导航采用硬编码方式,所有用户看到的菜单相同。完整方案是将菜单数据存储于sys_menu表,后端依据用户角色查询权限菜单,前端动态渲染侧边栏。该方案设计已完成,但在 MVP 阶段,鉴于系统角色数量有限,硬编码菜单结合 API 层鉴权已能保障安全。

状态:✅ 基础版已完成(路由守卫 + 硬编码菜单)。动态菜单方案已设计完成,计划在需要支持多角色差异化导航时实现。

2.3 第三优先级·代码就绪:组件层 — v-permission 指令

directives/permission.ts 实现了一个简洁的自定义指令:

export const permission: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const userStore = useUserStore()
    // 获取指令绑定的值,即所需权限
    const requiredPerm = binding.value as string
    // 如果所需权限存在,且当前用户不是超级管理员,且用户菜单中不包含该权限
    if (requiredPerm && !userStore.roles.includes('SUPER_ADMIN')
        && !userStore.menus.some(m => m.perms === requiredPerm)) {
      el.parentNode?.removeChild(el)
    }
  }
}

用法很简单:<el-button v-permission="'attendance:punch'">打卡</el-button>。如果当前用户没有 attendance:punch 权限,这个按钮直接从 DOM 中移除,连 v-if 都不用手动写。

这个设计的巧妙之处在于:SUPER_ADMIN 角色绕过所有组件级权限检查,管理员永远能看到所有按钮,不需要逐项配置。

当前版本限制: 指令代码已完成,但权限判断依赖userStore.menus。因动态菜单(2.2 的升级版)尚未实现,menus当前为空数组,导致非管理员用户的所有带v - permission按钮均会被隐藏。待动态菜单接入后,该功能即可正常使用。

状态:⚠️ 代码已完成,待动态菜单数据接入后即可正常工作。当前依赖 SUPER_ADMIN 豁免逻辑保证管理员可用。

2.4 第四优先级·设计中:数据权限 — ALL / DEPT_AND_CHILD / SELF

API、路由与组件三层解决了能否访问的问题,然而,还有一个更深入的问题,即能看到哪些数据。这一层在权限系统中最为复杂,也是 MVP 阶段唯一未实现执行逻辑的部分,不过其设计方案已完全确定。

DataPermissionContext 定义了三种数据范围:

// 代表可访问全部数据
public static final String DATA_SCOPE_ALL = "ALL";
// 代表可访问本部门及子部门数据
public static final String DATA_SCOPE_DEPT_AND_CHILD = "DEPT_AND_CHILD";
// 代表仅可访问本人数据
public static final String DATA_SCOPE_SELF = "SELF";

这个数据范围绑定在角色表 sys_roledata_scope 字段上(字段和种子数据已就绪)。一个拥有「部门经理」角色的用户,data_scope 值为 DEPT_AND_CHILD,那么查询员工列表时,通过 MyBatis 拦截器动态注入 WHERE dept_id IN (本部门ID, 子部门ID1, 子部门ID2 ...) 的条件。

角色 data_scope 能看到的员工数据
HR 管理员 ALL 全公司所有员工
部门经理 DEPT_AND_CHILD 本部门及下属部门员工
普通员工 SELF 仅自己的信息

这个机制妙就妙在它是声明式的。 无需在每个 Service 方法中编写if (role == SUPER_ADMIN) { … } else { … }这样的判断逻辑,只需配置一次数据范围,注入一次 SQL,整个模块的查询便会自动应用该规则。

为何 MVP 阶段未实现? 数据权限的实现涉及 MyBatis 拦截器、SQL 解析、部门树递归查询以及多表字段映射,是四层中最为复杂的一层。在 MVP 阶段,角色种类较少(仅管理员、部门经理、普通员工三种),数据隔离需求并不强烈。因此,相较于在早期耗费大量时间实现复杂的通用方案,先确定设计并预留接口,待多角色上线时再进行开发更为明智。

状态:📋 设计方案已完成(DataPermissionContext + sys_role.data_scope + MyBatis 拦截器方案),执行逻辑计划在后续迭代中实现。

四层权限控制完整时序图

第三部分:数据库设计精要

3.1 核心实体关系

云山HRM 共有 32 张业务表,按模块组织:

模块 前缀 核心表 数量
系统权限 sys_ sys_user, sys_role, sys_menu, sys_user_role, sys_role_menu, sys_login_log, sys_config 7
组织人事 org_ / hr_ hr_employee, org_department, org_job_category, org_job_position, org_dept_position, hr_employee_change_log 6
考勤管理 att_ att_punch_record, att_daily_result, att_month_summary, att_shift, att_group, att_leave_type, att_leave_quota 等 11
薪酬管理 sal_ sal_salary_item, sal_salary_template, sal_template_item, sal_salary_record, sal_salary_record_item 5
招聘管理 recruitment_ recruitment_position, recruitment_candidate, recruitment_candidate_status_log 3

核心实体关系简要版:

核心数据库 ER 图

3.2 命名规范

命名对象 规范 示例
表名 {module}_{entity} hr_employee, att_daily_result
字段名 snake_case employee_no, direct_leader_id
主键 id BIGINT AUTO_INCREMENT 所有表统一
唯一索引 uk_{field} uk_employee_no, uk_username
普通索引 idx_{field} idx_dept_id, idx_status
时间字段 DATETIME DEFAULT CURRENT_TIMESTAMP created_at, updated_at

3.3 脚本管理策略

数据库脚本没有堆在一个文件里,而是按用途拆成了 7个文件:

文件 类别 说明
db_table.sql 建表 用于创建 32 张业务表,采用 InnoDB 存储引擎,字符集为 utf8mb4
db_data.sql 初始数据 用于初始化公司、部门、员工、用户、角色、菜单、考勤种子数据
db_table_config.sql 系统配置 用于初始化sys_config 表 + 加班换算规则
db_table_update.sql 增量更新 后续迭代新增的表,通过 CREATE TABLE IF NOT EXISTS 安全追加
db_seed_salary.sql 薪资测试数据 薪资科目、模板、核算记录、明细的测试数据
db_test_attendance.sql 考勤测试数据 请假、加班、班次、考勤组、打卡记录测试数据
db_query_attendance.sql 查询参考 10 个常用考勤查询 SQL(日考勤、月汇总、异常统计等)

要重建表结构跑 db_table.sql,要测考勤跑 db_test_attendance.sql。每个文件职责单一,出问题也容易定位。

3.4 关键设计决策

为何选择 InnoDB + 自增主键?

InnoDB 是 MySQL 中广泛使用的存储引擎,具备事务处理、行级锁等功能,可有效保障数据的完整性与一致性,这对于 HRM 系统至关重要。

对比项 InnoDB + 自增 UUID 雪花ID
插入性能 ⭐⭐⭐ 顺序写入 ⭐ 随机写入 ⭐⭐
索引大小 8字节 16字节 8字节
可读性 ⭐⭐⭐ 直观 ⭐ 难以辨认 ⭐⭐
分布式兼容 ⭐ 单机友好 ⭐⭐⭐ ⭐⭐⭐
选择理由 HRM 单实例部署 + 可读性优先 → 自增主键是最佳选择

对于用于个人作品展示的 HRM 系统,无需考虑分库分表,自增主键凭借其简洁性与直观性占据优势。

第四部分:两个状态机的设计对比

HRM 系统的核心是状态流转。员工从入职到离职,候选人从沟通到入职,每一步都涉及状态的合法转换。

起初,我并未计划采用状态机方案。最初想法很简单,即在员工表中设置一个status字段,通过setStatus()方法更改状态。然而,Trae 在编写员工状态变更代码时提出建议,指出这种直接调用setStatus()方法缺乏约束,一旦代码出错,员工状态可能从 待入职 直接变为 已离职,导致数据异常。因此,Trae 建议使用状态机锁定合法转换路径,对非法转换直接抛出异常。

我一听有道理,就让它继续展开了。后面我们又讨论了候选人模块,发现候选人的状态流转不需要这么强的约束,于是做了两种不同风格的设计,对照看很有意思。

4.1 员工状态机:6 状态 12 规则

EmployeeStatus 枚举定义了 6 种状态:待入职、试用期、正式、待离职、已离职、已取消入职。各状态之间的合法流转路径共 12 条,详见下文图 [员工状态机流转图]。

状态转换规则用一张静态 Map 定义:

private static final Map<EmployeeStatus, Set<EmployeeStatus>> TRANSITION_RULES = new HashMap<>();

static {
    TRANSITION_RULES.put(EmployeeStatus.PENDING_HIRE, 
        Set.of(EmployeeStatus.PROBATION, EmployeeStatus.ACTIVE, EmployeeStatus.OFFER_CANCELLED));
    TRANSITION_RULES.put(EmployeeStatus.PROBATION, 
        Set.of(EmployeeStatus.ACTIVE, EmployeeStatus.TERMINATED, EmployeeStatus.RESIGNATION_PENDING));
    TRANSITION_RULES.put(EmployeeStatus.ACTIVE, 
        Set.of(EmployeeStatus.RESIGNATION_PENDING, EmployeeStatus.TERMINATED, EmployeeStatus.PROBATION));
    TRANSITION_RULES.put(EmployeeStatus.RESIGNATION_PENDING, 
        Set.of(EmployeeStatus.ACTIVE, EmployeeStatus.TERMINATED));
    TRANSITION_RULES.put(EmployeeStatus.TERMINATED, 
        Set.of(EmployeeStatus.PENDING_HIRE));
}

变量命名说明: 上述代码为最终修正版本。Trae 最初生成的枚举名存在中式英语问题,例如PENDING(待入职,语义不明)、REGULAR(意为常规的,并非正式的)、PENDING_LEAVE(待离职,中式直译)、LEFT(已离职,left为动词过去式,作为形容词不专业)。我将其修正为PENDING_HIREACTIVERESIGNATION_PENDINGTERMINATED,这些命名更易于开发者理解。可见,Trae 生成代码质量尚可,但命名方面需人为把关。

此外,这套规则并非 Trae 一次编写正确,其最初版本存在两个问题:

  • 待入职 → 已离职:将拿到 offer 但尚未正式入职的人员标记为"已离职",这种情况并不合理。因为"离职"意味着员工已经在公司任职过,而待入职人员并未开始工作。修正方案是新增 OFFER_CANCELLED(已取消入职)状态,当 offer 撤回时,状态变更为 PENDING_HIREOFFER_CANCELLED,以此与真正的离职状态彻底区分开来。
  • 待离职缺少撤销路径:Trae 最初遗漏了待离职状态撤销离职并回到正式状态的路径,我补充了 RESIGNATION_PENDING → ACTIVE 这一状态转换路径。

经过修正,最终确定为 6 个状态、12 条规则,每一条规则在业务层面都经得起仔细考量。以下是最终确认的完整转换表:

from → to 含义 触发的异动类型
待入职 → 试用期 员工正式入职 HIRED(入职)
待入职 → 正式 直接入职为正式员工 HIRED(入职)
待入职 → 已取消入职 撤销 offer OFFER_CANCELLED(取消入职)
试用期 → 正式 转正 CONFIRMED(转正)
试用期 → 待离职 试用期发起离职 RESIGNED(发起离职)
试用期 → 已离职 试用期离职 TERMINATED(离职)
正式 → 正式 员工异动 TRANSFERRED(异动)
正式 → 待离职 发起辞职 RESIGNED(发起离职)
正式 → 已离职 解聘 TERMINATED(离职)
待离职 → 正式 撤销离职 WITHDRAWN(撤销离职)
待离职 → 已离职 离职生效 TERMINATED(离职)
已离职 → 待入职 重新入职 REHIRED(重新入职)

员工状态机流转图

4.2 为什么不用 GoF 状态模式?

GoF 状态模式将对象状态封装为独立类,通过类间切换改变对象行为。但对于仅有 6 个状态和 12 条规则的员工状态机而言,使用该模式实属杀鸡用牛刀。

方案 类数量 代码量 新人理解时间
GoF 状态模式 6 个状态类 + 1 个 Context ~240 行 15 分钟
静态 Map 1 个枚举 + 1 个 Map ~40 行 30 秒

员工状态机静态 Map 方案与 GoF 状态模式对比图

4.3 审计与可追溯性

状态机不仅需具备防错功能,还应具有可追溯性。每次状态转换时,EmployeeTransitionServiceImpl.transition() 方法在更新状态后,会将变更前后的数据以 JSON 格式写入 hr_employee_change_log 表。如此一来,任何时候都能够追溯任意员工的状态变更历史,包括操作人、操作时间以及状态变化情况。

之所以选择 JSON 快照而非字段级日志,是由于员工数据结构复杂,涵盖部门、职位、薪酬等多维度信息。JSON 能够完整还原变更前后的全貌,相比之下,字段级日志只能呈现零散的变更点。

状态机的具体实现细节,将在后面的文章中详细阐述。此处仅探讨架构层面的设计决策。

第五部分:Trae 在架构决策中的表现点评

至此,对第二篇中 Trae 在架构决策过程中的真实表现进行总结:

采纳的部分 ✅

决策 Trae 做了什么 为什么直接采纳
后端四层 MVC 严格按照规范生成 Controller-Service-Mapper 分层骨架 标准做法,没有歧义
JWT + Spring Security 标准 JWT 无状态认证 + BCrypt 加密 安全实践成熟,无需调整
数据库命名 module_entity / snake_case / 索引命名 规范先行,一致性高
状态机 Map 方案 用静态 Map 替代 GoF 状态模式 够用就好
审计日志 JSON 快照 oldData/newData 存为 JSON 结构化追溯,比字段级日志更灵活

调整了的部分 ⚠️

调整点 Trae 原来写的 我改成了什么 原因
整体架构方案 提议加入 Redis 缓存 + RabbitMQ 消息队列 全部砍掉,保持单实例部署的简洁性 个人作品无需中间件,增加部署复杂度无实际价值
v-permission 指令 初始版本对所有角色生效 加了 SUPER_ADMIN 豁免逻辑 管理员不应被按钮权限限制
前端请求响应拦截 初始只处理了 token 附加 补充了 responseType === 'blob' 判断和 401 强制登出 Excel 下载是 blob 流,不走统一返回体
员工状态枚举命名 PENDING / REGULAR / PENDING_LEAVE / LEFT 修正为 PENDING_HIRE / ACTIVE / RESIGNATION_PENDING / TERMINATED,并新增 OFFER_CANCELLED Trae 生成的变量名中式英语严重,LEFT 等命名不专业,需人工修正
前端分层 给出了五层结构(Views → Components → API → Stores → Utils),线性箭头暗示严格的依赖链 纠正为经典四层(表现层 / 业务逻辑层 / 数据访问层 / 基础设施层),每层内可包含多个子模块 五层是对经典四层的过度拆分,且线性箭头在实际开发中经不起推敲,Components 不会直接调 API,API 和 Stores 也不是上下游关系

架构层面 Trae 的能力边界

就本次项目来看,Trae 在架构层面呈现出三个显著优势与两个不足

优势:

  1. 规范执行力:只要规范明确,Trae 便能严格遵循,不会遗忘、懈怠或偏离。例如,Controller 均按要求添加了 @Tag 注解,SQL 命名也全部采用了 snake_case 格式。。
  2. 模式识别:Trae 能够依据项目规模,自动挑选适宜的设计模式。如针对仅有 6 个状态的状态机,选用静态 Map 而非 GoF 状态模式,避免了不考虑实际场景而盲目套用复杂模式的情况。
  3. 一致性保障:在 4 个模块、32 张表以及 100 + 个 API 的整个项目中,Trae 确保了命名风格、包结构以及分层边界始终保持统一。

不足:

  1. 业务理解不够深入:Trae 虽知晓如何搭建架构,却未能充分理解为何此业务场景需要更严格的约束。例如员工状态机中,像待入职直接标记为已离职这种非法转换路径,便是由我发现并纠正的,Trae 起初并未察觉到该业务问题。
  2. 方案易过度设计:Trae 倾向于一开始就提供一套企业级标配方案,诸如 Redis、MQ、分布式锁等一应俱全。然而,在实际项目的起步阶段,这些往往并非必需。这就需要人为判断并筛选,去除冗余部分,前面提及的 Redis 和 RabbitMQ 便是典型示例。

结尾

至此,架构篇已讲解完毕,我们探讨了这套系统的核心架构内容,包括前后端的分层方式、权限的四层纵深控制机制、数据库的建模方法以及状态的流转过程。

回顾整个过程,Trae 在这些架构决策中并非主导者,却是最佳执行者。但 Trae 在确保架构一致性、维持命名规范性以及实现安全配置完整性等方面,发挥了重要作用。

下一篇,我们将聚焦于 5 天内我与 Trae 推进 24 个任务的过程,探讨 TDD 如何落地实施、遇到分歧怎样解决,以及哪些陷阱必须亲身经历才能避免。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐