【项目一DORM|架构分析】

宿舍电力管理系统后端架构深度解析

📖 文档说明

本文档以完整的HTTP请求处理流程为线索,深入剖析宿舍电力管理系统的后端架构、中间件机制和核心设计模式。从请求入口到业务逻辑再到数据持久化,逐层解析每个环节的工作原理。


目录

  1. 架构全景图
  2. 请求处理全流程剖析
  3. 核心中间件深度解析
  4. 分层架构与业务逻辑
  5. 关键设计模式与最佳实践
  6. 安全机制实现详解
  7. 性能优化策略
  8. [核心语法与Java 17特性](#核心语法与Java 17特性)

1. 架构全景图

1.1 系统架构总览

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                         用户层 (Frontend)                        │
│                     Vue.js / Dorm-Power-Console                  │
└───────────────────────┬─────────────────────────────────────────┘
                        │ HTTPS / WebSocket
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Spring Boot 3.2.3 应用层                      │
│  ┌─────────────┬─────────────┬─────────────┬─────────────┐     │
│  │ Controller  │  Service    │ Repository  │   Entity    │     │
│  │ (路由层)    │  (业务层)   │  (数据层)   │   (模型层)  │     │
│  └──────┬──────┴──────┬──────┴──────┬──────┴──────┬──────┘     │
│         │             │             │             │            │
│  ┌──────▼──────────────────────────────────────────▼──────┐    │
│  │         核心中间件层 (Middleware Layer)                  │    │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │    │
│  │  │Spring Security│  │  AOP切面    │  │  JWT过滤器  │   │    │
│  │  │  (认证授权)   │  │  (日志限流) │  │  (令牌验证) │   │    │
│  │  └─────────────┘  └─────────────┘  └─────────────┘   │    │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │    │
│  │  │  限流器     │  │  缓存管理   │  │  异常处理   │   │    │
│  │  │ (RateLimiter)│ │ (@Cacheable)│ │ (@ControllerAdvice)│ │
│  │  └─────────────┘  └─────────────┘  └─────────────┘   │    │
│  └──────────────────────────────────────────────────────┘    │
└───────────────────────┬─────────────────────────────────────────┘
                        │ JDBC / MQTT
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│                    数据层 (Data Layer)                           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐         │
│  │  PostgreSQL  │  │    H2 DB     │  │   MQTT Broker│         │
│  │  (生产环境)   │  │  (测试环境)  │  │  (设备通信)  │         │
│  └──────────────┘  └──────────────┘  └──────────────┘         │
└─────────────────────────────────────────────────────────────────┘

1.2 技术栈矩阵

分层 技术组件 版本 职责
核心框架 Spring Boot 3.2.3 应用启动、自动配置
编程语言 Java 21 语法支持、性能、虚拟线程
构建工具 Maven 3.x 依赖管理、构建
认证授权 Spring Security 6.x 认证、授权、方法级安全
令牌机制 JWT (JJWT) 0.12.x 无状态令牌生成验证
数据持久 Spring Data JPA 3.x ORM、数据库操作
数据库 PostgreSQL 15.x 生产环境数据存储
实时通信 MQTT (Paho) 1.2.x IoT设备通信
WebSocket Spring WebSocket 6.x 浏览器实时推送
限流 Guava RateLimiter 32.x 令牌桶算法限流
日志 Log4j 2 / SLF4J 2.x 日志记录、审计
验证 Spring Validation 3.x 参数校验
文档 OpenAPI 3 (Swagger) 2.x API文档生成

2. 请求处理全流程剖析

2.1 典型请求流程:用户登录

让我们以POST /api/auth/login登录请求为例,完整追踪请求处理链路:

复制代码
客户端请求
    ↓
1. 跨域预检 (CORS)
    ↓
2. Spring Security过滤器链
    ├─ CORS过滤器 (CorsFilter)
    ├─ JWT认证过滤器 (JwtAuthenticationFilter) → 无Token,跳过
    └─ 授权过滤器 (AuthorizationFilter)
    ↓
3. AOP切面拦截 (@Around)
    ├─ 获取请求信息 (IP、URI、Method)
    ├─ 限流检查 (RateLimit: 2 req/s)
    └─ 记录请求日志
    ↓
4. Controller层 (AuthController.login)
    ├─ @Valid 验证请求参数
    ├─ 调用Service层 (AuthService.login)
    └─ 返回ResponseEntity
    ↓
5. Service层 (AuthService.login)
    ├─ 参数规范化 (trim, toLowerCase)
    ├─ 验证账号格式
    ├─ 查询用户 (UserAccountRepository.findById)
    ├─ 验证密码 (PBKDF2哈希比对)
    ├─ 生成JWT令牌 (JwtUtil.generateToken)
    └─ 构造响应
    ↓
6. Repository层 (UserAccountRepository)
    ├─ JPA查询 (findById)
    ├─ SQL执行 (PostgreSQL)
    └─ 结果映射 (ResultSet → UserAccount)
    ↓
7. AOP审计日志 (@Around @AuditLog)
    └─ 记录操作日志 (AUDIT Logger)
    ↓
8. 响应返回
    ├─ 状态码 200
    ├─ JSON响应体
    └─ 无CORS头(已处理)

2.2 代码层面的详细流程

阶段1: CORS跨域配置 (CorsConfig.java)
java 复制代码
@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();

        // 允许的来源(生产环境应限制为具体域名)
        config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "*"));

        // 允许的HTTP方法
        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));

        // 允许的请求头
        config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Requested-With"));

        // 是否允许携带Cookie
        config.setAllowCredentials(true);

        // 预检请求缓存时间(秒)
        config.setMaxAge(3600L);

        // 允许暴露的响应头
        config.setExposedHeaders(Arrays.asList("Authorization"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);  // 应用于所有路径

        return source;
    }
}

关键点解析:

  • 预检请求 (Preflight): 浏览器在发送复杂请求(如带自定义头的POST)前,会先发送OPTIONS请求询问服务器是否允许
  • Access-Control-Allow-Origin: 响应头,指定允许跨域的来源
  • Access-Control-Allow-Credentials : 是否允许携带Cookie,设置为true时allowedOrigins不能为*
阶段2: Spring Security过滤器链 (SecurityConfig.java)
java 复制代码
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        // 1. CORS配置
        .cors(cors -> cors.configurationSource(corsConfigurationSource))

        // 2. CSRF防护(JWT无状态应用禁用)
        .csrf(AbstractHttpConfigurer::disable)

        // 3. 请求授权规则
        .authorizeHttpRequests(authorize -> authorize
            // OPTIONS预检请求放行
            .requestMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll()

            // 公开路径(无需认证)
            .requestMatchers(
                "/health",
                "/actuator/**",
                "/api/auth/**",       // 认证相关接口
                "/api/simulator/**",  // MQTT模拟器接口
                "/ws",                // WebSocket握手
                "/swagger-ui/**",
                "/swagger-ui.html",
                "/v3/api-docs/**",
                "/v3/api-docs.yaml"
            ).permitAll()

            // RBAC管理(需要ADMIN角色)
            .requestMatchers("/api/rbac/**").hasRole("ADMIN")

            // 管理员路径(需要ADMIN角色)
            .requestMatchers("/api/admin/**").hasRole("ADMIN")

            // 用户管理(需要认证)
            .requestMatchers("/api/users/**").authenticated()

            // 设备管理(需要认证)
            .requestMatchers("/api/devices/**").authenticated()

            // 遥测数据(需要认证)
            .requestMatchers("/api/telemetry/**").authenticated()

            // 命令控制(需要认证)
            .requestMatchers("/api/commands/**").authenticated()
            .requestMatchers("/api/strips/**").authenticated()
            .requestMatchers("/api/cmd/**").authenticated()

            // 设备分组(需要认证)
            .requestMatchers("/api/groups/**").authenticated()

            // 告警管理(需要认证)
            .requestMatchers("/api/alerts/**").authenticated()

            // 定时任务(需要认证)
            .requestMatchers("/api/tasks/**").authenticated()

            // 计费管理(需要认证)
            .requestMatchers("/api/billing/**").authenticated()

            // 宿舍管理(需要认证)
            .requestMatchers("/api/dorm/**").authenticated()

            // 学生管理(需要认证)
            .requestMatchers("/api/students/**").authenticated()

            // AI报告(需要认证)
            .requestMatchers("/api/rooms/**").authenticated()

            // AI客服Agent(需要认证)
            .requestMatchers("/api/agent/**").authenticated()

            // 通知系统(需要认证)
            .requestMatchers("/api/notifications/**").authenticated()

            // 其他路径需要认证
            .anyRequest().authenticated()
        )

        // 4. 添加JWT认证过滤器(在UsernamePasswordAuthenticationFilter之前)
        .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

权限分级说明:

