Skip to content
第 3 章 ⏱ 12 分钟阅读

第 3 章:架构设计 ​

学习目标 ​

  • 掌握前后端分离项目的分层架构
  • 设计模块依赖关系与包结构
  • 熟悉常见的部署架构模式

一、整体架构 ​

TaskFlow 采用经典前后端分离 + 三层架构。前端 Vue 单页应用通过 Nginx 反向代理访问后端 API,后端按 Controller / Service / Mapper 分层。

┌──────────────────────────────────────────┐
│  浏览器 (Chrome / Edge)                   │
└─────────────────┬────────────────────────┘
                  │ HTTP/HTTPS
┌─────────────────▼────────────────────────┐
│  Nginx (反向代理 + 静态资源)              │
└─────────────────┬────────────────────────┘
                  │
┌─────────────────▼────────────────────────┐
│  Spring Boot 应用 (Tomcat 嵌入)           │
│  ┌────────────────────────────────────┐  │
│  │ Controller (接收请求)               │  │
│  ├────────────────────────────────────┤  │
│  │ Service (业务逻辑)                  │  │
│  ├────────────────────────────────────┤  │
│  │ Mapper (数据访问)                   │  │
│  └────────────────────────────────────┘  │
└────┬──────────────┬──────────────┬───────┘
     │              │              │
  ┌──▼───┐      ┌──▼───┐      ┌───▼───┐
  │MySQL │      │Redis │      │RabbitMQ│
  └──────┘      └──────┘      └───────┘

二、后端分层 ​

java
// 标准的 Controller / Service / Mapper 三层
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {        // ① 控制层:接收请求、参数校验、返回响应
    private final UserService userService;

    @GetMapping("/{id}")
    public Result<UserVO> getById(@PathVariable Long id) {
        return Result.ok(userService.getVOById(id));
    }
}

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {  // ② 业务层:事务、权限、业务编排
    private final UserMapper userMapper;

    @Transactional
    @Override
    public UserVO getVOById(Long id) {
        User user = userMapper.selectById(id);
        Assert.notNull(user, ErrorCode.USER_NOT_FOUND);
        return convertToVO(user);
    }
}

@Mapper
public interface UserMapper extends BaseMapper<User> { // ③ 数据层:SQL 操作
    User selectByUsername(@Param("username") String username);
}

各层职责:

  • Controller:接请求、验参数、调 Service、包响应、不写业务逻辑
  • Service:事务控制、业务编排、跨 Mapper 操作、权限校验
  • Mapper:SQL 操作,继承 MyBatis Plus 的 BaseMapper 即可省掉 80% 单表 CRUD

⚠️ 坑 1:Controller 里写业务判断是常见坏味道。if (user.status == 0) ... 应该在 Service 层。Controller 只做参数转对象,Service 才决定"能不能继续"。

三、模块依赖关系 ​

taskflow-parent (父 POM,统一版本)
    │
    ├── taskflow-common            ← 通用类,不依赖任何业务
    │       ↑
    ├── taskflow-system            ← 业务模块,依赖 common
    │       ↑
    └── taskflow-application       ← 启动模块,依赖 system + common

父 POM 统一管理版本:

xml
<!-- taskflow-parent/pom.xml -->
<groupId>com.taskflow</groupId>
<artifactId>taskflow-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
    <module>taskflow-common</module>
    <module>taskflow-system</module>
    <module>taskflow-application</module>
</modules>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.5</version>
        </dependency>
    </dependencies>
</dependencyManagement>

子模块只写 groupId + artifactId,不写版本号:

xml
<!-- taskflow-system/pom.xml -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <!-- 版本由父 POM 管理 -->
</dependency>

<dependency>
    <groupId>com.taskflow</groupId>
    <artifactId>taskflow-common</artifactId>
    <version>${project.version}</version>
</dependency>

四、请求处理流程 ​

一个 HTTP 请求的完整生命周期:

客户端请求
    │
    ▼
① Nginx (反向代理,gzip,SSL)
    │
    ▼
