宿舍电力管理系统后端架构深度解析
📖 文档说明
本文档以完整的HTTP请求处理流程为线索,深入剖析宿舍电力管理系统的后端架构、中间件机制和核心设计模式。从请求入口到业务逻辑再到数据持久化,逐层解析每个环节的工作原理。
目录
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/falseacquire(): 阻塞等待获取令牌,可能等待- 平滑突发限流 (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);
}
}
}
设计亮点:
- ✅ 线程安全 : 使用
ConcurrentHashMap、AtomicInteger、volatile保证多线程安全 - ✅ 资源控制: 固定线程池 + 设备数量限制,防止资源耗尽
- ✅ 优雅停止 : 使用
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", "空调");
总结
核心架构要点
- 分层清晰: Controller → Service → Repository → Entity
- 中间件强大: Spring Security + AOP + JWT + RateLimiter
- 安全机制完善: 密码加密、令牌管理、权限控制、限流防护
- 性能优化到位: 缓存、异步、批量操作、索引优化
- 代码质量高: 依赖注入、设计模式、函数式编程
- Java 21虚拟线程: 高并发IoT场景的理想选择
学习路径建议
- 第一阶段: 理解分层架构和Spring Boot基础
- 第二阶段: 掌握Spring Security和JWT认证
- 第三阶段: 深入AOP和设计模式
- 第四阶段: 学习性能优化和高级特性
- 第五阶段: 实践项目开发和调试