级别 路径模式 说明
公开 /health, /api/auth/**, /api/simulator/** 无需认证即可访问
认证 /api/devices/**, /api/telemetry/** 需要有效的JWT令牌
管理员 /api/admin/**, /api/rbac/** 需要ADMIN角色

过滤器链执行顺序:

复制代码
1. WebAsyncManagerIntegrationFilter
2. SecurityContextPersistenceFilter
3. HeaderWriterFilter
4. CorsFilter  ← CORS跨域处理
5. LogoutFilter
6. JwtAuthenticationFilter  ← JWT令牌验证
7. DefaultLoginPageGeneratingFilter
8. DefaultLogoutPageGeneratingFilter
9. UsernamePasswordAuthenticationFilter
10. AuthorizationFilter  ← 授权检查
...
阶段3: JWT认证过滤器 (JwtAuthenticationFilter.java)
java 复制代码
@Override
protected void doFilterInternal(
    HttpServletRequest request,
    HttpServletResponse response,
    FilterChain filterChain
) throws ServletException, IOException {

    // 步骤1: 从请求头提取Token
    String token = getTokenFromRequest(request);
    // Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

    // 步骤2: 验证Token(黑名单检查 + 签名验证)
    if (token != null && jwtUtil.validateToken(token)) {
        try {
            // 步骤3: 解析JWT载荷
            Claims claims = Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())  // HMAC-SHA256密钥
                    .build()
                    .parseClaimsJws(token)
                    .getBody();

            // 提取用户名和角色
            String username = claims.getSubject();      // "admin"
            String role = claims.get("role", String.class);  // "ADMIN"

            // 步骤4: 从RBAC系统获取用户权限
            List<SimpleGrantedAuthority> authorities = getUserAuthorities(username, role);
            // ["ROLE_ADMIN", "device:read", "device:write", ...]

            // 步骤5: 创建认证对象
            UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(username, null, authorities);

            // 设置请求详情(IP、SessionID等)
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            // 步骤6: 设置到SecurityContext(线程局部变量)
            SecurityContextHolder.getContext().setAuthentication(authentication);

        } catch (Exception e) {
            // Token无效,继续过滤链(未认证状态)
        }
    }

    // 步骤7: 继续执行过滤器链
    filterChain.doFilter(request, response);
}

private String getTokenFromRequest(HttpServletRequest request) {
    String bearerToken = request.getHeader("Authorization");
    if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
        return bearerToken.substring(7);  // 去掉"Bearer "前缀
    }
    return null;
}

private List<SimpleGrantedAuthority> getUserAuthorities(String username, String defaultRole) {
    List<SimpleGrantedAuthority> authorities = new ArrayList<>();

    try {
        // 从RBAC系统查询用户角色
        List<Role> roles = rbacService.getUserRoles(username);

        if (roles.isEmpty() && defaultRole != null) {
            // 无分配角色,使用默认角色
            authorities.add(new SimpleGrantedAuthority("ROLE_" + defaultRole.toUpperCase()));
        } else {
            // 添加角色和权限
            for (Role role : roles) {
                if (role.isEnabled()) {
                    // 角色权限(如ROLE_ADMIN)
                    authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode().toUpperCase()));

                    // 具体权限(如device:read)
                    for (Permission permission : role.getPermissions()) {
                        if (permission.isEnabled()) {
                            authorities.add(new SimpleGrantedAuthority(permission.getCode()));
                        }
                    }
                }
            }
        }
    } catch (Exception e) {
        // 获取权限失败,降级为默认角色
        if (defaultRole != null) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + defaultRole.toUpperCase()));
        }
    }

    return authorities;
}

JWT结构解析:

复制代码
Header: {
  "alg": "HS256",      // 签名算法:HMAC-SHA256
  "typ": "JWT"         // Token类型
}

Payload (Claims): {
  "sub": "admin",      // 主题(用户名)
  "role": "ADMIN",     // 角色
  "iat": 1709600000,   // 签发时间(秒级时间戳)
  "exp": 1709686400    // 过期时间(24小时后)
}

Signature: HMACSHA256(base64UrlEncode(header)+'.'+base64UrlEncode(payload), secret)
阶段4: AOP切面限流与日志 (ApiAspect.java)
java 复制代码
@Around("@annotation(com.dormpower.annotation.RateLimit) || " +
        "@within(org.springframework.web.bind.annotation.RestController)")
public Object handleApiRequest(ProceedingJoinPoint joinPoint) throws Throwable {

    long startTime = System.currentTimeMillis();

    // 获取请求上下文
    HttpServletRequest request = getRequest();
    String method = request.getMethod();      // "POST"
    String uri = request.getRequestURI();     // "/api/auth/login"
    String ip = getClientIp(request);         // "192.168.1.100"

    // 获取目标方法信息
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method targetMethod = signature.getMethod();

    // 步骤1: 限流检查
    if (!isAdminUser()) {  // 管理员跳过限流
        // 优先使用方法上的@RateLimit注解
        RateLimit rateLimit = targetMethod.getAnnotation(RateLimit.class);
        if (rateLimit != null) {
            // 获取或创建限流器(按type区分)
            RateLimiter limiter = getRateLimiter(rateLimit);
            if (!limiter.tryAcquire()) {  // 尝试获取令牌
                logger.warn("请求被限流: {} {} from {}", method, uri, ip);
                throw new RuntimeException("请求过于频繁,请稍后再试");
            }
        } else {
            // 默认API限流(10 req/s)
            if (!apiRateLimiter.tryAcquire()) {
                logger.warn("API请求被限流: {} {} from {}", method, uri, ip);
                throw new RuntimeException("请求过于频繁,请稍后再试");
            }
        }
    }

    // 步骤2: 记录请求开始日志
    logger.debug("请求开始: {} {} from {}", method, uri, ip);

    try {
        // 步骤3: 执行目标方法(Controller方法)
        Object result = joinPoint.proceed();

        // 步骤4: 记录响应日志
        long duration = System.currentTimeMillis() - startTime;
        logger.info("请求完成: {} {} - {}ms - {}", method, uri, duration, ip);

        return result;

    } catch (Throwable e) {
        // 步骤5: 记录错误日志
        long duration = System.currentTimeMillis() - startTime;
        logger.error("请求失败: {} {} - {}ms - {} - {}",
            method, uri, duration, ip, e.getMessage());
        throw e;
    }
}

private RateLimiter getRateLimiter(RateLimit rateLimit) {
    String key = rateLimit.type();  // 如"login", "device"
    // 动态创建或获取限流器
    return rateLimiters.computeIfAbsent(key, k -> RateLimiter.create(rateLimit.value()));
}

@RateLimit注解使用示例:

java 复制代码
@PostMapping("/login")
@RateLimit(value = 2.0, type = "login")  // 每秒2个请求
@AuditLog(value = "用户登录", type = "AUTH")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
    // ...
}

Guava RateLimiter工作原理:

  • 令牌桶算法: 固定速率生成令牌,桶容量 = 速率 × 时间
  • tryAcquire(): 非阻塞尝试获取令牌,立即返回true/false
  • acquire(): 阻塞等待获取令牌,可能等待
  • 平滑突发限流 (SmoothBursty): 默认实现,允许突发流量
阶段5: Controller层处理 (AuthController.java)
java 复制代码
@RestController
@RequestMapping("/api/auth")
@Tag(name = "认证管理", description = "提供用户认证和令牌管理的RESTful API接口")
public class AuthController {

    @Autowired
    private AuthService authService;

    @Operation(summary = "用户登录", description = "使用账号密码登录系统,返回JWT令牌")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "登录成功"),
        @ApiResponse(responseCode = "401", description = "账号或密码错误")
    })
    @RateLimit(value = 2.0, type = "login")
    @AuditLog(value = "用户登录", type = "AUTH")
    @PostMapping("/login")
    public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
        // 步骤1: 参数验证(@Valid)
        // LoginRequest包含account和password字段,使用@NotBlank等注解校验

        String account = request.getAccount();
        String password = request.getPassword();

        // 步骤2: 调用Service层
        Map<String, Object> result = authService.login(account, password);

        // 步骤3: 返回响应
        return ResponseEntity.ok(result);
        // HTTP/1.1 200 OK
        // Content-Type: application/json
        // {
        //   "token": "eyJhbGci...",
        //   "user": {
        //     "username": "admin",
        //     "email": "admin@dorm.local",
        //     "role": "ADMIN"
        //   }
        // }
    }
}

OpenAPI注解作用:

  • @Tag: 分组标签,用于Swagger UI分类
  • @Operation: 接口描述,生成API文档
  • @ApiResponses: 响应码说明
  • @Parameter: 参数说明
  • 效果 : 自动生成交互式API文档,访问/swagger-ui.html查看
阶段6: Service层业务逻辑 (AuthService.java)
java 复制代码
@Service
public class AuthService {

    @Autowired
    private UserAccountRepository userAccountRepository;

    @Autowired
    private JwtUtil jwtUtil;

    @Value("${admin.username:admin}")
    private String adminUsername;

    @Value("${admin.email:admin@dorm.local}")
    private String adminEmail;

    public Map<String, Object> login(String account, String password) {
        // 步骤1: 参数规范化
        String normalizedAccount = account.trim();
        String normalizedAdminUsername = adminUsername.trim();
        String normalizedAdminEmail = adminEmail.trim().toLowerCase();

        // 步骤2: 验证账号格式(仅允许管理员账号登录)
        if (!normalizedAdminUsername.equals(normalizedAccount) &&
            !normalizedAdminEmail.equals(normalizedAccount.toLowerCase())) {
            throw new AuthenticationException("Invalid credentials");
        }

        // 步骤3: 查询用户(JPA Repository)
        UserAccount user = userAccountRepository.findById(normalizedAdminUsername).orElse(null);
        if (user == null) {
            throw new ResourceNotFoundException("User not found");
        }

        // 步骤4: 验证密码(PBKDF2哈希比对)
        // 数据库存储格式: pbkdf2_sha256$160000$salt$hash
        if (!EncryptionUtil.verifyPassword(password, user.getPasswordHash())) {
            throw new AuthenticationException("Invalid credentials");
        }

        // 步骤5: 生成JWT令牌
        String token = jwtUtil.generateToken(user.getUsername(), user.getRole());

        // 步骤6: 构造响应
        Map<String, Object> response = new HashMap<>();
        response.put("token", token);
        response.put("user", Map.of(
            "username", user.getUsername(),
            "email", user.getEmail(),
            "role", user.getRole()
        ));

        return response;
    }
}

密码验证流程:

java 复制代码
// EncryptionUtil.verifyPassword
public static boolean verifyPassword(String password, String encoded) {
    // 1. 拆分存储的哈希字符串
    // encoded = "pbkdf2_sha256$160000$a3f5c2d8b1e7$9f8e7d6c5b4a..."
    String[] parts = encoded.split("\\$");
    int iterations = Integer.parseInt(parts[1]);  // 160000
    String salt = parts[2];                        // "a3f5c2d8b1e7"
    String expectedDigest = parts[3];              // "9f8e7d6c5b4a..."

    // 2. 使用相同盐值和迭代次数重新哈希输入密码
    String actualDigest = hashPassword(password, salt, iterations);

    // 3. 恒定时间比较(防止时序攻击)
    return constantTimeEquals(expectedDigest, actualDigest);
}

private static String hashPassword(String password, String salt, int iterations) {
    PBEKeySpec spec = new PBEKeySpec(
        password.toCharArray(),              // 密码字符数组
        HexFormat.of().parseHex(salt),      // 16进制盐值解码为字节数组
        iterations,                          // 迭代次数(160000)
        256                                  // 密钥长度(256位)
    );

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    byte[] hash = factory.generateSecret(spec).getEncoded();

    return HexFormat.of().formatHex(hash);  // 转换为16进制字符串
}

安全特性:

  • 盐值 (Salt): 16字节随机数,防止彩虹表攻击
  • 迭代次数: 160000次,增加破解成本(约0.1秒/次)
  • 算法: PBKDF2WithHmacSHA256,行业标准密钥派生函数
  • 恒定时间比较: 防止时序攻击(timing attack)
阶段7: Repository层数据访问 (UserAccountRepository.java)
java 复制代码
@Repository
public interface UserAccountRepository extends JpaRepository<UserAccount, String> {

    // Spring Data JPA方法名查询(自动实现)

    // 根据邮箱查询用户
    Optional<UserAccount> findByEmail(String email);

    // 检查用户名是否存在
    boolean existsById(String username);

    // 自定义JPQL查询
    @Query("SELECT u FROM UserAccount u WHERE u.username = ?1 AND u.passwordHash = ?2")
    Optional<UserAccount> findByUsernameAndPassword(String username, String passwordHash);
}

JPA执行流程:

复制代码
UserAccount user = userAccountRepository.findById("admin").orElse(null);

1. Spring Data JPA代理生成实现类
   UserAccountRepository → SimpleJpaRepository

2. 调用EntityManager
   entityManager.find(UserAccount.class, "admin")

3. JPA提供者(Hibernate)执行
   - 检查一级缓存(Persistence Context)
   - 检查二级缓存(如启用)
   - 执行SQL: SELECT * FROM user_account WHERE username = 'admin'

4. ResultSet映射到Entity
   - 反射创建UserAccount对象
   - 设置属性(id, email, passwordHash, role...)
   - 托管到Persistence Context(一级缓存)

5. 返回Optional<UserAccount>

生成的SQL示例:

sql 复制代码
-- findById
SELECT
    ua.username,
    ua.email,
    ua.password_hash,
    ua.role,
    ua.reset_code_hash,
    ua.reset_expires_at,
    ua.created_at,
    ua.updated_at
FROM user_account ua
WHERE ua.username = 'admin';

-- existsById
SELECT EXISTS(
    SELECT 1 FROM user_account ua WHERE ua.username = 'admin'
);
阶段8: 审计日志记录 (ApiAspect.java)
java 复制代码
@Around("@annotation(com.dormpower.annotation.AuditLog)")
public Object handleAuditLog(ProceedingJoinPoint joinPoint) throws Throwable {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    AuditLog auditLog = method.getAnnotation(AuditLog.class);

    HttpServletRequest request = getRequest();
    String ip = getClientIp(request);  // "192.168.1.100"
    String user = request.getHeader("X-User");  // 可选的自定义用户头

    long startTime = System.currentTimeMillis();

    try {
        // 执行目标方法
        Object result = joinPoint.proceed();

        // 记录成功审计日志
        long duration = System.currentTimeMillis() - startTime;
        auditLogger.info("[{}] {} - {} - {}ms - SUCCESS",
                auditLog.type(),      // "AUTH"
                auditLog.value(),     // "用户登录"
                user != null ? user : ip,  // "admin" 或 IP
                duration);

        return result;

    } catch (Throwable e) {
        // 记录失败审计日志
        long duration = System.currentTimeMillis() - startTime;
        auditLogger.error("[{}] {} - {} - {}ms - FAILED: {}",
                auditLog.type(),
                auditLog.value(),
                user != null ? user : ip,
                duration,
                e.getMessage());
        throw e;
    }
}

审计日志输出示例:

复制代码
[AUDIT] [AUTH] 用户登录 - admin - 45ms - SUCCESS
[AUDIT] [DEVICE] 创建设备 - 192.168.1.100 - 23ms - FAILED: Device ID already exists

Log4j 2配置要点:

xml 复制代码
<Logger name="AUDIT" level="info" additivity="false">
    <AppenderRef ref="AuditFile"/>
</Logger>

<RollingFile name="AuditFile" fileName="logs/audit.log"
             filePattern="logs/audit-%d{yyyy-MM-dd}-%i.log">
    <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %m%n"/>
    <Policies>
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
        <SizeBasedTriggeringPolicy size="100MB"/>
    </Policies>
</RollingFile>

3. 核心中间件深度解析

3.1 Spring Security安全框架

3.1.1 认证流程
复制代码
1. 客户端发送请求(带Authorization: Bearer <token>)
   ↓
2. JwtAuthenticationFilter拦截
   ↓
3. 提取并验证Token
   ├─ 黑名单检查(登出令牌)
   ├─ 签名验证(HMAC-SHA256)
   ├─ 过期检查(exp claim)
   └─ 提取Claims(username, role)
   ↓
4. 查询RBAC系统
   ├─ 获取用户角色(Role)
   ├─ 获取角色权限(Permission)
   └─ 构造GrantedAuthority列表
   ↓
5. 创建Authentication对象
   UsernamePasswordAuthenticationToken(username, null, authorities)
   ↓
6. 设置到SecurityContext
   SecurityContextHolder.getContext().setAuthentication(authentication)
   ↓
7. 授权过滤器检查
   ├─ URL权限(requestMatchers().hasRole())
   └─ 方法级权限(@PreAuthorize)
   ↓
8. 放行或拒绝
3.1.2 授权配置详解
java 复制代码
.authorizeHttpRequests(authorize -> authorize
    // 1. 完全公开(无需任何认证)
    .requestMatchers("/health", "/api/auth/**").permitAll()

    // 2. 角色级别授权
    .requestMatchers("/api/admin/**").hasRole("ADMIN")  // 需要ROLE_ADMIN

    // 3. 权限级别授权
    .requestMatchers("/api/devices/**").hasAuthority("device:read")

    // 4. 多角色/权限组合
    .requestMatchers("/api/users/**")
        .hasAnyAuthority("user:read", "user:write")  // 满足任一即可

    // 5. 认证即可(不需要特定角色)
    .requestMatchers("/api/telemetry/**").authenticated()

    // 6. 自定义授权逻辑
    .requestMatchers("/api/custom/**").access((auth, req) -> {
        Authentication authentication = auth.get();
        return authentication != null &&
               authentication.getName().equals("admin");
    })

    // 7. 默认规则(兜底)
    .anyRequest().authenticated()
)

权限表达式对比:

表达式 说明 示例
permitAll() 完全公开 健康检查、登录接口
denyAll() 完全拒绝 未实现接口
authenticated() 需要认证(任何用户) 个人资料查询
hasRole("ADMIN") 需要ADMIN角色 管理员后台
hasAuthority("device:read") 需要device:read权限 设备数据查询
hasAnyRole("ADMIN", "USER") 满足任一角色 多角色访问
hasIpAddress("192.168.1.0/24") IP白名单 内网访问
3.1.3 方法级安全控制
java 复制代码
@Service
public class DeviceService {

    @Autowired
    private DeviceRepository deviceRepository;

    // 需要ADMIN角色
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteDevice(String deviceId) {
        deviceRepository.deleteById(deviceId);
    }

    // 需要device:write权限
    @PreAuthorize("hasAuthority('device:write')")
    public Device updateDevice(Device device) {
        return deviceRepository.save(device);
    }

    // 自定义SpEL表达式
    @PreAuthorize("#deviceId == authentication.name or hasRole('ADMIN')")
    public Device getDevice(String deviceId) {
        return deviceRepository.findById(deviceId).orElse(null);
    }

    // 后置过滤(返回结果过滤)
    @PostFilter("filterObject.owner == authentication.name")
    public List<Device> getAllDevices() {
        return deviceRepository.findAll();
    }
}

SpEL表达式常用变量:

  • authentication: Authentication对象
  • principal: 当前用户(UserDetails)
  • #param: 方法参数
  • returnObject: 返回值(@PostAuthorize)

3.2 AOP切面编程

3.2.1 AOP核心概念
概念 说明 代码示例
切面 (Aspect) 横切关注点的模块化 @Aspect @Component class ApiAspect
连接点 (Join Point) 程序执行的某个特定位置 方法调用、异常抛出
通知 (Advice) 在连接点执行的动作 @Around, @Before, @After
切点 (Pointcut) 匹配连接点的谓词 @annotation(RateLimit)
引入 (Introduction) 向类添加新方法/字段 不常用
3.2.2 通知类型详解
java 复制代码
@Aspect
@Component
public class LoggingAspect {

    // 环绕通知 - 最强大,可控制方法执行
    @Around("execution(* com.dormpower.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        // 前置逻辑
        logger.info("方法开始: {}", joinPoint.getSignature());

        try {
            // 执行目标方法
            Object result = joinPoint.proceed();

            // 后置逻辑
            long duration = System.currentTimeMillis() - start;
            logger.info("方法完成: {} - {}ms", joinPoint.getSignature(), duration);

            return result;

        } catch (Throwable e) {
            // 异常逻辑
            logger.error("方法异常: {} - {}", joinPoint.getSignature(), e.getMessage());
            throw e;
        } finally {
            // 最终逻辑
            logger.debug("方法结束: {}", joinPoint.getSignature());
        }
    }

    // 前置通知 - 方法执行前
    @Before("execution(* com.dormpower.controller.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("Controller方法调用: {}", joinPoint.getSignature());
    }

    // 后置通知 - 方法执行后(无论是否异常)
    @After("execution(* com.dormpower.repository.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        logger.debug("Repository方法结束: {}", joinPoint.getSignature());
    }

    // 返回通知 - 方法正常返回后
    @AfterReturning(pointcut = "execution(* com.dormpower.service.*.*(..))",
                    returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        logger.info("方法返回: {} - 结果: {}", joinPoint.getSignature(), result);
    }

    // 异常通知 - 方法抛出异常后
    @AfterThrowing(pointcut = "execution(* com.dormpower.service.*.*(..))",
                   throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        logger.error("方法异常: {} - {}", joinPoint.getSignature(), ex.getMessage());
    }
}
3.2.3 切点表达式语法
java 复制代码
// 1. execution - 匹配方法执行
@Pointcut("execution(* com.dormpower.service.*.*(..))")
// 任意返回类型,service包下任意类的任意方法,任意参数

// 2. @annotation - 匹配带注解的方法
@Pointcut("@annotation(com.dormpower.annotation.RateLimit)")

// 3. @within - 匹配带注解的类
@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")

// 4. within - 匹配指定包或类
@Pointcut("within(com.dormpower.controller..*)")
// controller包及其子包下的所有类

// 5. args - 匹配参数类型
@Pointcut("execution(* *(String,..))")
// 第一个参数为String类型的方法

// 6. 组合使用
@Pointcut("@annotation(RateLimit) || @within(RestController)")
// 带@RateLimit注解的方法 OR @RestController注解的类中的方法

切点表达式通配符:

  • *: 匹配任意字符(不包括包分隔符)
  • ..: 匹配任意数量的包或参数
  • +: 匹配指定类及其子类

3.3 JWT令牌管理

3.3.1 JWT工具类实现
java 复制代码
@Component
public class JwtUtil {

    @Value("${security.jwt.secret}")
    private String secret;

    @Value("${security.jwt.expiration:86400000}")  // 24小时
    private long expiration;

    @Autowired
    private TokenBlacklist tokenBlacklist;

    /**
     * 生成JWT令牌
     */
    public String generateToken(String username, String role) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);

        return Jwts.builder()
                .setSubject(username)                    // 主题(用户名)
                .claim("username", username)             // 自定义声明
                .claim("role", role)                     // 角色
                .setIssuedAt(now)                        // 签发时间
                .setExpiration(expiryDate)               // 过期时间
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)  // 签名
                .compact();                              // 压缩为字符串
    }

    /**
     * 验证令牌
     */
    public boolean validateToken(String token) {
        // 1. 检查黑名单
        if (tokenBlacklist.isBlacklisted(token)) {
            return false;
        }

        try {
            // 2. 解析并验证签名
            Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            // 签名无效、过期、格式错误等
            return false;
        }
    }

    /**
     * 从令牌获取用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }

    /**
     * 将令牌加入黑名单(登出)
     */
    public void blacklistToken(String token) {
        tokenBlacklist.addToBlacklist(token);
    }

    private SecretKey getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}
