【应用程序】基于 Spring Boot + Spring AI的虚拟宠物Web 应用(三)
四、数据持久化方案目前的状态管理最大的问题就是:应用重启,猫就饿死了(数据全丢了)。作为一只负责任的铲屎官,我们得给猫咪的状态找个靠谱的家。4.1 方案对比方案优点缺点适用场景推荐度内存 Map(现状)简单、快、零配置重启丢失、不支持多实例、无 TTL本地演示、开发调试⭐Redis持久化、高性能、原生支持 TTL、数据结构丰富需要额外部署 Redis 服务生产环境首选⭐⭐⭐⭐⭐关系型数据库(MySQL/PostgreSQL)结构化、易查询、事务支持速度稍慢、需要表设计需要复杂查询、报表统计⭐⭐⭐⭐文件存储(JSON/YAML)简单、无需外部依赖并发差、不适合高频读写单机轻量应用、配置存储⭐⭐H2 / SQLite(嵌入式数据库)零配置、持久化、SQL 支持并发性能一般中小型应用、快速原型⭐⭐⭐4.2 推荐方案:Redis + 持久化对于这个场景,Redis 是比较 sweet 的选择:** Redis**数据结构匹配:宠物状态就是简单的 key-value(Hash),Redis 天生擅长TTL 自动清理:支持设置过期时间,长时间不活跃的会话自动清理,省内存性能极好:读写都是微秒级,不会影响 AI 交互的响应速度Spring Boot 集成简单:spring-boot-starter-data-redis一行依赖搞定支持集群:应用多实例部署时,Redis 是共享状态的最佳选择持久化选项:RDB 快照 + AOF 日志,数据不会丢4.3 Redis 数据结构设计TTL管理EXPIRE pet:state:user-123 6048007天后自动删除Redis数据结构Key: pet:state:user-123Hashfield: hungervalue: 69field: happinessvalue: 53field: lastInteractionTimevalue: 2026-05-26T14:30:00field: moodvalue: NORMALfield: versionvalue: 1Key 设计规范:prefix : 业务标识 separator : ":" namespace : 状态类型 id : 会话 ID 完整格式 : pet:state:{conversationId} 示例 : pet:state:user-123-abcHash 字段设计:字段类型说明hungerInteger饥饿度 0-100happinessInteger开心度 0-100lastInteractionTimeISO-8601 String上次互动时间moodString当前心情枚举值versionInteger乐观锁版本号(防止并发覆盖)TTL 策略:默认 TTL:7 天(604800秒)每次互动后刷新 TTL长时间不活跃的宠物自动"放生"(清理数据)4.4 存储层代码实现// PetStateRepository.java - 存储接口publicinterfacePetStateRepository{OptionalPetStatefindById(StringconversationId);voidsave(PetStatestate);voiddeleteById(StringconversationId);}// RedisPetStateRepository.java - Redis 实现@Repository@PrimarypublicclassRedisPetStateRepositoryimplementsPetStateRepository{privatefinalStringRedisTemplateredisTemplate;privatefinalObjectMapperobjectMapper;privatestaticfinalStringKEY_PREFIX="pet:state:";privatestaticfinallongTTL_SECONDS=7*24*60*60;// 7天publicRedisPetStateRepository(StringRedisTemplateredisTemplate,ObjectMapperobjectMapper){this.redisTemplate=redisTemplate;this.objectMapper=objectMapper;}@OverridepublicOptionalPetStatefindById(StringconversationId){Stringkey=KEY_PREFIX+conversationId;// 使用 Hash 操作获取所有字段MapObject,Objectentries=redisTemplate.opsForHash().entries(key);if(entries.isEmpty()){returnOptional.empty();}returnOptional.of(mapToPetState(conversationId,entries));}@Overridepublicvoidsave(PetStatestate){Stringkey=KEY_PREFIX+state.getConversationId();// 使用 Hash 存储,字段清晰,更新灵活MapString,Stringmap=newHashMap();map.put("hunger",String.valueOf(state.getHunger()));map.put("happiness",String.valueOf(state.getHappiness()));map.put("lastInteractionTime",state.getLastInteractionTime().toString());map.put("mood",state.getMood().name());// 使用 putAll 原子性写入redisTemplate.opsForHash().putAll(key,map);// 刷新 TTLredisTemplate.expire(key,TTL_SECONDS,TimeUnit.SECONDS);}@OverridepublicvoiddeleteById(StringconversationId){redisTemplate.delete(KEY_PREFIX+conversationId);}/** * 将 Redis Hash 映射为 PetState 对象 */privatePetStatemapToPetState(StringconversationId,MapObject,Objectentries){PetStatestate=newPetState(conversationId);if(entries.containsKey("hunger")){state.setHunger(Integer.parseInt(entries.get("hunger").toString()));}if(entries.containsKey("happiness")){state.setHappiness(Integer.parseInt(entries.get("happiness").toString()));}if(entries.containsKey("lastInteractionTime")){state.setLastInteractionTime(LocalDateTime.parse(entries.get("lastInteractionTime").toString()));}if(entries.containsKey("mood")){state.setMood(PetMood.valueOf(entries.get("mood").toString()));}returnstate;}}4.5 降级方案:内存实现(用于开发测试)// InMemoryPetStateRepository.java - 内存实现(开发/测试用)@Repository@Profile("dev")// 只在 dev 环境生效publicclassInMemoryPetStateRepositoryimplementsPetStateRepository{privatefinalMapString,PetStatestore=newConcurrentHashMap();@OverridepublicOptionalPetStatefindById(StringconversationId){returnOptional.ofNullable(store.get(conversationId));}@Overridepublicvoidsave(PetStatestate){store.put(state.getConversationId(),state);}@OverridepublicvoiddeleteById(StringconversationId){store.remove(conversationId);}}五、定时任务设计猫咪不是机器,它需要**"活着"的感觉**。即使主人不在线,它也应该有自己的生活规律——会饿、会无聊、会想主人。5.1 状态自然衰减