Spring Boot实战:基于状态机与Redis实现用户触发式关系系统
发布时间:2026/9/5 2:36:23来源:尧图网络
最近在开发一个社交类应用时遇到了一个有趣的用户故事需求核心是模拟一种“偶遇”与“关系建立”的机制。这让我联想到网络上流行的一个梗——“老婆是靠捡来的”虽然说法戏谑但其背后反映的是一种通过特定条件触发、从无到有建立虚拟关系的技术实现逻辑。在技术层面这通常涉及到用户画像匹配、事件触发、状态机管理以及数据持久化等一系列后端开发知识。本文将从一个实战项目出发完整拆解如何实现一个类似的“缘分匹配”系统后端。我们将使用 Spring Boot 作为基础框架整合 Redis 实现实时状态与缓存并设计一套清晰的状态流转逻辑。无论你是想学习 Spring Boot 项目实战还是对用户行为触发、有限状态机FSM的应用感兴趣这篇文章都能提供从设计到代码的完整参考。1. 系统核心概念与设计思路在开始编码之前我们首先要明确系统要做什么以及为什么这样设计。1.1 业务场景抽象“捡到”这个动作在系统中可以抽象为一个用户对另一个虚拟角色或另一用户发起的特定“触发事件”。这个事件需要满足一系列前置条件如地理位置、用户属性、时间点等触发后系统将改变两者之间的关系状态例如从“陌生人”变为“已结识”。我们的 demo 系统核心流程如下用户系统使用者拥有基础属性ID 名称 等级等。目标角色可以被“捡到”的对象我们称之为TargetCharacter。它拥有触发条件、属性以及一个状态。触发与匹配用户执行某个动作如点击“探索”系统根据规则判断是否满足触发某个角色出现的条件。状态流转一旦触发用户与该角色之间会建立一条关系记录并进入初始状态如“已发现”。后续可通过其他操作如“对话”、“赠送礼物”推动状态向“已结识”、“亲密”等方向流转。数据持久化用户信息、角色信息、关系状态都需要存储。1.2 技术架构选型为了快速实现并保证可扩展性我们选择以下技术栈后端框架Spring Boot 2.7.x。它提供了快速构建、自动配置和丰富的 Starter 支持。数据持久层MySQL用于存储用户、角色等核心实体信息保证数据的持久化和复杂查询。JPA (Hibernate)作为 ORM 框架简化数据库操作。Redis用于缓存热点数据如角色配置、存储用户会话状态以及实现简单的分布式锁确保触发事件的原子性。状态管理采用状态模式 (State Pattern)或简单的枚举状态机来管理关系的生命周期使状态流转逻辑清晰、易于维护。1.3 核心数据模型设计我们设计三个核心实体用户 (User)系统使用者。目标角色 (TargetCharacter)可被“捡到”的角色包含触发规则。用户-角色关系 (UserCharacterRelation)记录用户与角色之间的关联及当前状态。2. 环境准备与项目搭建2.1 开发环境要求JDK版本 8 或 11推荐 11。Maven版本 3.6 及以上。IDEIntelliJ IDEA 或 Eclipse。中间件MySQL 5.7 或 8.0Redis 5.0 或 6.02.2 初始化 Spring Boot 项目使用 Spring Initializr 或 IDE 创建项目选择以下依赖Spring Web构建 Web 接口。Spring Data JPA数据库 ORM 支持。MySQL DriverMySQL 连接。Spring Data RedisRedis 集成。Lombok简化实体类代码可选但推荐。生成的pom.xml关键依赖部分如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies2.3 基础配置在application.yml或application.properties中配置数据库和 Redis。application.yml示例spring: datasource: url: jdbc:mysql://localhost:3306/character_system?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 项目初期使用生产环境应改为 validate 或 none show-sql: true properties: hibernate: format_sql: true redis: host: localhost port: 6379 # password: 如果你的Redis有密码 database: 0 lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 server: port: 8080 # 自定义配置角色触发概率示例 app: trigger: base-probability: 0.3 # 基础触发概率 30%3. 核心数据模型与仓储层实现3.1 实体类定义首先创建对应的实体类使用 JPA 注解进行映射。1. 用户实体 (User.java)package com.example.charactersystem.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; Entity Table(name sys_user) Data public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false, unique true, length 50) private String username; Column(nullable false, length 100) private String nickname; private Integer level 1; Column(name create_time, updatable false) private LocalDateTime createTime; PrePersist protected void onCreate() { createTime LocalDateTime.now(); } }2. 目标角色实体 (TargetCharacter.java)这个实体定义了可以被“捡到”的角色属性及其触发条件。触发条件这里用 JSON 字符串存储实际解析时处理。package com.example.charactersystem.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalTime; Entity Table(name target_character) Data public class TargetCharacter { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false, length 100) private String name; Column(length 500) private String description; // 角色描述 Column(name trigger_condition, columnDefinition TEXT) private String triggerCondition; // JSON格式的触发条件如 {minLevel: 5, allowedTime: [08:00, 20:00]} Column(name base_probability) private Double baseProbability 0.1; // 基础触发概率 Column(name is_active) private Boolean isActive true; // 是否可被触发 // 触发后的初始关系状态 Enumerated(EnumType.STRING) Column(name initial_relation_state, length 50) private RelationState initialRelationState RelationState.DISCOVERED; }3. 用户-角色关系实体 (UserCharacterRelation.java)这是核心的状态记录表使用枚举来定义关系状态。package com.example.charactersystem.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; Entity Table(name user_character_relation, uniqueConstraints {UniqueConstraint(columnNames {user_id, character_id})}) Data public class UserCharacterRelation { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; ManyToOne JoinColumn(name user_id, nullable false) private User user; ManyToOne JoinColumn(name character_id, nullable false) private TargetCharacter character; Enumerated(EnumType.STRING) Column(nullable false, length 50) private RelationState currentState; // 当前关系状态 Column(name trigger_time, nullable false) private LocalDateTime triggerTime; // 触发时间 Column(name last_interact_time) private LocalDateTime lastInteractTime; // 最后交互时间 PrePersist protected void onCreate() { triggerTime LocalDateTime.now(); if (lastInteractTime null) { lastInteractTime triggerTime; } } PreUpdate protected void onUpdate() { lastInteractTime LocalDateTime.now(); } }4. 关系状态枚举 (RelationState.java)package com.example.charactersystem.entity; public enum RelationState { // 未触发 NONE, // 已发现刚“捡到” DISCOVERED, // 已结识进行了初步互动 ACQUAINTED, // 亲密互动达到一定阈值 INTIMATE, // 关系结束 ENDED }3.2 仓储层 (Repository)创建对应的 JPA Repository 接口。package com.example.charactersystem.repository; import com.example.charactersystem.entity.TargetCharacter; import com.example.charactersystem.entity.User; import com.example.charactersystem.entity.UserCharacterRelation; import com.example.charactersystem.entity.RelationState; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; public interface UserRepository extends JpaRepositoryUser, Long { OptionalUser findByUsername(String username); } public interface TargetCharacterRepository extends JpaRepositoryTargetCharacter, Long { ListTargetCharacter findByIsActiveTrue(); } public interface UserCharacterRelationRepository extends JpaRepositoryUserCharacterRelation, Long { OptionalUserCharacterRelation findByUserIdAndCharacterId(Long userId, Long characterId); ListUserCharacterRelation findByUserIdAndCurrentState(Long userId, RelationState state); boolean existsByUserIdAndCharacterId(Long userId, Long characterId); }4. 核心业务逻辑实现触发与状态机这是系统的“大脑”。我们将创建一个服务类CharacterTriggerService来处理“捡到”角色的核心逻辑。4.1 触发条件校验器首先定义一个接口和简单实现用于解析和校验 JSON 格式的触发条件。package com.example.charactersystem.service.condition; import com.example.charactersystem.entity.User; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.time.LocalTime; Component Slf4j public class TriggerConditionChecker { private static final ObjectMapper objectMapper new ObjectMapper(); public boolean checkCondition(User user, String conditionJson) { if (conditionJson null || conditionJson.trim().isEmpty()) { return true; // 无条件限制 } try { JsonNode root objectMapper.readTree(conditionJson); // 检查等级要求 if (root.has(minLevel)) { int minLevel root.get(minLevel).asInt(); if (user.getLevel() minLevel) { return false; } } // 检查时间段 if (root.has(allowedTime)) { JsonNode timeArray root.get(allowedTime); if (timeArray.isArray() timeArray.size() 2) { LocalTime start LocalTime.parse(timeArray.get(0).asText()); LocalTime end LocalTime.parse(timeArray.get(1).asText()); LocalTime now LocalTime.now(); // 简单的时间段判断假设时间段在同一天内 if (now.isBefore(start) || now.isAfter(end)) { return false; } } } // 可以扩展更多条件特定道具、完成特定任务等 return true; } catch (Exception e) { log.error(解析触发条件JSON失败: {}, conditionJson, e); return false; // 解析失败视为条件不满足 } } }4.2 概率触发与防重复逻辑触发需要引入随机性和防止用户重复触发同一角色。package com.example.charactersystem.service; import com.example.charactersystem.entity.*; import com.example.charactersystem.repository.TargetCharacterRepository; import com.example.charactersystem.repository.UserCharacterRelationRepository; import com.example.charactersystem.repository.UserRepository; import com.example.charactersystem.service.condition.TriggerConditionChecker; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.concurrent.TimeUnit; Service Slf4j RequiredArgsConstructor public class CharacterTriggerService { private final UserRepository userRepository; private final TargetCharacterRepository characterRepository; private final UserCharacterRelationRepository relationRepository; private final TriggerConditionChecker conditionChecker; private final RedisTemplateString, String redisTemplate; private ListTargetCharacter activeCharactersCache; private final Random random new Random(); // 项目启动时加载所有活跃角色到缓存也可用Redis缓存 PostConstruct public void initActiveCharacters() { refreshActiveCharactersCache(); } private void refreshActiveCharactersCache() { this.activeCharactersCache characterRepository.findByIsActiveTrue(); log.info(已加载 {} 个活跃角色到缓存。, activeCharactersCache.size()); } /** * 用户执行“探索”动作尝试触发角色 * param userId 用户ID * return 触发到的角色如果未触发则返回null */ Transactional public TargetCharacter tryTriggerCharacter(Long userId) { // 1. 获取用户 User user userRepository.findById(userId) .orElseThrow(() - new RuntimeException(用户不存在: userId)); // 2. 获取所有活跃角色 if (activeCharactersCache.isEmpty()) { refreshActiveCharactersCache(); } // 3. 遍历角色应用条件过滤和概率判断 for (TargetCharacter character : activeCharactersCache) { // 3.1 检查用户是否已拥有该角色关系 boolean alreadyHas relationRepository.existsByUserIdAndCharacterId(userId, character.getId()); if (alreadyHas) { continue; // 已拥有跳过 } // 3.2 检查触发条件 if (!conditionChecker.checkCondition(user, character.getTriggerCondition())) { continue; // 条件不满足跳过 } // 3.3 概率判定 (使用Redis分布式锁防止并发重复触发) String lockKey trigger:lock:user:%d:char:%d.formatted(userId, character.getId()); Boolean lockAcquired redisTemplate.opsForValue().setIfAbsent(lockKey, 1, Duration.ofSeconds(5)); if (Boolean.TRUE.equals(lockAcquired)) { try { // 再次检查防止获取锁期间状态已变化 if (relationRepository.existsByUserIdAndCharacterId(userId, character.getId())) { continue; } // 计算最终概率可结合用户幸运值等属性 double finalProbability character.getBaseProbability(); // 简化处理 if (random.nextDouble() finalProbability) { // 3.4 触发成功创建关系记录 UserCharacterRelation relation new UserCharacterRelation(); relation.setUser(user); relation.setCharacter(character); relation.setCurrentState(character.getInitialRelationState()); // 使用角色定义的初始状态 relationRepository.save(relation); log.info(用户 {} 成功触发角色: {}, user.getNickname(), character.getName()); // 可以发布一个领域事件用于后续处理如发送通知 return character; } } finally { // 释放锁 redisTemplate.delete(lockKey); } } else { log.debug(获取触发锁失败可能并发操作用户ID: {}, 角色ID: {}, userId, character.getId()); } } // 遍历结束仍未触发 log.debug(用户 {} 本次探索未触发任何新角色。, user.getNickname()); return null; } }4.3 状态流转服务触发后用户可以通过互动如对话、送礼来提升关系状态。我们创建一个状态流转服务。package com.example.charactersystem.service; import com.example.charactersystem.entity.RelationState; import com.example.charactersystem.entity.UserCharacterRelation; import com.example.charactersystem.repository.UserCharacterRelationRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.EnumMap; import java.util.Map; Service Slf4j RequiredArgsConstructor public class RelationStateService { private final UserCharacterRelationRepository relationRepository; // 定义一个简单的状态流转规则映射当前状态 - 下一个可能状态 private static final MapRelationState, RelationState[] STATE_TRANSITION_MAP new EnumMap(RelationState.class); static { STATE_TRANSITION_MAP.put(RelationState.DISCOVERED, new RelationState[]{RelationState.ACQUAINTED, RelationState.ENDED}); STATE_TRANSITION_MAP.put(RelationState.ACQUAINTED, new RelationState[]{RelationState.INTIMATE, RelationState.ENDED}); STATE_TRANSITION_MAP.put(RelationState.INTIMATE, new RelationState[]{RelationState.ENDED}); // NONE 和 ENDED 没有下一个状态 } /** * 尝试改变用户与某个角色的关系状态 * param relationId 关系记录ID * param targetState 目标状态 * param interactionType 互动类型如“TALK”, “GIFT”可用于更精细的规则 * return 是否成功改变状态 */ Transactional public boolean changeRelationState(Long relationId, RelationState targetState, String interactionType) { UserCharacterRelation relation relationRepository.findById(relationId) .orElseThrow(() - new RuntimeException(关系记录不存在: relationId)); RelationState currentState relation.getCurrentState(); // 1. 检查目标状态是否合法 if (!isValidTransition(currentState, targetState)) { log.warn(非法状态流转: 从 {} 到 {}, currentState, targetState); return false; } // 2. 可选根据 interactionType 执行额外业务逻辑如增加亲密度、消耗道具等 // if (GIFT.equals(interactionType)) { ... } // 3. 更新状态 relation.setCurrentState(targetState); relationRepository.save(relation); // PreUpdate 注解会更新 lastInteractTime log.info(关系状态更新成功: 记录ID {} 从 {} 变为 {}, relationId, currentState, targetState); return true; } /** * 判断状态流转是否有效 */ private boolean isValidTransition(RelationState from, RelationState to) { // 不允许向 NONE 或自身流转 if (to RelationState.NONE || from to) { return false; } // 已结束的关系不能再改变 if (from RelationState.ENDED) { return false; } RelationState[] possibleNextStates STATE_TRANSITION_MAP.get(from); if (possibleNextStates null) { return false; } for (RelationState possible : possibleNextStates) { if (possible to) { return true; } } return false; } /** * 获取当前关系的下一个可能状态列表用于前端展示 */ public RelationState[] getPossibleNextStates(Long relationId) { UserCharacterRelation relation relationRepository.findById(relationId) .orElseThrow(() - new RuntimeException(关系记录不存在: relationId)); return STATE_TRANSITION_MAP.getOrDefault(relation.getCurrentState(), new RelationState[0]); } }5. 控制器层与 API 设计提供 RESTful API 供前端调用。package com.example.charactersystem.controller; import com.example.charactersystem.entity.RelationState; import com.example.charactersystem.entity.TargetCharacter; import com.example.charactersystem.service.CharacterTriggerService; import com.example.charactersystem.service.RelationStateService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; RestController RequestMapping(/api/character) RequiredArgsConstructor public class CharacterController { private final CharacterTriggerService triggerService; private final RelationStateService stateService; /** * 探索接口 */ PostMapping(/explore/{userId}) public ResponseEntityMapString, Object explore(PathVariable Long userId) { TargetCharacter triggeredChar triggerService.tryTriggerCharacter(userId); MapString, Object result new HashMap(); if (triggeredChar ! null) { result.put(success, true); result.put(message, 恭喜你遇到了 triggeredChar.getName()); result.put(character, triggeredChar); } else { result.put(success, false); result.put(message, 这次探索什么都没有发现...); } return ResponseEntity.ok(result); } /** * 与角色互动推进关系 */ PostMapping(/interact/{relationId}) public ResponseEntityMapString, Object interact( PathVariable Long relationId, RequestParam RelationState targetState, RequestParam(defaultValue TALK) String action) { boolean success stateService.changeRelationState(relationId, targetState, action); MapString, Object result new HashMap(); result.put(success, success); result.put(message, success ? 互动成功 : 互动失败状态流转不合法或关系已结束。); return ResponseEntity.ok(result); } /** * 获取关系可能的下一个状态 */ GetMapping(/relation/{relationId}/next-states) public ResponseEntityMapString, Object getNextStates(PathVariable Long relationId) { RelationState[] nextStates stateService.getPossibleNextStates(relationId); MapString, Object result new HashMap(); result.put(relationId, relationId); result.put(possibleNextStates, nextStates); return ResponseEntity.ok(result); } }6. 系统测试与验证6.1 初始化测试数据在src/main/resources/data.sql中确保spring.jpa.hibernate.ddl-auto设置为create或create-drop以执行添加测试数据。-- 插入测试用户 INSERT INTO sys_user (username, nickname, level) VALUES (zhangsan, 张三, 10); INSERT INTO sys_user (username, nickname, level) VALUES (lisi, 李四, 3); -- 插入目标角色 INSERT INTO target_character (name, description, trigger_condition, base_probability, initial_relation_state, is_active) VALUES (苏瓷, 一位温婉的古风少女喜欢诗词。, {minLevel: 5, allowedTime: [06:00, 23:00]}, 0.25, DISCOVERED, true), (赛博猫, 来自未来的机械猫精通电子设备。, {minLevel: 8}, 0.15, DISCOVERED, true), (旅行画家, 总是在路上的艺术家触发地点随机。, null, 0.05, DISCOVERED, true);6.2 使用 Postman 或 curl 测试 API启动应用运行 Spring Boot 主类。用户探索# 假设张三的用户ID是1 curl -X POST http://localhost:8080/api/character/explore/1多次调用由于概率设定可能会返回触发成功或失败的信息。查询关系你需要先通过查询数据库user_character_relation表获取创建的关系记录ID。进行互动# 假设关系记录ID是1目标状态是 ACQUAINTED curl -X POST http://localhost:8080/api/character/interact/1?targetStateACQUAINTEDactionGIFT查看下一个可能状态curl -X GET http://localhost:8080/api/character/relation/1/next-states7. 常见问题与排查思路在开发和测试过程中你可能会遇到以下问题问题现象可能原因排查步骤与解决方案应用启动失败数据库连接错误1. MySQL 服务未启动。2.application.yml中数据库配置错误用户名、密码、URL。3. 数据库character_system不存在。1. 检查 MySQL 服务状态并启动。2. 仔细核对配置文件中的spring.datasource配置项。3. 登录 MySQL执行CREATE DATABASE character_system;。调用探索接口总是返回“未触发”1. 角色基础概率设置过低如0.01。2. 用户不满足触发条件等级不够、时间不对。3.target_character表中is_active字段为 false。4. Redis 连接失败导致分布式锁逻辑异常。1. 调整测试角色的base_probability为 1 进行验证。2. 检查用户等级和当前时间修改测试角色的trigger_condition或用户等级。3. 确保角色数据is_active1。4. 检查 Redis 服务是否运行以及 Spring 配置是否正确。状态流转接口返回“互动失败”1. 传入的targetState枚举值不正确。2. 当前状态到目标状态的流转不符合预定义规则。3. 关系记录已处于ENDED状态。1. 确认传入的targetState是DISCOVERED,ACQUAINTED,INTIMATE,ENDED中的一个。2. 调用/next-states接口查看当前允许的状态列表。3. 检查数据库确认关系状态。PostConstruct缓存未加载可能 Bean 初始化顺序问题或characterRepository.findByIsActiveTrue()查询无结果。1. 检查TargetCharacterRepository方法名是否正确。2. 确认数据库中有is_activetrue的数据。3. 在 Service 方法中增加 null 判断和 fallback 逻辑。并发请求下同一用户可能重复触发同一角色Redis 分布式锁未生效或锁过期时间太短。1. 确保 Redis 配置正确且服务可用。2. 适当增加锁的持有时间示例中为5秒。3. 在锁内再次检查关系是否存在双检锁示例代码已实现。8. 最佳实践与扩展建议一个可用的 demo 只是起点要用于实际项目还需要考虑更多工程化问题。8.1 配置与规则外部化触发条件目前使用 JSON 字符串存储在数据库。对于复杂规则可以考虑引入规则引擎如 Drools、Easy Rules或使用 Groovy 等脚本语言将规则逻辑与代码解耦。概率模型基础概率过于简单。可以设计更复杂的概率模型例如最终概率 基础概率 用户幸运值系数 - 今日已触发次数衰减。这些系数和公式应配置在配置中心如 Apollo、Nacos或数据库中便于运营调整。8.2 性能优化缓存策略角色数据如示例所示启动时或定时加载到本地内存缓存Caffeine。对于数据更新可以通过发布订阅模式通知应用刷新缓存。用户关系列表高频查询接口如“我的角色列表”可以对结果进行 Redis 缓存并设置合理的过期时间。数据库优化为user_character_relation表的(user_id, character_id)建立唯一索引防止脏数据也提升查询速度。为user_id和current_state等查询条件字段建立索引。8.3 状态机的进阶设计状态模式如果状态流转逻辑非常复杂且每个状态的行为差异很大建议使用经典的状态模式。为每个RelationState定义一个具体的状态类将状态相关的行为如“能否接收礼物”、“升级到下一状态的条件”封装在各自类中使代码更符合开闭原则。状态历史在UserCharacterRelation中增加一个state_historyJSON 或关联子表记录状态每次变化的时间、原因互动类型便于回溯和分析。8.4 监控与日志关键日志CharacterTriggerService中的触发成功/失败日志、RelationStateService中的状态变更日志应使用INFO或WARN级别记录并带上用户ID、角色ID等关键业务参数便于后续排查问题和分析用户行为。业务指标使用 Micrometer 等工具暴露 metrics如character.trigger.attempt.count触发尝试次数、character.trigger.success.count触发成功次数、relation.state.change.count状态变更次数接入 Prometheus 和 Grafana 进行监控。8.5 安全与权限接口鉴权示例中 API 直接使用userId路径参数。在生产环境中必须集成 Spring Security 或 Shiro 等安全框架从登录用户的 Token 或 Session 中获取当前用户ID防止越权操作。参数校验使用Valid注解和 Bean Validation 对控制器入参进行校验防止非法参数导致系统异常。通过以上步骤我们完成了一个具备核心触发、状态管理和基础 API 的“缘分匹配”系统后端。从简单的概率触发到状态机管理再到缓存和锁的应用这个项目涵盖了 Spring Boot 开发中多个常见的技术点。你可以在此基础上根据实际业务需求扩展更丰富的角色属性、更复杂的互动玩法以及更完善的管理后台。
网站建设高端定制企业官网