3.3.2 令牌黑名单实现
java 复制代码
@Component
public class TokenBlacklist {

    // 线程安全的HashMap,存储令牌及其加入黑名单的时间戳
    private final ConcurrentHashMap<String, Long> blacklist = new ConcurrentHashMap<>();

    // 令牌有效期(与JWT过期时间一致,2小时)
    private static final long EXPIRATION_MS = 2 * 60 * 60 * 1000;

    /**
     * 将令牌加入黑名单
     */
    public void addToBlacklist(String token) {
        // 记录当前时间戳
        blacklist.put(token, System.currentTimeMillis());

        // 清理过期的黑名单条目(异步执行,避免阻塞)
        CompletableFuture.runAsync(this::cleanupExpiredTokens);
    }

    /**
     * 检查令牌是否在黑名单中
     */
    public boolean isBlacklisted(String token) {
        Long timestamp = blacklist.get(token);
        if (timestamp == null) {
            return false;  // 未在黑名单中
        }

        // 检查是否已过期(黑名单有效期2小时)
        return System.currentTimeMillis() - timestamp < EXPIRATION_MS;
    }

    /**
     * 清理过期的黑名单条目
     */
    private void cleanupExpiredTokens() {
        long now = System.currentTimeMillis();

        // 移除所有过期的条目
        blacklist.entrySet().removeIf(entry ->
            now - entry.getValue() > EXPIRATION_MS
        );
    }

    /**
     * 获取黑名单大小(用于监控)
     */
    public int getSize() {
        return blacklist.size();
    }
}

黑名单机制的优势:

  • ✅ 实现令牌即时失效(登出功能)
  • ✅ 无需维护服务端Session
  • ✅ 分布式环境下无需共享状态(每个实例独立维护)
  • ✅ 自动清理过期条目,内存占用可控

性能考量:

  • ConcurrentHashMap保证线程安全
  • 异步清理避免阻塞主流程
  • 令牌数量可控(仅登出令牌,非所有令牌)

3.4 限流器实现

3.4.1 限流配置
java 复制代码
@Configuration
public class RateLimitConfig {

    /**
     * API通用限流器 - 每秒10个请求
     * 应用场景: 大多数API接口的默认限流
     */
    @Bean
    public RateLimiter apiRateLimiter() {
        return RateLimiter.create(10.0);  // 10 permits per second
    }

    /**
     * 登录接口限流器 - 每秒2个请求
     * 应用场景: 防止暴力破解攻击
     * 安全考量: 降低密码猜测成功率
     */
    @Bean
    public RateLimiter loginRateLimiter() {
        return RateLimiter.create(2.0);  // 2 permits per second
    }

    /**
     * 设备级限流器映射
     * 应用场景: 每个设备独立限流
     * 数据结构: ConcurrentHashMap(线程安全)
     */
    @Bean
    public ConcurrentHashMap<String, RateLimiter> deviceRateLimiters() {
        return new ConcurrentHashMap<>();
    }
}
3.4.2 令牌桶算法原理
复制代码
┌─────────────────────────────────────┐
│         令牌桶 (Token Bucket)        │
├─────────────────────────────────────┤
│  容量: 10 (每秒生成10个令牌)         │
│  当前令牌数: ████████░░ (8/10)      │
│                                     │
│  生成速率: 每100ms生成1个令牌        │
│  ┌──────────────────────────────┐  │
│  │  时间轴:                      │  │
│  │  0ms    100ms   200ms   300ms │  │
│  │   ●      ●      ●      ●     │  │
│  │  (生成)  (生成) (生成) (生成)  │  │
│  └──────────────────────────────┘  │
└─────────────────────────────────────┘

请求到来:
1. 尝试获取令牌 (tryAcquire)
   ├─ 有令牌 → 放行,令牌数-1
   └─ 无令牌 → 拒绝或阻塞

2. 令牌补充 (后台线程)
   每100ms补充1个令牌(平滑速率)

Guava RateLimiter特性:

  • 平滑突发限流 (SmoothBursty): 默认实现,允许突发流量
  • 平滑预热限流 (SmoothWarmingUp): 逐渐达到稳定速率
  • 非阻塞模式 : tryAcquire()立即返回
  • 阻塞模式 : acquire()等待直到获取令牌

使用示例:

java 复制代码
// 非阻塞模式(推荐)
if (apiRateLimiter.tryAcquire()) {
    // 处理请求
} else {
    // 限流,返回429
    throw new RuntimeException("请求过于频繁");
}

// 阻塞模式(不推荐用于Web请求)
apiRateLimiter.acquire();  // 可能等待数秒
// 处理请求

// 带超时的阻塞模式
if (apiRateLimiter.tryAcquire(1, 100, TimeUnit.MILLISECONDS)) {
    // 100ms内获取到令牌
} else {
    // 超时,限流
}
3.4.3 动态限流器管理
java 复制代码
private final Map<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();

private RateLimiter getRateLimiter(RateLimit rateLimit) {
    String key = rateLimit.type();  // 如"login", "device", "api"

    // 计算型获取:如果不存在则创建,存在则返回
    return rateLimiters.computeIfAbsent(key, k -> {
        // 动态创建限流器
        RateLimiter limiter = RateLimiter.create(rateLimit.value());
        logger.info("创建限流器: type={}, rate={}/s", key, rateLimit.value());
        return limiter;
    });
}

// 使用示例
@RateLimit(type = "device-control", value = 5.0)
public void controlDevice(String deviceId, String command) {
    // 每秒最多5个设备控制请求
}

限流策略最佳实践:

  • 全局限流: 防止系统过载(10 req/s)
  • 接口限流: 保护关键接口(登录2 req/s)
  • 用户级限流: 防止恶意用户(基于IP或用户ID)
  • 设备级限流: 保护IoT设备(每个设备5 req/s)
  • 分级限流: 管理员豁免限流

3.5 缓存管理

3.5.1 Spring Cache注解
java 复制代码
@Service
public class DeviceService {

    @Autowired
    private DeviceRepository deviceRepository;

    /**
     * 查询设备列表(带缓存)
     * 缓存名: "devices"
     * 缓存条件: 结果不为null且不为空列表
     */
    @Cacheable(value = "devices", unless = "#result == null || #result.isEmpty()")
    public List<Map<String, Object>> getDevices() {
        List<Device> devices = deviceRepository.findAll();
        // ... 转换为Map列表
        return result;
    }

    /**
     * 查询设备状态(带缓存)
     * 缓存key: deviceId参数
     */
    @Cacheable(value = "deviceStatus", key = "#deviceId")
    public Map<String, Object> getDeviceStatus(String deviceId) {
        Device device = deviceRepository.findById(deviceId).orElse(null);
        // ... 构造状态Map
        return status;
    }