② Tomcat (Servlet 容器)
    │
    ▼
③ Spring Security Filter Chain
   - TraceIdFilter (生成请求 ID)
   - JwtAuthenticationFilter (验证 token)
   - AccessDeniedHandler (权限不足)
    │
    ▼
④ DispatcherServlet (路由匹配)
    │
    ▼
⑤ @RestController 方法 (参数绑定 + @Valid 校验)
    │
    ▼
⑥ @RequiresPermission AOP (权限校验)
    │
    ▼
⑦ Service (业务逻辑,@Transactional 事务)
    │
    ▼
⑧ MyBatis Mapper (SQL 执行)
    │
    ▼
⑨ MySQL / Redis (数据)
    │
    ▼
响应序列化 + 包装 Result<T>

五、包结构规范 ​

每个业务模块按职责分包,统一约定:

com.taskflow.system
├── controller/        # 控制层
├── service/           # 业务接口
│   └── impl/          # 业务实现
├── mapper/            # 数据访问
├── entity/            # 数据库实体
├── dto/               # 入参对象(前端传过来)
├── vo/                # 出参对象(返回给前端)
├── convert/           # DTO/VO/Entity 互转
├── constant/          # 常量
└── enums/             # 枚举

为什么不用 Entity 直接当 VO?

  • Entity 字段多,可能含密码、版本号
  • VO 字段是给前端看的,要做脱敏处理
  • DTO 是入参校验,加 @NotBlank 等注解
java
// 入参 DTO
@Data
public class UserCreateDTO {
    @NotBlank
    @Length(min = 3, max = 50)
    private String username;

    @NotBlank
    @Length(min = 6, max = 100)
    private String password;
}

// 出参 VO(隐藏敏感字段)
@Data
public class UserVO {
    private Long id;
    private String username;
    private String nickname;
    // 注意:没有 password 字段!
}

六、缓存策略 ​

权限校验每个接口都会触发,必须缓存:

java
@Service
@RequiredArgsConstructor
public class PermissionService {
    private final RedisTemplate<String, Object> redis;

    private static final String PERM_KEY_PREFIX = "user:perms:";
    private static final Duration CACHE_TTL = Duration.ofMinutes(30);

    public Set<String> getUserPermissions(Long userId) {
        String key = PERM_KEY_PREFIX + userId;

        // ① 查缓存
        Object cached = redis.opsForValue().get(key);
        if (cached instanceof Set<?> set) {
            return set.stream().map(Object::toString).collect(Collectors.toSet());
        }

        // ② 查 DB
        Set<String> perms = loadFromDB(userId);

        // ③ 写缓存
        redis.opsForValue().set(key, perms, CACHE_TTL);
        return perms;
    }
}

缓存失效时机:用户角色变更、菜单权限调整时,主动清理:

java
public void clearCache(Long userId) {
    redis.delete(PERM_KEY_PREFIX + userId);
}

七、异常处理 ​

统一异常处理用 @RestControllerAdvice 拦截所有 Controller 抛出的异常:

java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusiness(BusinessException ex) {
        return Result.fail(ex.getCode(), ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValid(MethodArgumentNotValidException ex) {
        String msg = ex.getBindingResult().getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining("; "));
        return Result.fail(400, msg);
    }
}

八、本章小结 ​

要点关键
架构模式前后端分离 + 三层架构
后端分层Controller / Service / Mapper
模块依赖parent / common / system / application
包结构controller / service / mapper / dto / vo
缓存Redis 缓存用户权限,30 分钟 TTL
异常全局 @RestControllerAdvice 统一捕获

动手练习 ​

  1. 画架构图:用 ASCII 或 draw.io 画出你项目的后端分层
  2. 分模块:把现有项目按 common/system/application 拆三个 Maven 模块
  3. 写包结构:在 system 模块下建 controller/service/mapper/dto/vo 等空包

下一章:第 4 章:数据库设计 →

本站基于 VitePress 构建 · 由 StackHub 团队维护