ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Spring Boot社区动物管理系统实战:从需求梳理到部署上线

Spring Boot社区动物管理系统实战:从需求梳理到部署上线 接手这个项目的时候我其实刚帮一个街道社区做完一套志愿者管理系统脑子还全是活动报名、时长统计那套逻辑。所以当朋友跟我提“社区动物管理”的时候我第一反应是这不就是给猫狗做个档案吗结果真动手做需求梳理才发现这玩意儿远比想象中复杂——它既要管“宠物”又要管“流浪动物”还得跟物业报备、社区义诊、领养审核、疫苗提醒这些东西搅在一起。今天就把这整套基于Spring Boot的社区动物管理系统从设计思路到落地细节从头到尾拆一遍。如果你也在做类似的社区管理类项目或者正准备用Spring Boot接一个“XX管理系统”的单子这篇应该能帮你省掉不少试错的成本。1. 内容整体设计与思路拆解1.1 先搞清楚社区动物管理系统到底在管什么做任何系统之前最忌讳的就是一上来就建表写接口。我见过太多人拿到“社区动物管理”这个题目直接开个animal表塞几个字段就算完事。但真实场景里“社区动物”这四个字至少拆出三类完全不同业务对象家养宠物有主人、有疫苗接种记录、有宠物证核心诉求是档案管理和提醒服务。流浪动物没有固定主人需要记录发现位置、健康状况、是否绝育、是否被领养核心诉求是跟踪和流转。社区公共事务比如物业报备、邻里投诉、义诊活动报名、领养审核这些是围绕动物产生的“事件流”。我当时跟社区的负责人聊完发现他们真正想要的其实是一个“人、动物、事件”三者联动的平台。业主可以给宠物建档看到疫苗到期提醒物业可以记录流浪动物的处置情况有意领养的人可以在线提交申请管理员审核后完成领养流程。这就决定了系统不能只做单表的CRUD得有清晰的角色体系和状态机设计。1.2 技术选型为什么是Spring Boot为什么不用别的选Spring Boot几乎没什么纠结的。一来它生态成熟社区管理这类业务系统需要的用户认证、权限控制、文件上传、定时任务全都有现成方案二来团队里其他人也更熟这套接手维护成本低。如果换成别的框架光培训成本就够喝一壶。具体用到的核心组件我列一下Spring Boot 2.7.x稳定版本搭配JDK 8兼容性最好避免JDK 17一些模块化带来的折腾。Spring Security JWT做登录认证和接口权限控制。MyBatis-Plus单表CRUD和条件查询效率极高尤其适合这种快速交付的管理系统。MySQL 8.0存储业务数据配合Redis做缓存和验证码存储。定时任务Spring Task实现疫苗到期提醒、流浪动物定期回访提醒。文件存储本地磁盘存储即可用于上传宠物照片、领养协议扫描件。还有一点很重要这系统会涉及到微信小程序或者H5端的对接可能。因为社区居民用手机操作是最自然的场景所以后端接口在设计时就要考虑前后端分离用JSON交互为后续多端复用留好余地。1.3 项目结构规划按业务域划分而不是按技术层很多初学Spring Boot的人会把代码分成controller、service、mapper这种纯技术包但业务一复杂就会互相纠缠。我这个项目直接按业务域来组织模块每个域内部再分controller、service、mappercom.community.animal ├── common // 通用配置、工具类、统一返回体 ├── system // 用户、角色、权限、登录 ├── pet // 宠物档案管理 ├── stray // 流浪动物管理 ├── adoption // 领养管理 ├── medical // 疫苗接种、健康检查 ├── community // 公告、投诉建议、活动报名 └── notification // 站内信、提醒任务这种结构的优势在于需求变更时改动范围能控制在对应的业务域内不会牵一发而动全身。比如后来加了一个“宠物走失登记”的功能我只加了lost模块其他模块完全没动。1.4 角色权限模型四类角色各管一摊权限设计我踩过不少坑最开始想着做个复杂的RBAC表后来发现对这种体量的系统纯属过度设计。最终我用的是“角色 接口权限”的简化模型系统管理员拥有全部权限负责系统配置、用户管理、数据统计。社区工作人员处理流浪动物上报、审核领养申请、发布公告、管理义诊活动。业主/居民宠物建档、疫苗记录查询、提交领养申请、上报流浪动物。普通访客只可查看公开的领养信息和公告浏览页面无需登录。权限控制用Spring Security的注解实现比如PreAuthorize(hasRole(ADMIN))再配合JWT里的role字段做判断够用且清爽。2. 核心细节解析与实操要点2.1 宠物档案表设计一张表撑起整个核心业务宠物档案是整个系统的心脏。我第一版设计的表结构比较简单结果做疫苗提醒功能时发现缺字段又回去加来回折腾。这里直接把最终版本分享出来字段说明也都注释清楚CREATE TABLE pet ( id bigint(20) NOT NULL AUTO_INCREMENT, pet_name varchar(50) NOT NULL COMMENT 宠物昵称, pet_type tinyint(4) NOT NULL COMMENT 类型1-狗2-猫3-其他, breed varchar(100) DEFAULT NULL COMMENT 品种, gender tinyint(4) DEFAULT NULL COMMENT 性别0-未知1-公2-母, birth_date date DEFAULT NULL COMMENT 出生日期, weight decimal(5,2) DEFAULT NULL COMMENT 体重kg, color varchar(50) DEFAULT NULL COMMENT 毛色, sterilized tinyint(1) DEFAULT 0 COMMENT 是否绝育0-否1-是, health_status varchar(500) DEFAULT NULL COMMENT 健康状况描述, avatar_url varchar(255) DEFAULT NULL COMMENT 宠物照片, owner_id bigint(20) NOT NULL COMMENT 主人ID关联user表, community_id bigint(20) NOT NULL COMMENT 所属社区ID, status tinyint(4) DEFAULT 1 COMMENT 状态0-注销1-正常2-已走失3-已死亡, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_owner_id (owner_id), KEY idx_community_id (community_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物档案表;几个值得强调的设计细节status字段必须有。宠物走失、死亡是真实发生的业务场景如果没有状态字段后续做统计和查询会非常痛苦。owner_id和community_id必须建索引。宠物档案最频繁的查询就是“某个业主名下的宠物”和“某个社区的宠物列表”这两个索引能让查询效率大幅提升。出生日期用date类型而不是字符串。因为后续要根据年龄做疫苗分组提醒日期类型可以直接用SQL计算年龄省去Java层转换的麻烦。2.2 流浪动物与领养状态机设计是重点流浪动物管理比宠物档案复杂在它有流转状态。一只流浪狗从被上报到收容到治疗再到被领养或放归中间每一步都是状态变化。我把状态机设计成上报(REPORTED) - 待核实(PENDING_VERIFY) - 已收容(SHELTERED) - 治疗中(TREATING) - 待领养(WAITING_ADOPTION) - 已领养(ADOPTED) - 已放归(RELEASED)这个状态流转在做领养审核时尤其关键。如果状态不对比如“治疗中”的动物不应该允许被申请领养那后端在接口层就必须拦下来。我用了一个简单的状态校验工具类public class StrayStateMachine { private static final MapInteger, ListInteger TRANSITIONS new HashMap(); static { TRANSITIONS.put(1, Arrays.asList(2, 5)); // 上报后可核实或直接放归 TRANSITIONS.put(2, Arrays.asList(3, 5)); // 核实后收容或放归 TRANSITIONS.put(3, Arrays.asList(4, 5)); // 收容后治疗或放归 TRANSITIONS.put(4, Arrays.asList(6, 7, 5)); // 治疗后可待领养、康复放归或死亡 TRANSITIONS.put(5, Collections.emptyList()); // 已放归终态 TRANSITIONS.put(6, Arrays.asList(7)); // 待领养可被领养 // 7-已领养 终态 } public static boolean canTransition(int from, int to) { return TRANSITIONS.getOrDefault(from, Collections.emptyList()).contains(to); } }这样每次状态更新时先校验合法性从源头上避免了数据错乱。如果不用状态机直接在Service里写if判断一旦状态多起来就是一团乱麻后期维护绝对想骂人。2.3 疫苗提醒功能Spring Task定时任务实战这个功能是社区方最看重的。宠物打疫苗不是一次性的事狂犬疫苗每年要打一次猫三联也有固定的接种周期。系统要在到期前自动提醒业主。实现思路其实不复杂第一步在疫苗记录表加一个next_due_date字段每次录入疫苗信息时自动计算下次到期日。比如狂犬疫苗有效期一年那next_due_date就是当前日期加365天。第二步编写定时任务每天凌晨扫描一次Component public class VaccineRemindTask { Autowired private VaccineRecordMapper vaccineRecordMapper; Autowired private NotificationService notificationService; Scheduled(cron 0 0 8 * * ?) // 每天早上8点执行 public void remindVaccineExpiry() { // 查询7天内到期的疫苗记录 LocalDate today LocalDate.now(); LocalDate sevenDaysLater today.plusDays(7); ListVaccineRecord records vaccineRecordMapper.selectList( new LambdaQueryWrapperVaccineRecord() .between(VaccineRecord::getNextDueDate, today, sevenDaysLater) .eq(VaccineRecord::getRemindStatus, 0) ); for (VaccineRecord record : records) { Pet pet petMapper.selectById(record.getPetId()); User owner userMapper.selectById(pet.getOwnerId()); // 发送站内信和短信提醒 notificationService.sendVaccineRemind(owner, pet, record); // 更新提醒状态防止重复发送 record.setRemindStatus(1); vaccineRecordMapper.updateById(record); } } }这里有个细节很容易被忽略必须加remind_status字段做幂等控制。否则任务执行到一半服务重启了下次扫描又会推送一遍相同提醒。加了状态字段之后只能扫“未提醒”的记录发送成功后才标记为已提醒可靠性高很多。2.4 文件上传宠物照片不能直接存数据库刚开始有同事提议把照片转成Base64存到数据库里被我当场否了。照片是典型的非结构化数据存数据库会导致数据库体积暴涨备份恢复都很慢。查询列表时要一次性返回大量数据接口响应时长飙升。根本没法利用CDN加速。正确做法是文件存磁盘或OSS数据库只存URL地址。本地存储实现很简单PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { // 校验文件大小这里限制5MB if (file.getSize() 5 * 1024 * 1024) { return Result.error(文件大小不能超过5MB); } // 校验文件类型只允许图片 String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); if (!Arrays.asList(.jpg, .jpeg, .png, .gif).contains(suffix.toLowerCase())) { return Result.error(仅支持jpg、jpeg、png、gif格式的图片); } // 生成唯一文件名防止重名覆盖 String filename UUID.randomUUID().toString().replace(-, ) suffix; // 按日期分目录存储避免单目录文件过多 String datePath new SimpleDateFormat(yyyy/MM/dd).format(new Date()); File dir new File(UPLOAD_DIR datePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir.getAbsolutePath() / filename)); // 返回访问URL String url /files/ datePath / filename; return Result.success(url); }按日期分目录这个习惯我是后来才养成的。之前所有图片都存在一个目录下半年后文件数量过万操作起来极其痛苦。按日期分目录后查找和清理都方便得多。3. 实操过程与核心环节实现3.1 从零搭建项目骨架初始化配置项目是直接用Spring Initializr生成的但有几个配置我后来手动改过这里同步一下。首先是pom.xml里的关键依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependencyapplication.yml里几个值得注意的配置项spring: datasource: url: jdbc:mysql://localhost:3306/community_animal?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 servlet: multipart: max-file-size: 5MB max-request-size: 20MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0MySQL的serverTimezoneAsia/Shanghai这个配置特别容易踩坑不加上时区参数连接会报错或者时间差8小时。另外MyBatis-Plus的逻辑删除配置我建议从一开始就加上这样删除宠物档案、删除领养申请都是软删除数据不消失统计报表还能用到历史数据。3.2 核心接口实现宠物建档上传实战宠物建档是业主用得最多的功能这里展示完整链路。Controller层接受请求Service层处理业务Mapper层操作数据库。直接上核心代码Service public class PetServiceImpl implements PetService { Override Transactional(rollbackFor Exception.class) public Pet createPet(PetCreateDTO dto, Long ownerId) { // 1. 校验当前用户是否已经达到建档上限 Long petCount petMapper.selectCount( new LambdaQueryWrapperPet() .eq(Pet::getOwnerId, ownerId) .eq(Pet::getStatus, 1) ); if (petCount 5) { throw new BusinessException(每户最多登记5只宠物); } // 2. 创建宠物档案 Pet pet new Pet(); BeanUtils.copyProperties(dto, pet); pet.setOwnerId(ownerId); if (dto.getBirthDate() ! null) { pet.setAge(calculateAge(dto.getBirthDate())); } petMapper.insert(pet); // 3. 记录一条操作日志 operationLogService.log(ownerId, CREATE_PET, 新建宠物档案 pet.getPetName(), pet.getId()); return pet; } }这里说几个我实际开发中体会很深的点为什么不直接在Controller里写业务逻辑宠物建档涉及建档数量校验、年龄计算、日志记录多个步骤如果都塞在Controller里代码会越来越臃肿。Service层把业务逻辑收拢起来Controller只负责参数接收和结果返回分工清晰也好写单元测试。Transactional一定要加。宠物建档需要同时插入宠物表和日志表如果第二步失败而第一步成功数据就不一致了。加上事务注解后任何一步抛异常都会回滚全部操作保证数据完整性。3.3 领养审核流程多角色协作的核心场景领养是整个系统里最复杂的一条链路涉及三方角色申请人业主、审核人社区工作人员、系统自动校验条件。我把整个流程拆成四步第一步提交申请。申请人在线选择待领养的动物填写个人住址、养宠经验、家庭成员等信息。后端自动校验该动物必须处于“待领养”状态申请人在系统内没有未完成的领养申请。第二步资格初审。社区工作人员查看申请材料确认申请人是本社区居民养宠条件合适。通过或驳回驳回时需要填写原因。第三步线下确认。这一步在系统外完成但系统要记录确认人和确认时间。通常社区工作人员会实地走访或者约谈申请人。第四步完成领养。确认通过后点击完成领养系统自动更新流浪动物状态为“已领养”同时为该动物创建一个关联到新主人的宠物档案。这里有个业务联动我实现时是放在一个事务里完成的Transactional(rollbackFor Exception.class) public void completeAdoption(Long adoptionId, Long operatorId) { AdoptionApplication application adoptionMapper.selectById(adoptionId); // 状态校验 if (application.getStatus() ! 2) { throw new BusinessException(当前申请状态不允许完成领养); } // 1. 更新申请状态 application.setStatus(3); application.setAuditBy(operatorId); application.setAuditTime(new Date()); adoptionMapper.updateById(application); // 2. 更新流浪动物状态为已领养 StrayAnimal stray strayMapper.selectById(application.getStrayId()); stray.setStatus(7); strayMapper.updateById(stray); // 3. 创建宠物档案关联到申请人 Pet pet new Pet(); pet.setPetName(stray.getStrayName()); pet.setPetType(stray.getStrayType()); pet.setOwnerId(application.getApplicantId()); pet.setCommunityId(application.getCommunityId()); petMapper.insert(pet); // 4. 记录领养日志 adoptionLogMapper.insert(...); }这段代码最有价值的是第四步的“自动建档”逻辑。领养完成后系统不是简单地把流浪动物状态改成“已领养”就完事而是自动生成一条宠物档案这样这只动物后续的疫苗记录、健康检查就可以挂在档案上继续跟踪。我第一次做的时候漏了这一步结果领养后的动物彻底“失联”了后面统计数据才发现问题。3.4 数据看板让管理层能一眼看懂全局系统交付时社区负责人提出一个需求“我们想知道辖区里有多少只狗、多少只猫多少流浪动物待领养这个月新增了多少宠物档案。”于是我又加了一个数据看板模块在首页展示运营数据。实现思路是聚合查询写一个DashboardService统一处理Service public class DashboardService { public DashboardVO getDashboardData(Long communityId) { DashboardVO vo new DashboardVO(); // 宠物总量及类型分布 vo.setTotalPet(petMapper.selectCount(...)); vo.setDogCount(petMapper.selectCount(...)); vo.setCatCount(petMapper.selectCount(...)); // 流浪动物状态统计 vo.setStrayTotal(strayMapper.selectCount(...)); vo.setStrayWaitingAdoption(strayMapper.selectCount(...)); vo.setStrayAdopted(strayMapper.selectCount(...)); // 本月新增 vo.setMonthNewPet(petMapper.selectCount(...)); vo.setMonthAdoption(adoptionMapper.selectCount(...)); // 疫苗即将到期数量 vo.setVaccineExpiring(vaccineRecordMapper.selectCount(...)); return vo; } }数据量小的时候这种多次查询没问题。但如果社区很大、数据量上来就得考虑用定时任务提前算好统计结果或者用Redis缓存。我是加了一层缓存每天早上8点定时统计并写入Redis前端页面直接读取缓存响应速度从几百毫秒降到几毫秒。4. 常见问题与排查技巧实录4.1 接口返回401Spring Security放行配置的坑这是第一个求助我帮忙的人反馈最多的问题。每写一个新接口前端一调就401排查一圈发现是Security配置里没放行该接口。Spring Security默认拦截所有请求必须显式指定哪些接口匿名可访问哪些需要认证Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/**).permitAll() // 登录注册接口放行 .antMatchers(/api/public/**).permitAll() // 公开接口放行 .antMatchers(/files/**).permitAll() // 静态文件放行 .anyRequest().authenticated() // 其他全部需要认证 .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }我的经验是把所有接口按访问权限分成三类——完全公开、登录可访问、管理员专属。在接口设计阶段就规划好URL前缀比如公开接口用/api/public/开头需要权限的用/api/admin/开头。这样Security配置只要写几行通配规则就够了不用每个接口都去配置放行。4.2 定时任务突然不执行了检查是不是被事务卡住疫苗提醒跑了一段时间后有次运营反馈说“这两周没人收到提醒”。我第一反应是看日志发现定时任务确实执行了但数据库更新卡住了——有个宠物档案的锁一直没释放导致任务里批量更新被阻塞。问题出在代码结构上发送提醒和更新状态在一个长事务里一条数据的锁冲突导致整个事务回滚。解决办法是把“发送提醒”和“更新状态”拆开发送成功一个就更新一个单条更新失败不影响其他记录。还有个项目特有的坑多实例部署时定时任务会重复执行。开发环境单机没问题生产上了两台服务器结果一条提醒发了两遍。解决方案是引入分布式锁我用的是Redis的SETNX命令实现一个简单的锁Boolean locked redisTemplate.opsForValue() .setIfAbsent(lock:vaccine_remind, 1, 5, TimeUnit.MINUTES); if (!Boolean.TRUE.equals(locked)) { log.info(上次任务未执行完成本次跳过); return; } try { // 执行任务 } finally { redisTemplate.delete(lock:vaccine_remind); }锁的过期时间设5分钟确保即使任务执行过程中服务崩溃锁也会自动释放不会出现死锁。4.3 列表查询慢索引与SQL优化的实际案例流浪动物列表页一开始响应时间1秒多用户反馈“卡得不行”。用explain分析SQL后发现状态条件查询没走索引全表扫描了。解决思路有两个第一给常用查询条件建联合索引ALTER TABLE stray_animal ADD INDEX idx_community_status (community_id, status);这个索引同时覆盖了“按社区筛选”和“按状态筛选”两个最常见的场景。第二优化查询方式。流浪动物列表本来用左连接查上传人信息每次都要关联user表。后来改成列表页不查全量用户信息只冗余存储上传人昵称到流浪动物表查询时直接取省掉一次Join。优化后接口响应从1.2秒降到80毫秒体感提升非常明显。这也是社区类管理系统的共性优化点——大部分列表页的慢都慢在无意义的关联查询上。4.4 照片上传后无法访问静态资源映射问题排查部署到服务器后发现宠物照片上传成功了但前端访问图片URL一直404。排查了半天发现是Spring Boot没有配置静态资源映射。本地开发能访问是因为IDE会自动处理部署到正式环境就没了。解决办法是在配置类里加上资源映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: UPLOAD_DIR); } }这个坑很隐蔽属于那种“开发环境一切正常一到生产就暴露”的典型问题。后来我把这个配置写进了项目初始化模板里新项目直接带上再也没踩过。4.5 常见问题速查表我整理了一份开发调试过程中遇到的问题速查表直接给过来按图索骥能省不少排查时间问题现象可能原因解决方案接口全部返回401Security配置未放行对应接口URL检查URL前缀是否匹配permitAll规则定时任务重复执行多实例部署无分布式锁引入Redis分布式锁控制并发列表查询越来越慢缺索引或关联查询过多explain分析SQL补充联合索引照片上传成功但访问404静态资源映射未配置添加addResourceHandlers映射删除宠物后关联数据报错逻辑外键未处理关联数据被级联删除或孤立使用软删除删除前先处理关联表数据提醒重复发送缺少幂等状态字段增加remind_status字段标记已发送时间字段差8小时MySQL连接时区未配置URL加serverTimezoneAsia/Shanghai大文件上传内存溢出未限制上传文件大小配置multipart max-file-size和max-request-size注意凡是涉及“多表联动更新”的操作比如完成领养同时更新申请状态、动物状态、创建档案必须放到一个事务里任何一个环节失败都要整体回滚。这是这类管理系统最容易出数据不一致的地方。5. 从0到1上线部署与运维实用经验5.1 服务器部署Docker Compose一键搞定系统开发完部署又是个坎。手动装JDK、MySQL、Redis、Nginx再调配置一套下来半天就没了。我直接用Docker Compose把这些服务编排起来以后换服务器一条命令全部搞定。docker-compose.yml的核心配置如下version: 3.8 services: mysql: image: mysql:8.0 container_name: animal-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: community_animal ports: - 3306:3306 volumes: - ./mysql-data:/var/lib/mysql command: --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci redis: image: redis:7.0 container_name: animal-redis ports: - 6379:6379 app: build: . container_name: animal-app depends_on: - mysql - redis ports: - 8080:8080 environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/community_animal?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: root123456 SPRING_REDIS_HOST: redis有个细节容器里访问MySQL和Redislocalhost不行必须用Service名称就是mysql、redis作为主机名。我第一次部署时没注意应用一直报连不上数据库排查半天才发现是这个问题。5.2 前端部署Nginx反向代理与端口转发前端是Vue项目构建后生成静态文件用Nginx托管。同时需要把后端接口代理到应用端口server { listen 80; server_name animal.community.com; # 前端静态文件 root /usr/share/nginx/html; index index.html; # 解决Vue路由刷新404的问题 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 上传的文件 location /files/ { proxy_pass http://127.0.0.1:8080/files/; } }try_files那一行特别重要。Vue使用history路由时直接访问/pets/123这种地址Nginx找不到对应的静态文件就会404。加上try_files所有请求都先尝试找真实文件找不到就回退到index.html由前端路由接管。5.3 数据备份绝不能省的最后一根救命稻草管理系统的数据是核心资产我把数据备份分成两层第一层MySQL每日自动备份。用crontab写个定时任务每天凌晨3点执行mysqldump0 3 * * * mysqldump -uroot -p123456 community_animal /backup/community_animal_$(date \%Y\%m\%d).sql # 只保留最近30天的备份 0 4 * * * find /backup -name *.sql -mtime 30 -exec rm {} \;第二层上传文件定期同步。宠物照片等用户在系统里创建的图片同步到另一个磁盘目录防止硬盘故障造成不可恢复的损失。这里要提醒一句备份脚本一定要先手动测试一遍。我吃过亏以为配置了crontab就万事大吉后来真需要恢复数据时才发现脚本路径写错了根本没执行成功过。6. 进阶思考这套系统还能做成什么样项目做完交付了但回头想想这套“社区动物管理”的系统设计思路其实具有很强的通用性。把“宠物”换成“老人”“儿童”“车辆”这套“档案管理 状态流转 提醒服务 多角色协作”的骨架都能复用。做这套系统的过程里我最大的收获不是学会多少个注解多少个框架而是想明白了一件事管理系统最核心的价值永远不是CRUD本身而是把线下混乱的流程梳理成清晰可追踪的线上路径。社区动物管理表面上是给猫狗建档案实际上是在理顺“人养宠物”和“社区管理”之间的各种关系。一只流浪狗从哪里来、经过谁核实、由谁治疗、被谁领养每一步都有据可查这才是系统真正被社区接受的原因。最后分享一个小技巧做这类多角色、多状态的系统在动手写代码之前拿白板把所有角色、状态、流转路径画一遍比任何技术选型都重要。把业务理清了代码写起来就跟抄作业一样顺。我在这个项目上花了两天梳理业务换来的是后面开发几乎没有大改的需求这笔时间花得值。
返回列表