    /**
     * 更新设备状态(清除缓存)
     * allEntries=true: 清除所有缓存条目
     */
    @CacheEvict(value = {"devices", "deviceStatus"}, allEntries = true)
    public void updateDeviceStatus(String deviceId, boolean online) {
        Device device = deviceRepository.findById(deviceId).orElse(null);
        if (device != null) {
            device.setOnline(online);
            deviceRepository.save(device);
        }
    }

    /**
     * 添加设备(清除缓存)
     */
    @CacheEvict(value = "devices", allEntries = true)
    public Device addDevice(Device device) {
        return deviceRepository.save(device);
    }

    /**
     * 条件缓存清除
     * 仅当结果为true时清除缓存
     */
    @CacheEvict(value = "deviceStatus", key = "#deviceId", condition = "#result == true")
    public boolean deleteDevice(String deviceId) {
        if (deviceRepository.existsById(deviceId)) {
            deviceRepository.deleteById(deviceId);
            return true;
        }
        return false;
    }
}
3.5.2 缓存配置
java 复制代码
@Configuration
@EnableCaching
public class CacheConfig {

    /**
     * 配置Caffeine缓存(高性能本地缓存)
     */
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();

        // 设置缓存规范
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(1000)              // 最大条目数
                .expireAfterWrite(10, TimeUnit.MINUTES)  // 写入后10分钟过期
                .expireAfterAccess(5, TimeUnit.MINUTES)  // 访问后5分钟过期
                .recordStats()                  // 记录统计信息
        );

        // 配置不同缓存的过期时间
        cacheManager.setCacheNames(Arrays.asList("devices", "deviceStatus", "users"));

        return cacheManager;
    }

    /**
     * 自定义缓存解析器
     */
    @Bean
    public CacheResolver cacheResolver(CacheManager cacheManager) {
        return new CacheResolver() {
            @Override
            public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
                // 自定义缓存选择逻辑
                return cacheManager.getCacheNames().stream()
                        .map(cacheManager::getCache)
                        .collect(Collectors.toList());
            }
        };
    }
}

缓存策略选择:

  • 本地缓存 (Caffeine): 单机、高性能、低延迟
  • 分布式缓存 (Redis): 集群、共享状态、持久化
  • 多级缓存: 本地 + Redis,兼顾性能和一致性

缓存注意事项:

  • ⚠️ 缓存穿透: 查询不存在的数据,使用布隆过滤器或空值缓存
  • ⚠️ 缓存击穿: 热点key过期,使用互斥锁或永不过期
  • ⚠️ 缓存雪崩: 大量key同时过期,使用随机过期时间

4. 分层架构与业务逻辑

4.1 四层架构详解

复制代码
┌─────────────────────────────────────────────┐
│           Controller 层 (控制器层)            │
│  职责: 接收HTTP请求,参数验证,返回响应       │
│  注解: @RestController, @RequestMapping     │
│  依赖: Service层                             │
├─────────────────────────────────────────────┤
│            Service 层 (业务层)               │
│  职责: 业务逻辑处理,事务管理,服务协调       │
│  注解: @Service, @Transactional             │
│  依赖: Repository层、其他Service             │
├─────────────────────────────────────────────┤
│         Repository 层 (数据访问层)            │
│  职责: 数据库操作,CRUD,自定义查询           │
│  注解: @Repository, @Query                  │
│  依赖: JPA, EntityManager                   │
├─────────────────────────────────────────────┤
│            Entity 层 (实体层)                │
│  职责: 数据模型,ORM映射,业务属性            │
│  注解: @Entity, @Table, @Id                 │
│  依赖: 无(纯POJO)                          │
└─────────────────────────────────────────────┘

4.2 各层职责详解

4.2.1 Controller层

职责边界:

  • ✅ 接收和解析HTTP请求
  • ✅ 参数验证(@Valid)
  • ✅ 调用Service层
  • ✅ 构造HTTP响应(ResponseEntity)
  • ✅ 异常转换(抛出特定异常)
  • ❌ 业务逻辑处理
  • ❌ 数据库操作
  • ❌ 复杂计算

典型Controller:

java 复制代码
@RestController
@RequestMapping("/api/devices")
@Tag(name = "设备管理", description = "设备CRUD和状态管理")
public class DeviceController {

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private ObjectMapper objectMapper;  // JSON处理

    // 查询列表
    @GetMapping
    public List<Map<String, Object>> getDevices() {
        return deviceService.getDevices();
    }

    // 查询详情
    @Operation(summary = "获取设备状态")
    @GetMapping("/{deviceId}/status")
    public ResponseEntity<?> getDeviceStatus(@PathVariable String deviceId) {
        // 1. 调用Service
        Map<String, Object> status = deviceService.getDeviceStatus(deviceId);

        // 2. 构造响应
        return ResponseEntity.ok(status);
    }

    // 创建设备(带限流和审计)
    @RateLimit(value = 2.0, type = "create-device")
    @AuditLog(value = "创建设备", type = "DEVICE")
    @PostMapping
    public ResponseEntity<?> createDevice(@Valid @RequestBody Device device) {
        try {
            // 1. 调用Service
            Device savedDevice = deviceService.addDevice(device);

            // 2. 返回200 OK
            return ResponseEntity.ok(savedDevice);
        } catch (Exception e) {
            // 3. 异常处理
            Map<String, Object> error = Map.of(
                "message", "Failed to create device: " + e.getMessage()
            );
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
        }
    }

    // 更新设备
    @AuditLog(value = "更新设备", type = "DEVICE")
    @PutMapping("/{deviceId}")
    public ResponseEntity<?> updateDevice(
            @PathVariable String deviceId,
            @Valid @RequestBody Device device) {
        // 业务逻辑在Service层
        Device updatedDevice = deviceService.updateDevice(deviceId, device);
        return ResponseEntity.ok(updatedDevice);
    }

    // 删除设备
    @AuditLog(value = "删除设备", type = "DEVICE")
    @DeleteMapping("/{deviceId}")
    public ResponseEntity<?> deleteDevice(@PathVariable String deviceId) {
        deviceService.deleteDevice(deviceId);
        return ResponseEntity.ok(Map.of("message", "Device deleted"));
    }
}

Controller最佳实践:

  • 使用@Valid进行参数校验
  • 使用ResponseEntity明确状态码
  • 使用Swagger注解生成文档
  • 使用AOP注解(@RateLimit, @AuditLog)
  • 异常统一由@RestControllerAdvice处理
4.2.2 Service层

职责边界:

  • ✅ 核心业务逻辑
  • ✅ 事务管理(@Transactional)
  • ✅ 服务协调(调用多个Repository)
  • ✅ 业务规则验证
  • ✅ 异常抛出(业务异常)
  • ❌ HTTP协议相关(Request/Response)
  • ❌ 数据库细节(SQL、连接)
  • ❌ 表示层逻辑(JSON、DTO转换)

典型Service:

java 复制代码
@Service
public class AuthService {

    @Autowired
    private UserAccountRepository userAccountRepository;

    @Autowired
    private JwtUtil jwtUtil;

    @Value("${admin.username:admin}")
    private String adminUsername;

    /**
     * 用户登录(核心业务逻辑)
     */
    @Transactional(readOnly = true)  // 只读事务,优化性能
    public Map<String, Object> login(String account, String password) {
        // 1. 参数规范化
        String normalizedAccount = account.trim();
        String normalizedAdminUsername = adminUsername.trim();
        String normalizedAdminEmail = adminEmail.trim().toLowerCase();

        // 2. 账号验证
        if (!normalizedAdminUsername.equals(normalizedAccount) &&
            !normalizedAdminEmail.equals(normalizedAccount.toLowerCase())) {
            throw new AuthenticationException("Invalid credentials");
        }

        // 3. 查询用户
        UserAccount user = userAccountRepository.findById(normalizedAdminUsername)
                .orElseThrow(() -> new ResourceNotFoundException("User not found"));

        // 4. 密码验证(安全关键点)
        if (!EncryptionUtil.verifyPassword(password, user.getPasswordHash())) {
            throw new AuthenticationException("Invalid credentials");
        }

        // 5. 生成JWT令牌
        String token = jwtUtil.generateToken(user.getUsername(), user.getRole());

        // 6. 构造响应
        return Map.of(
            "token", token,
            "user", Map.of(
                "username", user.getUsername(),
                "email", user.getEmail(),
                "role", user.getRole()
            )
        );
    }

    /**
     * 用户注册(写操作)
     */
    @Transactional  // 读写事务
    public Map<String, Object> register(String username, String email, String password) {
        // 1. 业务规则验证
        if (userAccountRepository.existsById(username)) {
            throw new BusinessException("Username already exists");
        }
        if (userAccountRepository.findByEmail(email).isPresent()) {
            throw new BusinessException("Email already exists");
        }

        // 2. 创建用户实体
        UserAccount user = new UserAccount();
        user.setUsername(username);
        user.setEmail(email);
        user.setPasswordHash(EncryptionUtil.hashPassword(password));
        user.setRole("user");
        user.setCreatedAt(System.currentTimeMillis());
        user.setUpdatedAt(System.currentTimeMillis());

        // 3. 保存到数据库
        userAccountRepository.save(user);

        // 4. 生成JWT
        String token = jwtUtil.generateToken(user.getUsername(), user.getRole());

        return Map.of(
            "token", token,
            "user", Map.of(
                "username", user.getUsername(),
                "email", user.getEmail(),
                "role", user.getRole()
            )
        );
    }

    /**
     * 重置密码(复杂业务流程)
     */
    @Transactional
    public Map<String, Object> resetPassword(String email, String resetCode, String newPassword) {
        // 1. 查询用户
        UserAccount user = userAccountRepository.findByEmail(email)
                .orElseThrow(() -> new ResourceNotFoundException("User not found"));

        // 2. 验证重置码是否过期
        if (user.getResetExpiresAt() < System.currentTimeMillis()) {
            throw new BusinessException("Reset code expired");
        }

        // 3. 验证重置码
        if (!EncryptionUtil.verifyPassword(resetCode, user.getResetCodeHash())) {
            throw new BusinessException("Invalid reset code");
        }

        // 4. 更新密码
        user.setPasswordHash(EncryptionUtil.hashPassword(newPassword));
        user.setResetCodeHash("");
        user.setResetExpiresAt(0);
        user.setUpdatedAt(System.currentTimeMillis());
        userAccountRepository.save(user);

        return Map.of("message", "Password reset successfully");
    }
}

Service层最佳实践:

  • 使用@Transactional管理事务
  • 抛出有意义的业务异常
  • 避免过长的方法(单一职责)
  • 使用领域对象(Entity)而非Map
  • 业务规则集中在Service层
4.2.3 Service层实战案例:MQTT模拟器服务

MQTT模拟器服务(MqttSimulatorService)是一个典型的后台服务示例,展示了如何使用多线程、并发控制和任务管理来处理IoT设备模拟。

核心功能:

  • 启动/停止模拟器任务
  • 模拟设备状态和遥测数据
  • 性能监控和统计
  • 任务历史管理

服务架构:

java 复制代码
@Service
public class MqttSimulatorService {

    private static final Logger log = LoggerFactory.getLogger(MqttSimulatorService.class);

    @Autowired
    private DeviceRepository deviceRepository;

    @Autowired
    private StripStatusRepository stripStatusRepository;

    // 使用固定线程池,限制并发数,避免资源耗尽
    private final ExecutorService executorService = Executors.newFixedThreadPool(
        Math.min(10, Runtime.getRuntime().availableProcessors())
    );

    // 使用ConcurrentHashMap保证线程安全的任务存储
    private final Map<String, SimulatorTask> simulatorTasks = new ConcurrentHashMap<>();
    private final AtomicInteger taskIdGenerator = new AtomicInteger(0);

    // 保留历史任务(最多100个),使用CopyOnWriteArrayList保证线程安全
    private final List<MqttSimulatorStatus> historyTasks = new CopyOnWriteArrayList<>();

    /**
     * 启动MQTT模拟器
     */
    public MqttSimulatorResponse startSimulator(MqttSimulatorRequest request) {
        // 1. 参数验证
        validateRequest(request);

        // 2. 生成唯一任务ID
        String taskId = "simulator_" + taskIdGenerator.incrementAndGet() + "_" + System.currentTimeMillis();

        // 3. 创建任务并存入Map
        SimulatorTask task = new SimulatorTask(taskId, request);
        simulatorTasks.put(taskId, task);

        // 4. 提交任务到线程池(异步执行)
        Future<?> future = executorService.submit(() -> {
            try {
                task.run();
            } catch (Exception e) {
                log.error("模拟器任务执行异常: taskId={}", taskId, e);
                task.setStatus("ERROR");
            } finally {
                // 任务完成后移除并保存到历史
                simulatorTasks.remove(taskId);
                saveToHistory(task);
            }
        });

        return MqttSimulatorResponse.builder()
                .taskId(taskId)
                .status("STARTED")
                .message("MQTT模拟器已启动")
                .build();
    }
}

并发控制要点:

组件 类型 用途
ExecutorService FixedThreadPool 限制并发线程数,防止资源耗尽
ConcurrentHashMap 线程安全Map 存储运行中的任务
CopyOnWriteArrayList 线程安全List 存储历史任务,支持高并发读
AtomicInteger/AtomicLong 原子计数器 统计消息数量,避免竞态条件

