
前阵子帮一个社区创业团队做了套“社区资源共享系统”技术栈就是咱们天天见的Spring Boot加Vue。老规矩咱不谈那些花里胡哨的概念就说真实落地需求怎么拆、表怎么建、接口怎么画、前端怎么接、最后怎么部署上线一条龙给你捋明白。很多朋友问过我社区资源共享这种项目不就是一个“二手物品发布”加“借用归还”吗真上手做才发现里头的门道多得是。资源状态怎么流转、预约超时怎么处理、信用分怎么算、管理员怎么审核每一步都在考验你设计得够不够细。这套系统做完前后端联调那一周踩的坑比写代码一个月学到的都多。这篇就把整个设计和实现过程掰开揉碎了写出来适合正在做毕设、准备面试项目、或者想给社区物业做内部工具的同学参考。前端和后端的代码思路都会涉及单看哪部分都行建议从头顺着读因为借用的状态机设计会贯穿前后端。1. 项目定位与技术选型为什么是Spring Boot Vue1.1 社区资源共享系统到底要解决什么问题先搞清楚一件事社区资源共享共享的到底是什么。我这里做的是“业主闲置物品互助借用”品类覆盖工具箱、露营装备、儿童玩具、图书期刊这些低频使用、闲置率高、买新的不划算的东西。核心流程就三条线资源拥有者发布物品填信息、传图片、定归还期限需求方搜索浏览发起借用申请完成线下交接系统全程记录借用状态到期提醒归还确认再挂上信用评价这个需求模型很典型物品有多状态、借用在时间上有天然周期、参与人之间有信任评价关系。所以它天生适合做成一个带状态机的业务系统也就非常适合用来展示Spring Boot的后端建模能力和Vue前端的交互设计能力。选Spring Boot加Vue并不是因为它“流行”就无脑选而是这个组合确实匹配项目特点。1.2 前后端分离架构的好处与选型权衡这套系统的用户分三类普通业主、社区管理员、系统运维。业主用的是H5和小程序端管理员用PC后台。如果不用前后端分离同一个后端同时渲染H5和管理后台那模板渲染逻辑会混得一团糟光是导航菜单和权限按钮的判断就够你写几百行重复代码。拆成前后端分离之后一个后端服务通过RESTful API同时喂三端数据前端各写各的界面权限通过登录后的令牌控制。结构上清爽得多后续要是加个安卓App后端一行不用改。Spring Boot这边我选了2.7.x版本配JDK 8。有人问为什么不上3.x说实话新项目用3.x完全没问题但Spring Boot 3要求JDK 17起步很多社区服务器还跑着老环境加上团队里有人对Jakarta命名空间不熟升级反而拖慢进度。做实际项目稳定压倒一切。1.3 整体功能模块划分我习惯先画思维导图再写代码这次模块划分如下用户模块注册、登录、个人信息维护、信用分展示资源模块发布、编辑、上下架、分类管理、图片上传借用模块借用申请、审核、取物确认、归还确认、逾期处理评价模块借用完成后双方互评管理后台用户管理、资源审核、借用记录监控、数据统计每个模块拆出来工作量估算就清晰了。整个系统开发周期排了三周第一周后端接口和数据库第二周前端页面加联调第三周留给我自己测试修Bug。实际做下来基本卡着时间走。2. 后端Spring Boot核心实现详解2.1 项目结构与分层设计后端直接用Spring Initializr生成的基础工程然后按下面这个结构重构src/main/java/com/community/share/ ├── config/ // 配置类跨域、拦截器、定时任务配置 ├── controller/ // 控制层接收请求、参数校验、返回结果 ├── service/ // 业务层核心业务逻辑 │ └── impl/ // 业务实现类 ├── mapper/ // 数据访问层MyBatis接口 ├── entity/ // 实体类数据库表对应 ├── dto/ // 数据传输对象前端交互参数 ├── common/ // 通用返回、异常处理、常量 └── ShareApplication.java这个分层是经典的三层架构。很多人觉得controller直接写SQL更快小项目是这样但当借用状态的流转逻辑一多你会发现在controller里堆逻辑根本没法维护。我在service层专门写了BorrowRecordService所有状态变更都必须经过这个类禁止在controller里直接改借用记录的状态字段这是铁律。2.2 数据库设计与核心表结构数据库我用了MySQL 5.7一共设计了六张核心表。用户表t_user字段类型说明idbigint主键自增usernamevarchar(50)登录名唯一索引passwordvarchar(100)BCrypt加密存储nicknamevarchar(50)昵称avatarvarchar(255)头像URLphonevarchar(20)手机号credit_scoreint信用分默认100roletinyint1-普通用户 2-管理员create_timedatetime注册时间资源表t_resource字段类型说明idbigint主键titlevarchar(100)资源标题descriptiontext详细描述category_idbigint分类IDcover_imagevarchar(255)封面图owner_idbigint所有者IDstatusint状态0-可借 1-被预约 2-借用中 3-已归还 4-已逾期 5-已下架daily_rentdecimal押金/积分消耗max_borrow_daysint最长借用天数view_countint浏览数create_timedatetime发布时间借用记录表t_borrow_record字段类型说明idbigint主键resource_idbigint资源IDborrower_idbigint借用人IDowner_idbigint资源所有者IDstatusint0-申请中 1-已同意 2-已取物 3-已归还 4-已拒绝 5-已取消 6-已逾期borrow_daysint申请借用天数apply_timedatetime申请时间approve_timedatetime同意时间return_timedatetime实际归还时间expected_return_timedatetime应还时间另外还有分类表、评价表、通知表结构比较常规就不贴了。关键设计点在于资源状态和借用记录状态是两套状态但它们必须联动。比如资源被预约时资源表状态改成1借用记录状态是1时资源必须是1。一开始我没做这个联动结果出现了资源都借出去了列表还显示可借的Bug后来专门在service层加了状态同步校验。2.3 核心接口设计与状态流转后端接口完全按照RESTful风格设计下面这几个接口是核心中的核心。POST /api/auth/registerPOST /api/auth/loginGET /api/resources?page1size10keywordcategoryIdPOST /api/resources (发布资源)GET /api/resources/{id} (资源详情)POST /api/resources/{id}/borrow (发起借用申请)PUT /api/borrow-records/{id}/approve (审核借用)PUT /api/borrow-records/{id}/confirm-take (确认取物)PUT /api/borrow-records/{id}/confirm-return (确认归还)借用状态机的流转我画在脑子里是这样的申请中 → 已同意 → 已取物 → 已归还申请中 → 已拒绝申请中 → 已取消已取物 超过应还时间 → 已逾期每个状态变更我都在service里写了一个独立方法比如confirmTake(Long recordId, Long currentUserId)方法会做三重校验记录必须处于“已同意”状态否则抛业务异常当前用户必须是借用人本人资源状态必须是“被预约”通过强校验保证状态流转不会乱。状态机设计这块如果你没想清楚就直接写接口那后面调试能把你折磨疯。2.4 JWT登录认证与接口权限控制登录认证用的JWT引入jjwt依赖实现。流程很简单用户登录成功服务端生成token返回前端前端存到localStorage后续请求塞进Authorization头后端的拦截器解析token把用户信息放到ThreadLocal里供业务使用token里我只放了userId和role两个关键信息过期时间设2小时。为什么要单独用拦截器而不是在每个controller里解析token因为如果每个接口都写解密逻辑那代码重复度太高。用拦截器统一处理登录校验、权限判断、异常处理都在一处完成。还有一个细节密码绝对不能明文存。用的BCrypt加密Spring Security的BCryptPasswordEncoder单独拿出来用就行比MD5安全得多。MD5加盐虽然也能用但BCrypt强度可调已经是行业默认标准了。2.5 Spring Boot定时任务的应用场景这个系统的定时任务特别关键。因为线下的借用流程天然有“时间期限”不能全靠用户自觉必须由系统兜底。我用Scheduled注解写了三个定时任务每小时扫一次“已同意但超24小时未取物”的记录自动取消并释放资源每晚零点扫一次“已取物但超过应还时间”的记录自动标记逾期并扣信用分每晚零点扫一次“预约超48小时未审核”的记录自动提醒资源所有者定时任务这块有个坑必须说Spring Boot的Scheduled默认单线程串行执行如果一个任务卡住了后面任务全部排队。我开发时不注意写了一个慢SQL的统计任务结果零点跑统计凌晨一点逾期处理才开始跑。解决办法就是加EnableAsync并用Async标记耗时的任务让它们并行执行。3. 前端Vue实现与前后端联调3.1 前端工程化搭建与目录规范前端用Vue 3加Vite没有用Vue CLI。Vite启动快HMR响应快开发体验真的不是一个级别。搭配的生态是Vue Router 4做路由Pinia做状态管理。目录结构如下src/ ├── api/ // 接口请求封装 │ ├── auth.js │ ├── resource.js │ └── borrow.js ├── assets/ // 静态资源 ├── components/ // 公共组件 │ ├── ResourceCard.vue │ └── ImageUpload.vue ├── router/ // 路由配置 │ └── index.js ├── stores/ // Pinia状态 │ ├── user.js │ └── app.js ├── views/ // 页面组件 │ ├── Home.vue │ ├── ResourceList.vue │ ├── ResourceDetail.vue │ ├── Publish.vue │ ├── Mine.vue │ └── Admin/ └── utils/ // 工具 ├── request.js └── auth.jsAPI层独立封装的好处是页面里不会出现一长串url字符串。所有请求统一走utils/request.js这个axios实例baseURL配成/api开发环境通过Vite代理转发到后端8081端口生产环境交给Nginx反向代理前端代码里不用写死任何后端地址。Vue 3的组合式APIComposition API写起来确实比选项式顺手尤其是借用状态这种需要复用的逻辑。我把借用操作抽成了一个useBorrowAction函数资源详情和个人中心都调它状态提示、异常处理逻辑只写一遍。3.2 路由规划动态路由与路由守卫路由这块我踩过不少坑值得单独说一说。本项目路由分两块普通用户能访问的页面和管理员专属页面。普通页面采用静态路由直接在router/index.js里配好。管理员页面用动态路由用户登录后根据角色动态添加。核心路由如下const routes [ { path: /, component: Home }, { path: /resources, component: ResourceList }, { path: /resources/:id, component: ResourceDetail }, { path: /publish, component: Publish, meta: { requiresAuth: true } }, { path: /mine, component: Mine, meta: { requiresAuth: true } }, { path: /login, component: Login } ]动态路由的要点在router.addRoute方法。用户登录拿到角色后如果是管理员再动态加入/admin路由。这个方案比把管理员路由直接配进静态路由更安全普通用户根本不知道有后台地址也能减少前端暴露面。路由守卫是另一个重点。我在全局前置守卫里写有requiresAuth标记的路由检查是否已登录没有token就跳转/login已登录但访问/login、/register自动跳回首页守卫里用到了Pinia里的用户状态。这里提醒一句页面刷新后Pinia状态会丢失所以token和用户信息一定要同步存到localStoragestore初始化时再重新读取否则一刷新页面就跳登录页很崩溃。3.3 组件化开发从资源卡片到借用弹窗前后端分离的一大优势就是组件化开发。我把前端页面拆成了几个核心组件每个组件只干一件事。ResourceCard资源卡片组件在列表页和“我的资源”页共用。props接收resource对象内部根据资源状态动态显示标签绿色“可借”、橙色“已预约”、蓝色“借用中”、灰色“已下架”。组件内部用Vue插槽卡片底部可以放不同操作按钮列表页放“查看详情”按钮个人中心放“编辑”“上下架”按钮。BorrowDialog借用组件写得最复杂。它接收resource对象后内部要计算最长可借天数让用户选择借用天数并显示逾期扣分规则。提交后调用借用接口根据后端返回结果显示不同提示。Vue插槽在这里也帮了大忙弹窗的底部按钮区做成插槽确认弹窗和提醒弹窗复用了同一套样式。组件通信我统一用props向下传、emit向上抛复杂状态放进Pinia。刚开始用Vue 3的时候总想用provide/inject做全局状态后来发现对于这个规模的项目props加emit就是最清晰的方式别人接手也能秒懂。3.4 接口封装与跨域处理前后端分离最烦的就是跨域。开发环境用Vite代理最省事在vite.config.js里这么配export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } })Vite代理和Nginx反向代理是同一套思路前端代码里所有请求都发到相对路径/api由代理转发到后端。这样浏览器认为请求是同源的根本不会触发跨域拦截。生产环境Nginx配置也类似location /api那段反代到Spring Boot的8081端口。后端也要多加一道保险CORS配置类里允许本地开发地址跨域这样即使有人绕过前端直接用Postman调试也不会被跨域问题拦路。axios封装还有一个重要细节响应拦截器统一处理后端返回格式。我的后端规定所有接口返回统一结构code、message、data。code为200表示成功401表示token过期403表示无权限。前端拦截器判断code为401时清空本地token并跳转登录页用户不需要在每个页面写重复的错误处理。4. 关键模块实操从资源发布到归还闭环4.1 资源发布与图片上传资源发布表单信息多但真正难搞的是图片上传。我前后实现了三种方案最后选了阿里云OSS。本地磁盘存储方案最简单但服务器磁盘空间有限而且图片不能跨域访问用户换头像后旧图没人清理时间长了磁盘直接打爆。OSS的方案贵一点但一劳永逸前端直传OSS加上后端签名或者后端转发都行。我用的后端签名方案前端请求后端获取上传凭证拿到凭证后直接传到OSS后端不经过图片流减少带宽压力。上传组件的实现逻辑调用GET /api/upload/policy获取OSS签名参数前端用FormData拼装签名参数和文件POST到OSS上传完成后拿到文件的公网URL回填到发布表单提交表单时封面图URL随资源信息一起提交整个流程走通之后体验很好用户选完图秒传完。这里有个大坑OSS的policy过期时间我一开始设了30分钟结果有用户选图磨蹭太久上传时签名过期报错。后来改成前端在调用上传前实时向后端获取签名上传和取签名之间间隔很短问题立刻消失。4.2 检索筛选与列表页优化资源列表页的查询条件有三个关键词模糊搜索、分类筛选、状态筛选。后端接口用MyBatis Plus实现动态SQL不为空的条件自动拼接。这个列表接口涉及分页我用的是MyBatis Plus的Page对象前端传page和size两个参数返回结果包含总记录数、当前页数据。前端列表页做了防抖处理关键词输入停止500毫秒后才发请求避免每敲一个字就请求一次。列表页流畅度有个容易被忽略的点封面图懒加载。社区用户传上来的图片很多是手机拍的几兆大图不处理的话列表页一次加载20张几兆的图手机直接卡死。解决方式有两个一是图片缩略图OSS本身就支持图片处理参数在URL后面拼上?x-oss-processimage/resize,w_400就能指定宽度压缩二是用Vue的懒加载指令进视口才加载。两个叠加列表页性能直接起飞。4.3 借用预约流程与状态机这是整个系统最核心的业务闭环状态机要从前端到后端彻底贯通。用户在资源详情页点“申请借用”填借用天数提交后创建一条状态为“申请中”的借用记录同时资源状态不变还是可借但页面按钮变为“审核中”。资源所有者在个人中心看到申请记录点同意后借用记录变成“已同意”资源状态同步变成“被预约”。从这一刻起其他用户不能申请这个资源前端通过资源状态直接禁用按钮。借用人确认“已取物”后借用记录变成“已取物”资源状态变成“借用中”。系统根据申请时填的借用天数计算出应还时间并启动定时任务监控。还回来的流程是借用人点“我已归还”或所有者点“确认归还”借用记录变成“已归还”资源状态变回“可借”。同时生成一条评价记录双方可互评。整个闭环里最微妙的地方在于“已同意”到“已取物”之间。如果用户申请了但不去取资源就一直被占着对别人不公平所以定时任务扫到超24小时没取物会自动取消资源释放回池子。这个规则我专门在借用弹窗里大字标红提示线下沟通成本少了很多纠纷。4.4 个人中心与信用评价信用分和借用权限直接挂钩。我在个人中心做了两个核心面板第一是“我借出的”列表展示我发布的所有资源的借用记录对应不同的状态标签未处理的申请红色高亮提醒。点“同意”或“拒绝”按钮直接调接口不用进详情页。第二是“我借到的”列表展示我申请的记录。状态为“已取物”时显示“确认归还”按钮点完流程进入已归还。评价功能做成评价优先于流程归还是吧后来发现太容易被恶意评价纠缠最终设计为确认归还后7天内可评价超时自动好评。每次借用完成后双方各得一个信用分变动按时归还加1分逾期扣5分恶意取消扣2分。信用分低于60分用户禁止发布新资源和发起借用申请。这套信用机制完全是社区运营拉着我磨了两次会磨出来的。技术实现不难难的是规则设计。做这种系统一定要多跟真正用的人聊别自己闷头想。5. 部署上线与问题排查实录5.1 本地开发环境版本搭配本地开发环境卡了我两天问题出在版本搭配上。我跟团队说的是这套JDK 8或11Maven 3.6Node.js 16MySQL 5.78.0也行Spring Boot我用的2.7.x对应MyBatis Plus 3.5.x这两个版本配合没问题。有同事问能不能用Spring Boot 3可以但注意MyBatis Plus要换3.5.3以上版本JDK必须17数据库驱动要换成com.mysql.cj.jdbc.Driver一堆细节不一样。如果你不是特别熟第一次做项目还是选稳定组合。Node版本这块也是坑。有同事装了Node 18直接跑Vite 3没问题但Node 22跑某老项目就报OpenSSL错误气得人想砸电脑。开发前统一用.nvmrc锁Node版本团队成员切版本零冲突。5.2 服务器部署与Docker方式部署上线我提供两套方案看你的服务器配置和运维水平。传统部署后端打包成jar包服务器装JDK 8nohup java -jar share-system.jar 跑起来前端npm run build生成dist目录放到Nginx的html目录Nginx配置一个location /api反向代理到8081端口Docker部署编写Dockerfile基础镜像用openjdk:8-jre-alpineJar包放进去前端构建镜像用nginx:alpinedist目录拷贝到镜像用docker-compose编排后端、前端、MySQL三个容器我个人推荐Docker Compose方案特别是MySQL版本管理。传统部署最怕服务器MySQL版本和本地不一致Docker方式镜像版本完全可控。而且Docker迁移方便这台服务器不行了装好Docker直接拉镜像就能换地方。5.3 常见问题速查表做这个项目前后踩了不少坑整理成速查表分享出来都是自己撞过墙才记住的。问题原因解决办法前端请求404后端接口路径和前端不一致打开浏览器Network看请求路径和后端Controller注解一一对照跨域报错开发环境没用代理检查Vite代理配置后端加CORS配置兜底数据库连接失败MySQL 8驱动时区问题JDBC URL加serverTimezoneAsia/ShanghaiSpring Boot启动报端口占用8081被占用lsof -i:8081找进程kill后重启或改端口图片上传成功但访问403OSS权限配置错误检查OSS Bucket权限是否为公共读前端刷新后跳登录页Pinia状态丢失登录状态持久化到localStorage初始化时恢复Maven依赖下载慢默认走中央仓库settings.xml配阿里云镜像定时任务不执行主类漏了EnableScheduling启动类检查注解配置借用记录状态对不上资源状态和记录状态没联动service层统一封装状态流转方法5.4 踩坑心得还有几个心得说给后来人听。数据库表设计一定要把status字段的枚举值注释写清楚。我用数字表示状态后来同事改代码全靠翻文档查0、1、2、3什么意思浪费了大量时间。建议在实体类里定义常量像public static final int STATUS_AVAILABLE 0代码可读性直接翻倍。前端拦截器处理错误提示一定要区分“业务错误”和“系统错误”。业务错误像“该资源已被借走”是正常提示用Message.error弹红色错误框系统错误像500服务器异常同样弹框但措辞要引导用户反馈。一开始统一弹“系统错误”用户完全看不懂为什么不能申请。最后一个心得是接口文档的重要性。我们开发周期紧前后端并行开发后端接口写好先给前端一份Swagger文档前端对着文档mock数据开发这样两边不用等效率提升至少30%。Spring Boot整合SpringDoc不复杂但省下来的沟通成本巨大。回头复盘这个项目我认为最值得学习的不是哪一行代码而是“状态机贯穿前后端”的设计思路。资源状态七种借用状态七种两套状态还要联动同步只要设计阶段把流转图想清楚后面写代码就是机械劳动。反之一上来就写代码写着写着就会发现状态逻辑漏洞百出。另外真诚建议做这种带线下流程的系统一定要给自己留出足够的测试时间。线上借用流程牵扯到用户之间的协调状态一旦调错就会导致线下纠纷。我花了两天专门模拟各种异常场景重复借用、超时未取、逾期不还、取消再申请每一个case都必须跑通。代码里有些细节我可能没写全比如消息通知、数据统计那块核心思路就是监听事件、落库、定时汇总三件套。留给你们自己动手补做出来会印象更深。这套系统后续还能扩展的方向也很多比如接入微信公众号模板消息做借用提醒、增加地图定位实现社区内部“附近好物”推荐、对接第三方信用分。技术栈不变照着这个骨架往里填功能就行。