任务执行器设计:

java 复制代码
private class SimulatorTask implements Runnable {
    private final String taskId;
    private final int devices;          // 设备数量(1-1000)
    private final int duration;         // 持续时间(秒)
    private final double interval;      // 发送间隔(秒)
    private final String brokerUrl;     // MQTT Broker地址
    private final String messageType;   // STATUS/TELEMETRY/MIXED

    // 线程安全的状态管理
    private volatile boolean running = true;
    private volatile String status = "RUNNING";
    private final AtomicInteger totalMessages = new AtomicInteger(0);
    private final AtomicInteger successMessages = new AtomicInteger(0);
    private final AtomicInteger errorMessages = new AtomicInteger(0);
    private final List<String> deviceIds = new ArrayList<>();

    @Override
    public void run() {
        try {
            startTime.set(System.currentTimeMillis());
            long endTimeMillis = startTime.get() + (duration * 1000L);

            // 模拟发送消息循环
            while (running && System.currentTimeMillis() < endTimeMillis) {
                for (String deviceId : deviceIds) {
                    if (!running) break;

                    // 根据消息类型发送
                    if ("STATUS".equals(messageType)) {
                        sendStatus(deviceId);
                    } else if ("TELEMETRY".equals(messageType)) {
                        sendTelemetry(deviceId);
                    } else { // MIXED
                        if (Math.random() > 0.5) {
                            sendStatus(deviceId);
                        } else {
                            sendTelemetry(deviceId);
                        }
                    }

                    totalMessages.incrementAndGet();
                }

                Thread.sleep((long) (interval * 1000));
            }

            status = "COMPLETED";

        } catch (InterruptedException e) {
            status = "STOPPED";
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            status = "ERROR";
        }
    }
}

参数验证与边界控制:

java 复制代码
private void validateRequest(MqttSimulatorRequest request) {
    // 设备数量限制:1-1000(避免资源耗尽)
    if (request.getDevices() <= 0 || request.getDevices() > 1000) {
        throw new IllegalArgumentException("设备数量必须在 1-1000 之间");
    }
    // 持续时间限制:1秒-24小时
    if (request.getDuration() <= 0 || request.getDuration() > 86400) {
        throw new IllegalArgumentException("持续时间必须在 1-86400 秒之间");
    }
    // 发送间隔限制:10ms-60秒
    if (request.getInterval() < 0.01 || request.getInterval() > 60) {
        throw new IllegalArgumentException("发送间隔必须在 0.01-60 秒之间");
    }
    // 在线率限制:0-100%
    if (request.getOnlineRate() < 0 || request.getOnlineRate() > 1) {
        throw new IllegalArgumentException("在线率必须在 0-1 之间");
    }
}

定时清理历史记录:

java 复制代码
@Scheduled(cron = "0 0 0 * * ?")  // 每天凌晨执行
public void cleanupHistory() {
    synchronized (historyTasks) {
        if (historyTasks.size() > 50) {
            int removeCount = historyTasks.size() - 50;
            for (int i = 0; i < removeCount; i++) {
                historyTasks.remove(0);
            }
            log.info("已清理 {} 条历史记录", removeCount);
        }
    }
}

设计亮点:

  • 线程安全 : 使用ConcurrentHashMapAtomicIntegervolatile保证多线程安全
  • 资源控制: 固定线程池 + 设备数量限制,防止资源耗尽
  • 优雅停止 : 使用volatile boolean标志位实现可控停止
  • 历史管理: 自动清理过期记录,控制内存占用
  • 异常处理: 完善的异常捕获和状态更新
4.2.4 Repository层

职责边界:

  • ✅ 数据库CRUD操作
  • ✅ 自定义查询(JPQL、Native SQL)
  • ✅ 分页、排序
  • ✅ 数据映射(ResultSet → Entity)
  • ❌ 业务逻辑
  • ❌ 事务管理(由Service层控制)
  • ❌ 复杂计算

典型Repository:

java 复制代码
@Repository
public interface DeviceRepository extends JpaRepository<Device, String> {

    // 1. 方法名查询(Spring Data JPA自动生成实现)

    // 根据房间查询设备
    List<Device> findByRoom(String room);

    // 根据房间和在线状态查询
    List<Device> findByRoomAndOnline(String room, boolean online);

    // 检查设备是否存在
    boolean existsById(String deviceId);

    // 统计房间设备数量
    long countByRoom(String room);

    // 2. @Query注解(自定义JPQL)

    // JPQL查询(面向对象)
    @Query("SELECT d FROM Device d WHERE d.room = :room AND d.online = true")
    List<Device> findOnlineDevicesByRoom(@Param("room") String room);

    // 原生SQL查询
    @Query(value = "SELECT * FROM device WHERE room = ?1 AND online = true", nativeQuery = true)
    List<Device> findOnlineDevicesNative(String room);

    // 聚合查询
    @Query("SELECT COUNT(d) FROM Device d WHERE d.room = :room")
    long countDevicesInRoom(@Param("room") String room);

    // 3. 排序和分页

    // 按创建时间降序
    List<Device> findByRoomOrderByCreatedAtDesc(String room);

    // 分页查询
    Page<Device> findByRoom(String room, Pageable pageable);

    // 排序查询
    List<Device> findByRoom(String room, Sort sort);

    // 4. 批量操作

    // 批量删除
    void deleteAllByIdIn(List<String> deviceIds);

    // 批量更新在线状态
    @Modifying
    @Query("UPDATE Device d SET d.online = :online WHERE d.id IN :deviceIds")
    void updateOnlineStatusForDevices(@Param("deviceIds") List<String> deviceIds,
                                       @Param("online") boolean online);
}

Repository查询方法命名规则:

前缀 说明 示例
find...By 查询 findByRoom, findByRoomAndOnline
exists...By 是否存在 existsById
count...By 统计数量 countByRoom
delete...By 删除 deleteByRoom
...OrderBy... 排序 findByRoomOrderByCreatedAtDesc

属性操作符:

操作符 说明 示例
And 逻辑与 findByRoomAndOnline
Or 逻辑或 findByRoomOrBuilding
Is, Equals 等于 findByRoomIs, findByRoomEquals
Between 范围 findByCreatedAtBetween
GreaterThan 大于 findByLastSeenTsGreaterThan
Like 模糊匹配 findByNameLike
IsNull, IsNotNull 空值判断 findByEmailIsNull
In 包含 findByIdIn
4.2.5 Entity层

职责边界:

  • ✅ 数据模型定义
  • ✅ ORM映射(@Entity, @Table)
  • ✅ 业务属性
  • ✅ 简单的getter/setter
  • ❌ 业务逻辑
  • ❌ 数据库操作
  • ❌ 复杂计算

典型Entity:

java 复制代码
@Entity
@Table(name = "device")
@Data  // Lombok注解,自动生成getter/setter/toString等
@NoArgsConstructor
@AllArgsConstructor
public class Device {

    @Id
    @Column(name = "id", length = 64)
    private String id;  // 设备唯一标识

    @Column(name = "name", length = 128, nullable = false)
    private String name;  // 设备名称

    @Column(name = "room", length = 64, nullable = false)
    private String room;  // 房间号

    @Column(name = "online")
    private boolean online = false;  // 在线状态

    @Column(name = "last_seen_ts")
    private long lastSeenTs = 0L;  // 最后心跳时间戳(秒)

    @Column(name = "created_at")
    private long createdAt = 0L;  // 创建时间

    @Column(name = "updated_at")
    private long updatedAt = 0L;  // 更新时间

    /**
     * 更新最后心跳时间
     */
    public void updateLastSeen() {
        this.lastSeenTs = System.currentTimeMillis() / 1000;
    }

    /**
     * 检查是否在线(基于心跳超时)
     */
    public boolean isReallyOnline() {
        long now = System.currentTimeMillis() / 1000;
        long timeout = 60L;  // 60秒超时
        return this.online && (now - this.lastSeenTs) < timeout;
    }
}

JPA注解详解:

注解 说明 示例
@Entity 标记为JPA实体 @Entity public class Device
@Table 指定表名 @Table(name = "device")
@Id 主键 @Id private String id
@GeneratedValue 主键生成策略 @GeneratedValue(strategy = AUTO)
@Column 列映射 @Column(name = "room", length = 64)
@Transient 非持久化字段 @Transient private String tempData
@Enumerated 枚举映射 @Enumerated(EnumType.STRING) private Status status
@Temporal 日期映射 @Temporal(TemporalType.TIMESTAMP) private Date createdAt
@Lob 大对象 @Lob private String description
@Version 乐观锁 @Version private int version

5. 关键设计模式与最佳实践

5.1 依赖注入 (DI) 模式

5.1.1 三种注入方式对比
java 复制代码
@Service
public class UserService {

    // 方式1: 字段注入(不推荐)
    @Autowired
    private UserRepository userRepository;

    // 方式2: Setter注入(适用于可选依赖)
    private EmailService emailService;

    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }

    // 方式3: 构造器注入(推荐⭐)
    private final JwtUtil jwtUtil;
    private final PasswordEncoder passwordEncoder;

    @Autowired  // Spring 4.3+ 可省略
    public UserService(JwtUtil jwtUtil, PasswordEncoder passwordEncoder) {
        this.jwtUtil = jwtUtil;
        this.passwordEncoder = passwordEncoder;
    }
}

注入方式选择:

方式 优点 缺点 适用场景
构造器注入 不可变、易于测试、强制依赖 代码稍长 主要依赖(推荐⭐)
Setter注入 可选依赖、灵活性高 可变、依赖可为null 可选依赖
字段注入 简洁 不易测试、隐藏依赖 简单场景(不推荐)
5.1.2 循环依赖解决方案
java 复制代码
// 问题: UserService → EmailService → UserService(循环)
@Service
public class UserService {
    @Autowired
    private EmailService emailService;
}

@Service
public class EmailService {
    @Autowired
    private UserService userService;  // 循环依赖
}

// 解决方案1: 使用Setter注入(延迟注入)
@Service
public class EmailService {
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

// 解决方案2: 使用@Lazy注解(延迟初始化)
@Service
public class EmailService {
    @Autowired
    @Lazy
    private UserService userService;
}

// 解决方案3: 重构设计(最佳)
// 提取公共服务,打破循环
@Service
public class NotificationService {
    // 处理通知逻辑
}

@Service
public class UserService {
    @Autowired
    private NotificationService notificationService;
}

@Service
public class EmailService {
    @Autowired
    private NotificationService notificationService;
}

5.2 Repository模式

java 复制代码
// 1. 定义Repository接口
public interface DeviceRepository extends JpaRepository<Device, String> {
    List<Device> findByRoom(String room);
}

// 2. Spring Data JPA自动生成实现类
// SimpleDeviceRepository implements DeviceRepository

// 3. 使用Repository
@Service
public class DeviceService {
    @Autowired
    private DeviceRepository deviceRepository;  // 注入实现类

    public List<Device> getDevicesByRoom(String room) {
        return deviceRepository.findByRoom(room);  // 调用自动生成的方法
    }
}

Repository模式优势:

  • ✅ 数据访问抽象(不暴露JPA细节)
  • ✅ 易于测试(可Mock)
  • ✅ 统一的数据访问接口
  • ✅ 自动实现CRUD方法

5.3 工厂模式

java 复制代码
// 设备工厂
@Component
public class DeviceFactory {

    /**
     * 创建设备
     */
    public Device createDevice(String id, String name, String room) {
        Device device = new Device();
        device.setId(id);
        device.setName(name);
        device.setRoom(room);
        device.setOnline(false);
        device.setLastSeenTs(System.currentTimeMillis() / 1000);
        device.setCreatedAt(System.currentTimeMillis() / 1000);
        device.setUpdatedAt(System.currentTimeMillis() / 1000);
        return device;
    }

    /**
     * 从DTO创建设备
     */
    public Device createDeviceFromDto(DeviceDto dto) {
        Device device = createDevice(dto.getId(), dto.getName(), dto.getRoom());
        // 额外的DTO到Entity转换逻辑
        return device;
    }
}

// 使用
@Service
public class DeviceService {
    @Autowired
    private DeviceFactory deviceFactory;

    public Device addDevice(DeviceDto dto) {
        Device device = deviceFactory.createDeviceFromDto(dto);
        return deviceRepository.save(device);
    }
}

5.4 策略模式

java 复制代码
// 1. 定义策略接口
public interface PowerCalculationStrategy {
    double calculatePower(List<Telemetry> telemetries);
}

// 2. 实现具体策略
@Component
@Qualifier("averageStrategy")
public class AveragePowerStrategy implements PowerCalculationStrategy {
    @Override
    public double calculatePower(List<Telemetry> telemetries) {
        return telemetries.stream()
                .mapToDouble(Telemetry::getPowerW)
                .average()
                .orElse(0.0);
    }
}

@Component
@Qualifier("peakStrategy")
public class PeakPowerStrategy implements PowerCalculationStrategy {
    @Override
    public double calculatePower(List<Telemetry> telemetries) {
        return telemetries.stream()
                .mapToDouble(Telemetry::getPowerW)
                .max()
                .orElse(0.0);
    }
}

// 3. 策略上下文
@Service
public class PowerCalculationService {

    private final Map<String, PowerCalculationStrategy> strategies;

    @Autowired
    public PowerCalculationService(
            @Qualifier("averageStrategy") PowerCalculationStrategy averageStrategy,
            @Qualifier("peakStrategy") PowerCalculationStrategy peakStrategy) {
        this.strategies = Map.of(
            "average", averageStrategy,
            "peak", peakStrategy
        );
    }

    public double calculatePower(String strategyType, List<Telemetry> telemetries) {
        PowerCalculationStrategy strategy = strategies.get(strategyType);
        if (strategy == null) {
            throw new IllegalArgumentException("Unknown strategy: " + strategyType);
        }
        return strategy.calculatePower(telemetries);
    }
}

// 4. 使用
@Service
public class AiReportService {
    @Autowired
    private PowerCalculationService powerCalculationService;

    public Map<String, Object> getAiReport(String roomId, String period) {
        List<Telemetry> data = telemetryRepository.findByRoomAndPeriod(roomId, period);

        // 根据需求选择策略
        double avgPower = powerCalculationService.calculatePower("average", data);
        double peakPower = powerCalculationService.calculatePower("peak", data);

        return Map.of(
            "averagePower", avgPower,
            "peakPower", peakPower
        );
    }
}

6. 安全机制实现详解

6.1 认证安全

6.1.1 JWT令牌安全
java 复制代码
@Component
public class JwtUtil {

    @Value("${security.jwt.secret}")
    private String secret;  // 必须足够长且随机(至少32字节)

    @Value("${security.jwt.expiration:86400000}")
    private long expiration;  // 24小时

    /**
     * 生成安全的JWT令牌
     */
    public String generateToken(String username, String role) {
        // 1. 使用足够长的密钥(建议32字节以上)
        // secret = "your-very-long-and-random-secret-key-change-in-production"

        // 2. 设置合理的过期时间(24小时)
        Date expiryDate = new Date(System.currentTimeMillis() + expiration);

        // 3. 添加必要的声明
        return Jwts.builder()
                .setSubject(username)
                .claim("username", username)
                .claim("role", role)
                .claim("iat", System.currentTimeMillis() / 1000)  // 签发时间
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .setId(UUID.randomUUID().toString())  // 唯一ID,用于吊销
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 验证令牌(多层检查)
     */
    public boolean validateToken(String token) {
        try {
            // 1. 黑名单检查
            if (tokenBlacklist.isBlacklisted(token)) {
                return false;
            }

            // 2. 签名验证 + 过期检查
            Jws<Claims> claims = Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())
                    .build()
                    .parseClaimsJws(token);

            // 3. 其他自定义检查(如issuer、audience)
            // Claims claimsBody = claims.getBody();

            return true;
        } catch (ExpiredJwtException e) {
            // 令牌过期
            return false;
        } catch (UnsupportedJwtException e) {
            // 不支持的令牌格式
            return false;
        } catch (MalformedJwtException e) {
            // 令牌格式错误
            return false;
        } catch (SignatureException e) {
            // 签名验证失败
            return false;
        } catch (IllegalArgumentException e) {
            // 令牌为空或无效
            return false;
        }
    }

    private SecretKey getSigningKey() {
        // Base64解码密钥
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}

JWT安全最佳实践:

  • ✅ 使用足够长的密钥(至少32字节,推荐64字节)
  • ✅ 设置合理的过期时间(access token 24小时,refresh token 7天)
  • ✅ 使用黑名单实现即时吊销
  • ✅ 添加唯一ID(jti claim)用于追踪
  • ✅ 使用HTTPS传输(防止中间人攻击)
  • ✅ 不在令牌中存储敏感信息(密码、身份证号等)
6.1.2 密码安全
java 复制代码
public class EncryptionUtil {

    // PBKDF2参数
    private static final String ALGORITHM = "PBKDF2WithHmacSHA256";
    private static final int ITERATIONS = 160000;  // 迭代次数
    private static final int KEY_LENGTH = 256;      // 密钥长度(位)
    private static final int SALT_LENGTH = 16;      // 盐值长度(字节)

    /**
     * 哈希密码(注册/修改密码时使用)
     */
    public static String hashPassword(String password) {
        // 1. 生成随机盐值
        byte[] salt = new byte[SALT_LENGTH];
        new SecureRandom().nextBytes(salt);
        String saltHex = HexFormat.of().formatHex(salt);

        // 2. 使用PBKDF2哈希密码
        String hashHex = hashPassword(password, saltHex, ITERATIONS);

        // 3. 格式化存储: algorithm$iterations$salt$hash
        return String.format("%s$%d$%s$%s",
                ALGORITHM, ITERATIONS, saltHex, hashHex);
    }

    /**
     * 验证密码(登录时使用)
     */
    public static boolean verifyPassword(String password, String encoded) {
        // 1. 拆分存储的哈希字符串
        String[] parts = encoded.split("\\$");
        if (parts.length != 4) {
            return false;
        }

        String algorithm = parts[0];
        int iterations = Integer.parseInt(parts[1]);
        String salt = parts[2];
        String expectedHash = parts[3];

        // 2. 使用相同的参数重新哈希输入密码
        String actualHash = hashPassword(password, salt, iterations);

        // 3. 恒定时间比较(防止时序攻击)
        return constantTimeEquals(expectedHash, actualHash);
    }

    /**
     * PBKDF2哈希实现
     */
    private static String hashPassword(String password, String saltHex, int iterations) {
        try {
            // 1. 解析盐值
            byte[] salt = HexFormat.of().parseHex(saltHex);

            // 2. 创建PBEKeySpec
            PBEKeySpec spec = new PBEKeySpec(
                    password.toCharArray(),  // 密码字符数组
                    salt,                   // 盐值
                    iterations,             // 迭代次数
                    KEY_LENGTH              // 密钥长度
            );

            // 3. 生成密钥
            SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
            byte[] hash = factory.generateSecret(spec).getEncoded();

            // 4. 转换为16进制字符串
            return HexFormat.of().formatHex(hash);

        } catch (Exception e) {
            throw new RuntimeException("Password hashing failed", e);
        }
    }

    /**
     * 恒定时间比较(防止时序攻击)
     */
    private static boolean constantTimeEquals(String a, String b) {
        // 1. 长度不同直接返回false(避免长度泄露)
        if (a.length() != b.length()) {
            return false;
        }

        // 2. 按位异或比较(恒定时间)
        int result = 0;
        for (int i = 0; i < a.length(); i++) {
            result |= a.charAt(i) ^ b.charAt(i);  // 异或运算
        }

        // 3. 返回比较结果
        return result == 0;
    }
}

密码安全特性:

  • 盐值 (Salt): 16字节随机数,每个用户独立,防止彩虹表攻击
  • 迭代次数: 160000次,增加破解成本(约0.1秒/次)
  • 算法: PBKDF2WithHmacSHA256,NIST推荐的密钥派生函数
  • 恒定时间比较: 防止时序攻击(timing attack)
  • 存储格式 : algorithm$iterations$salt$hash,便于升级算法

密码策略建议:

  • 最小长度: 8字符
  • 复杂度要求: 大小写字母+数字+特殊字符
  • 禁止常见密码: 123456, password, admin123等
  • 定期更换: 90天
  • 登录失败锁定: 5次失败后锁定30分钟

6.2 授权安全

6.2.1 RBAC权限模型
java 复制代码
// 角色实体
@Entity
@Table(name = "role")
public class Role {
    @Id
    private String code;  // 角色代码(如ADMIN, USER)

    private String name;  // 角色名称

    private boolean enabled;  // 是否启用

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "role_permission",
        joinColumns = @JoinColumn(name = "role_code"),
        inverseJoinColumns = @JoinColumn(name = "permission_code")
    )
    private Set<Permission> permissions;  // 角色拥有的权限
}

// 权限实体
@Entity
@Table(name = "permission")
public class Permission {
    @Id
    private String code;  // 权限代码(如device:read, device:write)

    private String name;  // 权限名称

    private String description;  // 权限描述

    private boolean enabled;  // 是否启用
}

// 用户角色关联
@Entity
@Table(name = "user_role")
@IdClass(UserRoleId.class)
public class UserRole {
    @Id
    private String username;

    @Id
    private String roleCode;

    private long assignedAt;  // 分配时间
}

权限层级:

复制代码
用户 (User)
  ↓ (多对多)
角色 (Role)
  ↓ (多对多)
权限 (Permission)
  ↓
资源操作 (device:read, device:write, user:admin)

权限设计原则:

  • 最小权限原则: 用户只拥有完成工作所需的最小权限
  • 职责分离: 管理员和普通用户权限分离
  • 权限继承: 子角色继承父角色权限
  • 动态授权: 运行时检查权限,而非硬编码
6.2.2 方法级权限控制
java 复制代码
@Service
public class DeviceService {

    @Autowired
    private DeviceRepository deviceRepository;

    // 需要ADMIN角色
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteDevice(String deviceId) {
        deviceRepository.deleteById(deviceId);
    }

    // 需要device:write权限
    @PreAuthorize("hasAuthority('device:write')")
    public Device updateDevice(Device device) {
        return deviceRepository.save(device);
    }

    // 自定义SpEL表达式:设备所有者或管理员
    @PreAuthorize("#deviceId == authentication.name or hasRole('ADMIN')")
    public Device getDevice(String deviceId) {
        return deviceRepository.findById(deviceId).orElse(null);
    }

    // 后置过滤:只返回当前用户拥有的设备
    @PostFilter("filterObject.owner == authentication.name")
    public List<Device> getAllDevices() {
        return deviceRepository.findAll();
    }

    // 后置授权:检查返回值
    @PostAuthorize("returnObject.owner == authentication.name or hasRole('ADMIN')")
    public Device getDeviceDetails(String deviceId) {
        return deviceRepository.findById(deviceId).orElse(null);
    }
}

6.3 API安全

6.3.1 限流防护
java 复制代码
// 全局限流(10 req/s)
if (!apiRateLimiter.tryAcquire()) {
    throw new RuntimeException("请求过于频繁");
}

// 接口级限流(登录2 req/s)
@RateLimit(value = 2.0, type = "login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
    // ...
}

// 用户级限流(基于IP)
String ip = getClientIp(request);
RateLimiter userLimiter = userLimiters.computeIfAbsent(ip,
    k -> RateLimiter.create(5.0));
if (!userLimiter.tryAcquire()) {
    throw new RuntimeException("请求过于频繁");
}

限流策略:

  • 全局限流: 防止系统过载(10 req/s)
  • 接口限流: 保护关键接口(登录2 req/s)
  • 用户级限流: 防止恶意用户(基于IP,5 req/s)
  • 设备级限流: 保护IoT设备(每个设备5 req/s)
6.3.2 CORS安全
java 复制代码
@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();

    // ⚠️ 生产环境不应使用"*",应指定具体域名
    config.setAllowedOrigins(Arrays.asList("https://yourdomain.com"));

    config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
    config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
    config.setAllowCredentials(true);  // 允许携带Cookie
    config.setMaxAge(3600L);  // 预检请求缓存1小时

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);

    return source;
}

CORS安全建议:

  • ❌ 禁止在生产环境使用allowedOrigins("*")
  • ✅ 明确指定允许的来源域名
  • ✅ 限制允许的HTTP方法
  • ✅ 限制允许的请求头
  • ✅ 合理设置预检请求缓存时间
6.3.3 参数验证
java 复制代码
// DTO验证
@Data
public class LoginRequest {
    @NotBlank(message = "账号不能为空")
    @Size(min = 3, max = 50, message = "账号长度必须在3-50之间")
    private String account;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 100, message = "密码长度必须在6-100之间")
    private String password;
}

// Controller中使用@Valid
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
    // 参数验证失败会抛出MethodArgumentNotValidException
    // 由@ExceptionHandler统一处理
}

常用验证注解:

注解 说明 示例
@NotNull 不能为null @NotNull private String name
@NotBlank 不能为null或空字符串 @NotBlank private String account
@Size 长度限制 @Size(min=6, max=100) private String password
@Email 邮箱格式 @Email private String email
@Pattern 正则匹配 @Pattern(regexp="^1[3-9]\\d{9}$") private String phone
@Min, @Max 数值范围 @Min(1) @Max(100) private int age

7. 性能优化策略

7.1 缓存优化

7.1.1 多级缓存
java 复制代码
@Service
public class DeviceService {

    @Autowired
    private DeviceRepository deviceRepository;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // L1: 本地缓存(Caffeine)
    private final Cache<String, Device> localCache = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();

    // L2: 分布式缓存(Redis)
    private static final String REDIS_PREFIX = "device:";

    /**
     * 获取设备(多级缓存)
     */
    public Device getDevice(String deviceId) {
        // 1. 先查本地缓存
        Device device = localCache.getIfPresent(deviceId);
        if (device != null) {
            return device;
        }

        // 2. 再查Redis缓存
        String redisKey = REDIS_PREFIX + deviceId;
        device = (Device) redisTemplate.opsForValue().get(redisKey);
        if (device != null) {
            // 回填本地缓存
            localCache.put(deviceId, device);
            return device;
        }

        // 3. 最后查数据库
        device = deviceRepository.findById(deviceId)
                .orElseThrow(() -> new ResourceNotFoundException("Device not found"));

        // 4. 写入缓存
        localCache.put(deviceId, device);
        redisTemplate.opsForValue().set(redisKey, device, 10, TimeUnit.MINUTES);

        return device;
    }

    /**
     * 更新设备(清除缓存)
     */
    @CacheEvict(value = "devices", key = "#deviceId")
    public Device updateDevice(String deviceId, Device device) {
        // 1. 更新数据库
        Device updatedDevice = deviceRepository.save(device);

        // 2. 清除本地缓存
        localCache.invalidate(deviceId);

        // 3. 清除Redis缓存
        String redisKey = REDIS_PREFIX + deviceId;
        redisTemplate.delete(redisKey);

        return updatedDevice;
    }
}

缓存策略选择:

  • 本地缓存 (Caffeine): 单机、高性能(微秒级)、低延迟
  • 分布式缓存 (Redis): 集群、共享状态、持久化(毫秒级)
  • 多级缓存: 本地 + Redis,兼顾性能和一致性
7.1.2 缓存预热
java 复制代码
@Component
public class CacheWarmupRunner implements CommandLineRunner {

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private CacheManager cacheManager;

    @Override
    public void run(String... args) {
        // 应用启动时预热热点数据
        CompletableFuture.runAsync(() -> {
            try {
                // 预热设备列表缓存
                deviceService.getDevices();
                logger.info("设备列表缓存预热完成");
            } catch (Exception e) {
                logger.error("缓存预热失败", e);
            }
        });
    }
}

7.2 异步处理

7.2.1 @Async异步方法
java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        // 核心线程数(CPU核心数)
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());

        // 最大线程数
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);

        // 队列容量
        executor.setQueueCapacity(100);

        // 线程名前缀
        executor.setThreadNamePrefix("async-");

        // 等待所有任务完成后再关闭
        executor.setWaitForTasksToCompleteOnShutdown(true);

        // 等待时间(秒)
        executor.setAwaitTerminationSeconds(60);

        executor.initialize();
        return executor;
    }
}

@Service
public class NotificationService {

    @Async("taskExecutor")  // 使用指定的线程池
    public CompletableFuture<Void> sendEmailAsync(String email, String subject, String content) {
        try {
            // 发送邮件(耗时操作)
            mailSender.send(createEmail(email, subject, content));
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            // 异步方法中的异常需要特殊处理
            CompletableFuture<Void> future = new CompletableFuture<>();
            future.completeExceptionally(e);
            return future;
        }
    }

    @Async
    public void sendSmsAsync(String phone, String message) {
        // 发送短信(耗时操作)
        smsService.send(phone, message);
    }
}

@Service
public class UserService {

    @Autowired
    private NotificationService notificationService;

    @Transactional
    public User registerUser(UserRegistrationDto dto) {
        // 1. 保存用户(同步)
        User user = userRepository.save(convertToEntity(dto));

        // 2. 发送欢迎邮件(异步,不阻塞)
        notificationService.sendEmailAsync(
            user.getEmail(),
            "欢迎注册",
            "感谢注册我们的系统"
        );

        // 3. 发送短信验证码(异步)
        notificationService.sendSmsAsync(
            user.getPhone(),
            "您的验证码是: 123456"
        );

        return user;
        // 方法立即返回,邮件和短信在后台发送
    }
}

异步方法注意事项:

  • ✅ 适用于I/O密集型操作(邮件、短信、HTTP请求)
  • ✅ 不适用于CPU密集型操作(大量计算)
  • ⚠️ 异步方法不能调用同一类中的其他方法(代理限制)
  • ⚠️ 事务不会传播到异步方法(@Transactional失效)
  • ⚠️ 异常处理需要特殊处理(CompletableFuture.exceptionally)
7.2.2 CompletableFuture链式调用
java 复制代码
@Service
public class ReportService {

    @Autowired
    private DeviceRepository deviceRepository;

    @Autowired
    private TelemetryRepository telemetryRepository;

    /**
     * 异步生成报告
     */
    public CompletableFuture<Report> generateReportAsync(String roomId) {
        // 1. 异步查询设备列表
        CompletableFuture<List<Device>> devicesFuture =
            CompletableFuture.supplyAsync(() ->
                deviceRepository.findByRoom(roomId)
            );

        // 2. 异步查询遥测数据
        CompletableFuture<List<Telemetry>> telemetryFuture =
            CompletableFuture.supplyAsync(() ->
                telemetryRepository.findByRoomAndLast24Hours(roomId)
            );

        // 3. 等待所有异步任务完成,然后组合结果
        return devicesFuture.thenCombine(telemetryFuture, (devices, telemetries) -> {
            // 4. 生成报告(在前两个任务完成后执行)
            return buildReport(roomId, devices, telemetries);
        }).exceptionally(ex -> {
            // 5. 异常处理
            logger.error("生成报告失败", ex);
            return createErrorReport(roomId, ex.getMessage());
        });
    }

    /**
     * 并行处理多个房间的报告
     */
    public CompletableFuture<Map<String, Report>> generateReportsForRooms(List<String> roomIds) {
        // 1. 为每个房间创建异步任务
        List<CompletableFuture<Map.Entry<String, Report>>> futures = roomIds.stream()
            .map(roomId -> generateReportAsync(roomId)
                .thenApply(report -> Map.entry(roomId, report))
            )
            .collect(Collectors.toList());

        // 2. 等待所有任务完成
        CompletableFuture<Void> allOf = CompletableFuture.allOf(
            futures.toArray(new CompletableFuture[0])
        );

        // 3. 组合结果
        return allOf.thenApply(v ->
            futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
        );
    }
}

CompletableFuture方法:

方法 说明 示例
supplyAsync() 异步执行有返回值的任务 CompletableFuture.supplyAsync(() -> query())
runAsync() 异步执行无返回值的任务 CompletableFuture.runAsync(() -> sendEmail())
thenApply() 转换结果 future.thenApply(result -> process(result))
thenAccept() 消费结果 future.thenAccept(result -> log(result))
thenCompose() 链式调用 future.thenCompose(result -> nextAsync(result))
thenCombine() 组合两个Future f1.thenCombine(f2, (r1, r2) -> combine(r1, r2))
allOf() 等待所有Future完成 CompletableFuture.allOf(f1, f2, f3)
anyOf() 等待任一Future完成 CompletableFuture.anyOf(f1, f2, f3)
exceptionally() 异常处理 future.exceptionally(ex -> handleError(ex))

7.3 批量操作优化

java 复制代码
@Service
public class DeviceService {

    @Autowired
    private DeviceRepository deviceRepository;

    /**
     * 批量插入设备(优化前)
     */
    public void createDevicesOld(List<Device> devices) {
        // ❌ 每次插入都执行一次SQL,N次往返
        for (Device device : devices) {
            deviceRepository.save(device);
        }
    }

    /**
     * 批量插入设备(优化后)
     */
    @Transactional
    public void createDevices(List<Device> devices) {
        // ✅ 批量插入,1次SQL
        deviceRepository.saveAll(devices);
    }

    /**
     * 批量更新设备状态
     */
    @Transactional
    public void updateDeviceStatusBatch(List<String> deviceIds, boolean online) {
        // ✅ 批量更新,1次SQL
        deviceRepository.updateOnlineStatusForDevices(deviceIds, online);
    }

    /**
     * 批量删除设备
     */
    @Transactional
    public void deleteDevicesBatch(List<String> deviceIds) {
        // ✅ 批量删除,1次SQL
        deviceRepository.deleteAllByIdIn(deviceIds);
    }

    /**
     * 批量查询设备(IN查询)
     */
    public List<Device> getDevicesBatch(List<String> deviceIds) {
        // ✅ 1次SQL查询多个设备
        return deviceRepository.findAllById(deviceIds);
    }
}

批量操作性能对比:

操作 逐条执行 批量执行 性能提升
插入100条 100次SQL 1次SQL 50-100倍
更新100条 100次SQL 1次SQL 50-100倍
删除100条 100次SQL 1次SQL 50-100倍
查询100条 100次SQL 1次SQL 10-50倍

7.4 数据库优化

7.4.1 索引优化
java 复制代码
@Entity
@Table(name = "telemetry")
@org.hibernate.annotations.Table(
    indexes = {
        @Index(name = "idx_device_id", columnList = "device_id"),
        @Index(name = "idx_ts", columnList = "ts"),
        @Index(name = "idx_device_id_ts", columnList = "device_id, ts")
    }
)
public class Telemetry {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "device_id", nullable = false)
    private String deviceId;

    @Column(name = "ts", nullable = false)
    private long ts;  // 时间戳(秒)

    @Column(name = "power_w")
    private double powerW;

    // ...
}

索引设计原则:

  • ✅ 为主键自动创建索引
  • ✅ 为外键创建索引
  • ✅ 为WHERE条件中的列创建索引
  • ✅ 为ORDER BY、GROUP BY中的列创建索引
  • ✅ 使用复合索引(最左前缀原则)
  • ❌ 避免过多索引(影响写性能)
  • ❌ 避免在低选择性列上创建索引(如性别)
7.4.2 分页查询
java 复制代码
@Repository
public interface TelemetryRepository extends JpaRepository<Telemetry, Long> {

    // 分页查询(避免OOM)
    @Query("SELECT t FROM Telemetry t WHERE t.deviceId = :deviceId ORDER BY t.ts DESC")
    Page<Telemetry> findByDeviceId(@Param("deviceId") String deviceId, Pageable pageable);

    // 游标分页(大数据量)
    @Query("SELECT t FROM Telemetry t WHERE t.deviceId = :deviceId AND t.id > :lastId ORDER BY t.id ASC")
    List<Telemetry> findByDeviceIdWithCursor(
        @Param("deviceId") String deviceId,
        @Param("lastId") Long lastId,
        Pageable pageable
    );
}

@Service
public class TelemetryService {

    /**
     * 分页查询遥测数据
     */
    public Page<Telemetry> getTelemetryByDevice(String deviceId, int page, int size) {
        // ✅ 使用Pageable,避免一次性加载大量数据
        Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "ts"));
        return telemetryRepository.findByDeviceId(deviceId, pageable);
    }

    /**
     * 游标分页(大数据量场景)
     */
    public List<Telemetry> getTelemetryWithCursor(String deviceId, Long lastId, int limit) {
        // ✅ 基于ID的游标分页,性能更好
        Pageable pageable = PageRequest.of(0, limit);
        return telemetryRepository.findByDeviceIdWithCursor(deviceId, lastId, pageable);
    }

    /**
     * 流式处理(超大数据量)
     */
    @Transactional(readOnly = true)
    public void processTelemetryStream(String deviceId, Consumer<Telemetry> processor) {
        // ✅ 使用Stream,逐条处理,避免内存溢出
        try (Stream<Telemetry> stream = telemetryRepository.findByDeviceIdAsStream(deviceId)) {
            stream.forEach(processor);
        }
    }
}

分页策略选择:

场景 策略 优点 缺点
小数据量 OFFSET/LIMIT 简单 深度分页性能差
大数据量 游标分页 性能好 需要有序列字段
超大数据量 流式处理 内存占用低 不能随机访问

8. 核心语法与Java 21特性

8.1 Java 21新特性

Java 21 是 LTS(长期支持)版本,引入了多项革命性特性,特别是虚拟线程对高并发IoT应用意义重大。

8.1.1 虚拟线程(Virtual Threads)⭐最重要

虚拟线程是 Java 21 最具革命性的特性,特别适合本项目的 IoT 设备高并发场景。

java 复制代码
// 传统线程(平台线程):每个线程对应一个OS线程,资源消耗大
// 适合:计算密集型任务
Thread platformThread = new Thread(() -> {
    processDeviceData(deviceId);  // 阻塞操作会占用OS线程
});
platformThread.start();  // 创建10000个线程会耗尽内存

// 虚拟线程:轻量级,由JVM调度,不绑定OS线程
// 适合:I/O密集型任务(HTTP请求、数据库查询、MQTT通信)
Thread virtualThread = Thread.ofVirtual().start(() -> {
    processDeviceData(deviceId);  // 阻塞时自动让出载体线程
});
// 可以轻松创建数百万个虚拟线程!

// 使用虚拟线程执行器(推荐方式)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    // 为每个设备创建一个虚拟线程处理
    for (String deviceId : deviceIds) {
        executor.submit(() -> {
            // I/O操作:MQTT消息发送、数据库查询、HTTP调用
            sendMqttMessage(deviceId, message);
            DeviceStatus status = queryDeviceStatus(deviceId);
            saveToDatabase(status);
            return null;
        });
    }
}  // 自动关闭,等待所有任务完成

虚拟线程 vs 平台线程对比:

特性 平台线程 虚拟线程
内存占用 ~1MB/线程 ~1KB/虚拟线程
创建成本 昂贵(OS系统调用) 廉价(普通Java对象)
最大数量 几千个(受内存限制) 数百万个
调度 OS调度 JVM调度
阻塞代价 占用OS线程 自动让出载体线程
适用场景 CPU密集型 I/O密集型

本项目应用场景:

java 复制代码
// MqttSimulatorService 中使用虚拟线程优化
// 优化前:使用固定线程池,限制并发数
private final ExecutorService executorService = Executors.newFixedThreadPool(10);

// 优化后:使用虚拟线程,无需限制并发数
private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();

// 为每个设备创建独立的虚拟线程
public void simulateDevices(List<String> deviceIds) {
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        for (String deviceId : deviceIds) {
            executor.submit(() -> {
                // 每个设备独立运行,阻塞不影响其他设备
                while (running) {
                    sendTelemetry(deviceId);      // MQTT I/O
                    Thread.sleep(interval * 1000); // 阻塞时让出载体线程
                }
            });
        }
    }
}

// WebSocket消息推送优化
public void broadcastToDevices(Set<String> deviceIds, String message) {
    // 为每个设备创建虚拟线程并发推送
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        deviceIds.forEach(deviceId ->
            executor.submit(() -> {
                webSocketSession.get(deviceId).sendMessage(message);
            })
        );
    }
}

虚拟线程注意事项:

  • ✅ 适合:HTTP请求、数据库查询、MQTT通信、文件I/O
  • ✅ 阻塞时自动让出载体线程,不影响其他虚拟线程
  • ❌ 不适合:CPU密集型计算(如加密、压缩)
  • ❌ 避免:synchronized 块内的阻塞(会钉住载体线程)
  • ❌ 避免:ThreadLocal 过度使用(每个虚拟线程独立)

Spring Boot 3.2+ 虚拟线程支持:

properties 复制代码
# application.properties
spring.threads.virtual.enabled=true  # 启用虚拟线程
java 复制代码
// Tomcat请求处理自动使用虚拟线程
// 异步任务自动使用虚拟线程
@Async
public CompletableFuture<Void> sendNotificationAsync(String userId) {
    // 此方法在虚拟线程中执行
    notificationService.send(userId, message);
    return CompletableFuture.completedFuture(null);
}
8.1.2 Sealed Classes(密封类)
java 复制代码
// 密封类:限制继承的类
public sealed interface DeviceCommand
    permits TurnOnCommand, TurnOffCommand, SetPowerLimitCommand {

    String getDeviceId();
    CommandType getType();
}

// 具体实现类
public record TurnOnCommand(String deviceId) implements DeviceCommand {
    @Override
    public CommandType getType() {
        return CommandType.TURN_ON;
    }
}

public record TurnOffCommand(String deviceId) implements DeviceCommand {
    @Override
    public CommandType getType() {
        return CommandType.TURN_OFF;
    }
}

// 使用
public void executeCommand(DeviceCommand command) {
    // 编译器知道所有可能的子类,可以进行模式匹配
    switch (command) {
        case TurnOnCommand c -> turnOnDevice(c.deviceId());
        case TurnOffCommand c -> turnOffDevice(c.deviceId());
        // 无需default,因为已覆盖所有子类
    }
}
8.1.2 Pattern Matching for switch(switch模式匹配)

Java 21 增强了 switch 的模式匹配能力,支持类型模式和守卫条件。

java 复制代码
// 旧写法:需要instanceof和强制转换
public String processDevice(Object obj) {
    if (obj instanceof Device d) {
        return d.getName();
    } else if (obj instanceof String s) {
        return s;
    }
    return "unknown";
}

// Java 21:switch模式匹配
public String processDevice(Object obj) {
    return switch (obj) {
        case Device d -> d.getName();
        case String s -> s;
        case Integer i -> "Device-" + i;
        case null -> "null device";  // 支持null检查
        default -> "unknown";
    };
}

// 带守卫条件的模式匹配
public String describeDevice(Object obj) {
    return switch (obj) {
        case Device d when d.isOnline() -> d.getName() + " (在线)";
        case Device d when !d.isOnline() -> d.getName() + " (离线)";
        case Device d -> d.getName();  // 兜底
        default -> "非设备对象";
    };
}

// 实际应用:处理不同类型的MQTT消息
public void handleMqttMessage(String topic, Object payload) {
    switch (payload) {
        case DeviceStatus status -> updateDeviceStatus(status);
        case TelemetryData data -> saveTelemetryData(data);
        case CommandAck ack -> handleCommandAck(ack);
        case String text -> handleTextMessage(text);
        case null -> logger.warn("收到空消息: {}", topic);
        default -> logger.warn("未知消息类型: {}", payload.getClass());
    }
}
8.1.3 Record Patterns(记录模式)

Java 21 支持在模式匹配中解构 Record。

java 复制代码
// 定义Record
public record Device(String id, String name, String room, boolean online) {}
public record Telemetry(String deviceId, double power, long timestamp) {}

// 旧写法
public void process(Object obj) {
    if (obj instanceof Device d) {
        String id = d.id();
        String name = d.name();
        System.out.println(id + ": " + name);
    }
}

// Java 21:Record解构
public void process(Object obj) {
    if (obj instanceof Device(String id, String name, String room, boolean online)) {
        System.out.println(id + ": " + name + " in " + room);
    }
}

// 嵌套解构
public record Room(String building, String number) {}
public record Device(String id, String name, Room location) {}

public void processDevice(Object obj) {
    if (obj instanceof Device(String id, String name, Room(String building, String number))) {
        System.out.println(name + " at " + building + "-" + number);
    }
}

// 在switch中使用Record解构
public String describe(Object obj) {
    return switch (obj) {
        case Device(String id, String name, _, boolean online) ->
            "Device " + name + (online ? " online" : " offline");
        case Telemetry(String deviceId, double power, _) ->
            "Power: " + power + "W from " + deviceId;
        default -> "Unknown";
    };
}
8.1.4 Sealed Classes(密封类)

密封类限制继承关系,增强类型安全,与 switch 模式匹配配合使用。

java 复制代码
// 密封类:限制继承的类
public sealed interface DeviceCommand
    permits TurnOnCommand, TurnOffCommand, SetPowerLimitCommand {

    String getDeviceId();
}

// 具体实现类(使用Record简化)
public record TurnOnCommand(String deviceId) implements DeviceCommand {}
public record TurnOffCommand(String deviceId) implements DeviceCommand {}
public record SetPowerLimitCommand(String deviceId, double limit) implements DeviceCommand {}

// 使用:编译器知道所有可能的子类,switch无需default
public void executeCommand(DeviceCommand command) {
    switch (command) {
        case TurnOnCommand c -> turnOnDevice(c.deviceId());
        case TurnOffCommand c -> turnOffDevice(c.deviceId());
        case SetPowerLimitCommand c -> setPowerLimit(c.deviceId(), c.limit());
        // 无需default,编译器保证覆盖所有情况
    }
}

密封类的优势:

  • 类型安全:编译期检查所有子类
  • 模式匹配友好:switch 可穷尽所有情况
  • API设计:明确限制扩展点
8.1.5 Records(记录类)

Record是不可变数据载体,自动生成标准方法。

java 复制代码
// Record写法:一行搞定
public record DeviceStatus(String deviceId, boolean online, double powerW) {
    // 编译器自动生成:
    // - 私有final字段
    // - 公有构造函数(紧凑构造器)
    // - getter方法(deviceId(), online(), powerW())
    // - equals, hashCode, toString
}

// 带验证逻辑的Record
public record DeviceConfig(String deviceId, int sampleInterval) {
    // 紧凑构造器:用于参数验证
    public DeviceConfig {
        if (sampleInterval < 1 || sampleInterval > 3600) {
            throw new IllegalArgumentException("采样间隔必须在1-3600秒之间");
        }
    }
}

Record适用场景:

  • ✅ DTO(数据传输对象)
  • ✅ 值对象(不可变)
  • ✅ API请求/响应
  • ❌ 需要继承的类
  • ❌ 需要可变状态的类

8.2 Stream API高级用法

8.2.1 收集器(Collectors)
java 复制代码
// 分组
Map<String, List<Device>> devicesByRoom = devices.stream()
    .collect(Collectors.groupingBy(Device::getRoom));

// 嵌套分组
Map<String, Map<Boolean, List<Device>>> grouped = devices.stream()
    .collect(Collectors.groupingBy(
        Device::getRoom,
        Collectors.partitioningBy(Device::isOnline)
    ));

// 统计
IntSummaryStatistics stats = devices.stream()
    .mapToInt(d -> d.getName().length())
    .summaryStatistics();
// 包含count, sum, min, max, average

// 联合字符串
String deviceNames = devices.stream()
    .map(Device::getName)
    .collect(Collectors.joining(", "));

// 转换为Map
Map<String, Device> deviceMap = devices.stream()
    .collect(Collectors.toMap(
        Device::getId,
        Function.identity(),
        (existing, replacement) -> existing  // 冲突解决策略
    ));

// 分区
Map<Boolean, List<Device>> partitioned = devices.stream()
    .collect(Collectors.partitioningBy(d -> d.getPowerW() > 100));
// {true=[高功率设备], false=[低功率设备]}
8.2.2 并行流
java 复制代码
// 顺序流
List<Device> onlineDevices = devices.stream()
    .filter(Device::isOnline)
    .collect(Collectors.toList());

// 并行流(大数据量时性能更好)
List<Device> onlineDevices = devices.parallelStream()
    .filter(Device::isOnline)
    .collect(Collectors.toList());

// 注意:并行流不是总是更快!
// 适用场景:
// ✅ 数据量大(>10000)
// ✅ 计算密集型操作
// ✅ 操作之间无依赖
// ❌ 数据量小
// ❌ I/O密集型操作
// ❌ 操作有副作用(修改共享状态)

8.3 函数式编程

8.3.1 Lambda表达式
java 复制代码
// 旧写法
Collections.sort(devices, new Comparator<Device>() {
    @Override
    public int compare(Device d1, Device d2) {
        return d1.getName().compareTo(d2.getName());
    }
});

// Lambda写法
Collections.sort(devices, (d1, d2) -> d1.getName().compareTo(d2.getName()));

// 方法引用
devices.sort(Comparator.comparing(Device::getName));
8.3.2 函数式接口
java 复制代码
// Function: 接受一个参数,返回一个结果
Function<Device, String> deviceToString = device -> device.getName();
String name = deviceToString.apply(device);

// Predicate: 接受一个参数,返回布尔值
Predicate<Device> isOnline = device -> device.isOnline();
boolean online = isOnline.test(device);

// Consumer: 接受一个参数,无返回值
Consumer<Device> printDevice = device -> System.out.println(device.getName());
printDevice.accept(device);

// Supplier: 无参数,返回一个结果
Supplier<Device> deviceSupplier = () -> new Device();
Device device = deviceSupplier.get();

// BiFunction: 接受两个参数,返回一个结果
BiFunction<String, String, Device> deviceCreator = (id, name) -> {
    Device device = new Device();
    device.setId(id);
    device.setName(name);
    return device;
};
Device device = deviceCreator.apply("device_001", "空调");

总结

核心架构要点

  1. 分层清晰: Controller → Service → Repository → Entity
  2. 中间件强大: Spring Security + AOP + JWT + RateLimiter
  3. 安全机制完善: 密码加密、令牌管理、权限控制、限流防护
  4. 性能优化到位: 缓存、异步、批量操作、索引优化
  5. 代码质量高: 依赖注入、设计模式、函数式编程
  6. Java 21虚拟线程: 高并发IoT场景的理想选择

学习路径建议

  1. 第一阶段: 理解分层架构和Spring Boot基础
  2. 第二阶段: 掌握Spring Security和JWT认证
  3. 第三阶段: 深入AOP和设计模式
  4. 第四阶段: 学习性能优化和高级特性
  5. 第五阶段: 实践项目开发和调试

扩展阅读

相关推荐
国科安芯2 小时前
抗辐照MCU在高空长航时无人机热管理系统中的可靠性研究
单片机·嵌入式硬件·架构·无人机·cocos2d·risc-v
Kiyra2 小时前
突破实时瓶颈:从零构建高性能 WebSocket 实时通讯架构
网络·人工智能·websocket·网络协议·架构·ai-native
程序员Ctrl喵3 小时前
分层架构的协同艺术——解构 Flutter 的心脏
flutter·架构
夏秃然3 小时前
AI 大模型与多模态底层架构解析
人工智能·架构
Hello.Reader3 小时前
Flutter IM 桌面端项目架构、聊天窗口布局与 WebSocket 长连接设计
websocket·flutter·架构
山顶望月3 小时前
OpenClaw 架构与设计思路分析
人工智能·架构
老迟聊架构3 小时前
完全基于对象存储的数据库引擎:SlateDB
数据库·后端·架构
天远云服4 小时前
PHP微服务风控架构:无缝接入天远劳动仲裁信息查询API排查用工黑产
大数据·微服务·架构·php
十月南城5 小时前
电商案例复盘:从单体到微服务的取舍账本——以业务增长阶段为主线复盘架构演进与决策依据
微服务·云原生·架构