ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MyBatis+MySQL小区管理系统实战与部署指南

SpringBoot+Vue+MyBatis+MySQL小区管理系统实战与部署指南 前后端分离做了三五年接手的项目没有一百也有八十但每次遇到“SpringBoot Vue MyBatis MySQL”这套组合还是会认真看两眼。原因很简单这套技术栈太典型了——既不像纯JSP老项目那样已经是上个时代的东西又不像微服务那套动不动就走注册中心、配置中心它恰恰是当前中小型管理系统最主流的形态。今天拆的这个综合小区管理系统就是一套非常标准的全套代码后端SpringBoot提供接口前端Vue做单页应用MyBatis负责数据库交互MySQL存数据源码完整、部署链路清晰很适合拿来当实战项目练手或者作为毕业设计、简历项目的底子去改。这篇文章我会把项目架构、核心代码逻辑、数据库设计、部署步骤和排坑经验全部过一遍保证你看完自己就能把这个项目跑起来甚至改造成自己的东西。1. 整体设计与技术选型思路1.1 小区管理系统到底管什么名字叫“综合小区管理系统”本质上就是一个多模块的管理后台核心对象是小区里的“人、房、钱、事”四样东西。先看人业主档案包括业主姓名、手机号、身份证、家庭成员、车辆信息、租客信息等。再看房楼栋、单元、楼层、房号、建筑面积、户型、朝向房子和业主之间是一对多的关系——一个业主名下可以挂好几套房一套房也可能住着业主和租客两个人。然后是钱物业费按面积按月计算水费电费燃气费代收停车费按月或按次收取每一笔钱都要有台账还得支持用Excel导出对账。最后是事报修工单、投诉建议、访客登记、公告通知这类流程性质的数据最考验CRUD功底的细节比如状态流转、处理人分配、时间节点记录。把功能模块列出来基本就是系统管理用户、角色、权限、菜单管理业主管理业主信息维护、房产关联、家庭成员房屋管理楼栋房屋信息、售租状态维护车位管理固定车位、临停车位、车辆进出记录收费管理物业费、水电费、停车费账单生成与收缴报修管理业主报修、物业派单、维修完成确认公告管理小区公告、通知公告发布与展示数据统计缴费率、报修量、收支汇总等基础统计图我之所以先列功能清单是因为后面所有技术选型、数据库设计、接口划分都是围着这些功能转的。你看它和常见的电商后台、OA系统高度相似这就是为什么这种项目适合当练习样本——把一套吃透了其他系统都是换皮。1.2 为什么是SpringBoot Vue这套组合说实话这套组合不是最“炫”的——没有微服务、没有高并发中间件但它非常务实。SpringBoot最大的价值不是技术多牛而是把Spring那套繁琐的配置几乎全部替你干完了。你不需要再写一堆XML配置文件也不需要纠结Tomcat怎么集成打包直接就是一个可运行的Jar包这对部署来说是大解放。Vue目前仍然是国内前端市场占有率最高的框架之一而且它对后端出身的人特别友好单文件组件SFC把HTML、JS、CSS写在一个.vue文件里模板语法又非常接近HTML语义学习曲线不算陡。更关键的是Vue的生态足够成熟——Element UI一个组件库把后台管理系统需要的表格、表单、弹窗、树形控件全包了开发效率非常客观。MyBatis的选择也很有意思。搞Java的都知道JPA也好、MyBatis-Plus也好在SQL可控性上都不如原生MyBatis来得直接。小区管理系统里有大量报表类查询比如按月统计各楼栋的物业费收缴情况、统计报修单的处理时长这种SQL用MyBatis写动态SQL非常舒服每个条件可以精确控制出问题也好排查。MySQL这家RDBMS就不用多说了开源、稳定、学习资料多、部署成本低。这套项目里用到MySQL 5.7或者8.0都没问题只要能跑通一组标准的建库建表脚本就行。1.3 前后端分离架构解决什么问题我说一个最直观的感受没有前后端分离的年代每个页面都几乎是一个“孤岛”——后端把数据塞进ModelAndView前端拿到的是渲染好的HTML一旦页面交互复杂起来前端脚本会非常臃肿后端模板也会越来越难维护。前后端分离之后后端关心的是“提供什么数据”前端关心的是“如何展示和交互”两者的边界非常清晰。对开发节奏的提升也很明显。前端改页面再也不用等后端重启后端加接口也不影响前端开发。两边只需要约定好接口的URL、入参和返回值就可以并行推进。实际在这个项目里联调阶段只需要对一下接口文档双方各自修改Bug就行效率比老式的JSP试错方式高出一个量级。代价也不是没有。最大的坑就是跨域问题前端页面跑在Nginx上接口跑在SpringBoot上浏览器安全策略会拦截跨域请求。解决方案后面会细说这里先记住一个核心原则——接口这一侧统一配置CORS或者用Nginx反向代理把/api开头的请求转发到后端服务这两个都是成熟做法。2. 数据库设计与后端核心实现2.1 数据表结构与关系梳理这个项目的数据库设计并不复杂但你一定要把表之间的关系理清楚不然写到联表查询时会非常痛苦。我在纸上先把核心表列一下大概就几张sys_user系统登录用户字段有user_id, username, password, real_name, role_id, statussys_role角色表一个用户对应一个角色简化版本不需要做多对多owner_info业主信息表owner_id, owner_name, phone, id_card, house_id, member_counthouse_info房屋表house_id, building_no, unit_no, floor_no, room_no, area, owner_idhouse_owner_rel很推荐的一张关联表因为一套房子可能被买卖、出租业主和房屋的关系其实是多对多的单独建关联表后期会很省事property_fee物业费账单fee_id, house_id, period账期amount, status(未缴/已缴)pay_timerepair_order报修单order_id, house_id, reporter_id, content, status, assignee, create_time, finish_timeparking_space车位表space_id, position_no, type(固定/临时), owner_id, statusnotice_info公告notice_id, title, content, publish_time, publisher设计取舍上有一个点很值得说。权限这块如果只是做毕业设计或者个人项目用“用户表里存一个角色ID”就够了不要一开始就上RBAC五张表。等你要往简历项目上写、想面试时强调“权限设计”的时候再扩展成用户-角色-菜单三张表加两张关联表那才是个加分点。这个项目的完整版里做了用户、角色、菜单三张表的权限模型菜单用父子层级表示前端根据路由表动态生成菜单算是够用的方案。字段类型上注意几点金额字段不要用float/double会有精度问题要用DECIMAL(10,2)联系电话这类用VARCHAR(20)身份证号用VARCHAR(18)这些看着是细节实际写代码时经常在这里翻车。2.2 SpringBoot后端工程结构拿到源码后第一件事是先看包结构这个项目的包结构完全按照三层架构来分的com.example.community ├── controller # 接口层接收HTTP请求返回JSON ├── service # 业务层写业务逻辑 │ └── impl ├── mapper # 数据访问层接口对应MyBatis Mapper ├── entity # 数据库实体类对应每张表 ├── dto # 请求/响应数据传输对象 ├── config # 配置类比如跨域配置、拦截器配置 ├── utils # 通用工具类JWT工具、日期工具等 └── common # 统一返回结果封装全局异常处理这套分层虽然老生常谈但它是经过大量项目验证的。Controller层一定不能写业务代码只负责接收参数、调用Service、包装返回Service层写事务逻辑比如生成物业费账单时要先检查房屋状态再插入账单记录这些都要用Transactional包起来Mapper层只做数据库交互一个方法对应一个SQL操作。项目里统一返回结果类我建议一定保留不要图省事每个接口都直接返回Map。正常项目里会定义一个Result类里面包含code、msg、data三个字段比如Result.success(data)、Result.error(参数错误)。这样做的好处是前端axios拦截器可以统一判断code不需要在每个页面里处理错误分支。2.3 MyBatis关键配置与动态SQL实战MyBatis在这套项目里的核心配置有几个地方容易踩坑。第一个是application.yml里数据库连接和驼峰映射的配置spring: datasource: url: jdbc:mysql://127.0.0.1:3306/community_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.community.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case: true这一行特别关键。数据库字段是house_idJava实体属性是houseId不开启驼峰映射的话查询结果就是null而且这种坑很隐蔽——启动不报错、查询不报错就是字段值全是空的。我见过太多新手在这个问题上卡一两天。第二个重点是MyBatis的动态SQL。比如报修订单的列表查询需要支持按状态筛选、按关键字搜索、按时间范围搜索条件组合不确定就必须用动态SQLselect idselectRepairList resultTypecom.example.community.dto.RepairDTO select * from repair_order where if teststatus ! null and status ! and status #{status} /if if testkeyword ! null and keyword ! and (house_no like concat(%, #{keyword}, %) or reporter_name like concat(%, #{keyword}, %)) /if if teststartTime ! null and create_time gt; #{startTime} /if if testendTime ! null and create_time lt; #{endTime} /if /where order by create_time desc /selectwhere标签会自动处理第一个条件前面的and这个特性在模糊搜索组合场景下是救命的。另外注意concat(%, #{keyword}, %)这种写法是为了防止SQL注入永远不要用字符串拼接的方式组装SQL条件。2.4 登录认证与权限拦截的实现思路登录这块完整版做的是JWT方案流程也不复杂用户提交用户名密码后端校验通过后用用户ID和角色信息生成一个token返回给前端前端把token存在localStorage里每次请求时放在HTTP请求头的Authorization字段里后端写一个拦截器在请求进入Controller之前先校验token校验失败直接返回401。这个拦截器是核心用HandlerInterceptor实现的最简单的版本public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains(/api/login)) { return true; } String token request.getHeader(Authorization); // 校验token不通过则返回401 return JwtUtil.verifyToken(token, response); } }权限控制则叠一层角色判断比如收费管理的接口只允许ADMIN角色访问业主端接口放开给普通业主用户。在拦截器里拿到token解析出的角色对比当前接口要求的权限码即可。这种设计已经能覆盖多数管理系统的权限需求开发成本低、面试也能讲得清楚。3. 前端Vue实现与联调细节3.1 Vue工程搭建与目录规划前端用的是Vue 2 Element UI的组合虽然Vue 3已经出了很多年但Vue 2的存量项目和资料量依然巨大选择它并不落伍遇到问题也更容易搜到解决方案。工程目录是标准的vue-cli脚手架src ├── api # 所有接口请求封装按模块拆文件 ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # Vuex状态管理 ├── views # 页面视图组件 └── App.vue我特别想强调api目录的重要性。很多新手习惯在页面里直接写axios.get(http://localhost:8080/api/xxx)这是坏习惯。正确做法是把每个接口都封装成函数统一放在api文件夹里比如api/fee.js里写import request from /utils/request export function getFeeList(params) { return request({ url: /fee/list, method: get, params }) }这样做好处非常多接口路径集中管理、每个模块的接口一目了然、以后后端接口改动时只需改一个文件不用满项目翻字符串。这个习惯对我的提升非常明显早期项目里接口散落各处每次联调改路径都是噩梦。3.2 路由、登录态与Vuex状态管理路由配置分两块静态路由和动态路由。静态路由只有登录页和404页其他页面都由动态路由生成。登录成功拿到当前用户的角色和权限码前端根据权限码动态拼接出用户可以访问的路由表再用router.addRoutes()注册路由。这也是动态菜单的实现原理——管理端看到“收费管理”业主端看不到因为路由表本来就不一样。Vuex在这里存三个关键东西token、用户信息、动态生成的路由表。刷新页面时Vuex数据会丢失所以需要从localStorage里再读一次token并重新拉取用户信息、重新生成路由。这个处理不当会导致一个经典问题用户刷新页面后白屏或者跳回登录页。解决思路是自己写一个init方法放在路由守卫里每次导航前检查store里有没有用户信息没有就从localStorage恢复。导航守卫的作用很直接在router.beforeEach里做登录拦截router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token to.path /login) { next(/) } else { next() } })这个版本的守卫逻辑简单清晰。需要细化的地方是要同时校验“token是否存在”和“用户信息是否已加载”如果token在但用户信息没了要去拉用户信息才能继续跳转否则动态路由还没生成直接跳转会白屏。3.3 axios封装与跨域联调axios封装是每次前后端联调的基石。项目中utils/request.js这个文件的职责很明确创建axios实例、配置baseURL、设置请求拦截器自动带上token、响应拦截器统一处理业务错误码和HTTP错误码。响应拦截器里最常用的逻辑是如果code为401说明登录过期清掉本地登录信息并跳转到登录页。关于跨域开发阶段和后端联调时可以直接使用Vue CLI自带的代理功能在vue.config.js里配置module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端请求/api/login就等价于请求http://localhost:8080/api/login浏览器看起来是同一个源不存在跨域。后端层面的跨域配置也要同步加上两套方案一起做部署到生产环境时由Nginx反向代理接管就不会有跨域问题。3.4 典型列表页面的实现逻辑拿业主列表页来说这个页面的实现模式几乎可以复制到项目里的所有模块。页面上有三样东西搜索条件栏、表格、分页器。搜索栏的表单数据就是查询参数点击“查询”按钮时重新调用列表接口表格部分直接用el-table绑定从接口返回的数组分页器的当前页和每页条数都作为查询参数传给后端。前端需要和后端协商统一的返回结构。这套项目的分页返回格式是{ code: 200, data: { list: [], total: 100 } }。前端axios响应拦截器解包后页面拿到的就是data.list和data.total。这类规范只要在项目开始时定下来后面每个页面写起来都很快。表单弹窗是另一个核心模式新增和编辑共用一个el-dialog进入编辑时把当前行的数据深拷贝到表单对象里打开新增时清空表单。提交时根据form.id是否存在来判断是新增还是更新对应调用不同的接口。这个套路在管理系统里出现频率极高熟练了之后做功能开发速度会很快。4. 从源码到上线完整部署教程4.1 部署环境准备清单工程要在本地跑起来需要先装好几样基础环境我把版本和用途列在下面软件推荐版本用途JDK1.8 及以上推荐1.8编译运行SpringBoot项目Maven3.6后端依赖管理与打包Node.js14前端依赖安装与构建MySQL5.7 / 8.0数据库存储Nginx1.18托管前端静态文件、反向代理接口Navicat或MySQL命令行任意执行SQL脚本、检查数据版本匹配上有几个容易踩的坑。JDK不要装太新的版本如果用的SpringBoot 2.xJDK 11或者8都合适JDK 17以上有些老版本的Spring依赖会出兼容问题。MySQL 8.0的驱动是com.mysql.cj.jdbc.DriverMySQL 5.7用com.mysql.jdbc.Driver或者直接都用cj驱动但注意8.0的url里必须要带serverTimezone参数否则会报时区错误。4.2 数据库初始化与导入数据库这步最简单也最不能出岔子。打开Navicat新建数据库命名为community_db字符集选择utf8mb4排序规则选utf8mb4_general_ci然后把项目里的sql/community_db.sql文件拖进去执行。这里我提醒一句一定要看清楚SQL脚本最后有没有默认数据尤其是管理员账号。如果脚本里没有你自己要手动插入一条用户不然登录永远进不去。执行成功之后建议先随便查两张表确认有数据比如SELECT * FROM sys_user; SELECT * FROM house_info LIMIT 5;这能快速验证表结构是否完整、默认数据是否导进去了。4.3 后端打包与启动后端是标准的Maven工程打包命令极其简单。在项目根目录有pom.xml的那个目录执行mvn clean package -DskipTests这个命令会跳过单元测试直接打包如果测试代码写得有问题会卡住打包流程。打包完成后在target目录下会生成一个community-server.jar。启动命令java -jar target/community-server.jar或者放后台运行Linux环境下nohup java -jar community-server.jar server.log 21 启动成功会看到SpringBoot的Logo和控制台日志。验证接口是否可以访问随便试一个curl http://localhost:8080/api/login返回JSON错误信息就说明服务已经起来了。需要注意端口是否被占用默认是8080如果被占了去application.yml里改server.port。4.4 前端构建与Nginx托管前端构建也非常简单在项目根目录有package.json的那个目录执行npm install npm run buildnpm install首次执行会慢一些网络差的话可以用淘宝镜像npm config set registry https://registry.npmmirror.com加速。构建完成后生成dist目录里面是纯静态文件——index.html、css文件、js文件。这一步的核心思路就是把那堆静态文件交给Nginx托管Nginx同时负责把/api的请求代理给后端服务。我在nginx/conf/nginx.conf里的配置通常是这样的server { listen 80; server_name localhost; # 前端静态文件 location / { root /usr/local/nginx/html/community; index index.html; try_files $uri $uri/ /index.html; # 解决history路由404 } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配置里最关键的就是try_files $uri $uri/ /index.html;这一行。如果项目采用的是history模式路由刷新一个子页面比如/fee/list时Nginx会在静态目录里找/fee/list这个文件找不到就404了。加了这个配置之后找不到文件时Nginx统一返回index.htmlVue接管路由自己匹配页面404问题彻底解决。配置好之后重新加载Nginxnginx -t nginx -s reload浏览器访问http://localhost输管理员账号密码能进系统就算部署成功。整套流程走下来前后端分离项目从源码到线上运行大概半小时就能搞定。4.5 部署方案对比单机还是容器化如果只是本地部署或者放一台云服务器上面这种Nginx Jar包的方案是最省事的。维护成本低排查问题直接看日志。但如果你想把项目做得更规范一点可以用Docker来跑MySQL、SpringBoot、Nginx三件套。我提供一个思路每个服务一个Docker容器用docker-compose.yml在一台机器上编排好处是环境隔离数据库配置、Java运行环境都不需要再手动安装。不过对于大多数场景我建议先按非容器化的方式跑通一遍。因为容器化虽然漂亮但排错门槛高一些。比如容器里的时区问题、MySQL容器数据持久化问题、网络模型问题没经验的话坑也不少。先把裸部署流程吃透再上容器这样遇到问题至少知道是哪一层出的问题。5. 高频踩坑与排查经验实录5.1 跨域请求失败的排查思路现象前端打开页面后所有请求都报blocked by CORS policy或者浏览器控制台出现Response to preflight request doesnt pass access control check。这是前后端分离项目里出场率最高的Bug没有之一。排查顺序我建议先看后端是不是Controller或全局配置里打了CrossOrigin注解或者专门配置了CorsFilter。如果没有最快的方式是加一个全局CORS配置类Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }注意addAllowedOriginPattern(*)和setAllowCredentials(true)一起用的时候不要用addAllowedOrigin(*)有些Spring版本会直接拦截这个组合。如果后端配置没问题那就看Nginx的反向代理是否生效确认请求是不是真正打到了后端端口上。5.2 刷新页面后404现象登录页能进主页面能进但一旦刷新/house/list之类的子路由页面一片空白或者404。原因基本就是history模式与Nginx静态服务不兼容。检查nginx.conf里有没有try_files那行。如果项目里使用的是hash模式路由则没有这个问题但URL会带/#/。生产环境我更推荐history模式配合try_files解决刷新问题URL美观也贴近真实项目习惯。还有一个小坑try_files $uri $uri/ /index.html;这行配置里的路径如果前端静态文件不是放在Nginx的html根目录下root路径要和实际路径完全一致否则首页都打不开。5.3 数据库连接报错一堆现象后端启动时报UnknownHostException、The server time zone value XXX is unrecognized、或者Access denied for user。这类报错90%是application.yml配置问题。我遇到过的情况有这几类密码包含特殊字符比如、$在yaml里没有加引号导致解析出错MySQL 8.0没加serverTimezoneAsia/Shanghai参数报时区错误用MySQL 5.7却加了cj驱动或者反过来驱动版本不匹配数据库用户名没有远程访问权限只允许localhost连接最直接的排查方式是把application.yml里的配置挨个核对一遍然后用Navicat手动连接一次能连上再跑后端。能连上说明配置和数据库都没问题连不上就照着提示一项项改。5.4 MyBatis查询结果字段全是null这个坑我在2.3里已经提前说了一遍但排查时太容易忽略值得单拎出来强调。SQL执行没报错查询结果有记录但Java对象里的字段就是null。第一反应看实体类字段名和数据库字段名是否一一对应再看application.yml里有没有开启map-underscore-to-camel-case: true。如果不开启就要在XML里每个字段手动写别名或者用resultMap来映射那工作量直接翻倍。建议从一开始就把这个配置加上。5.5 SpringBoot端口被占用现象后端启动提示Port 8080 was already in use。排查方式# 查找占用8080的进程 netstat -ano | grep 8080拿到进程ID后杀掉即可。也可以直接换端口改server.port。如果项目要部署到环境上建议用端口大于10000的比如18080避开一些常见软件占用的端口。5.6 前端构建报错npm install阶段最常遇到两类问题。一类是依赖包下载超时解决办法是换淘宝镜像另一类是版本冲突比如Node版本过高导致node-sass安装失败解决办法是把Node降到项目要求的版本很多老Vue项目要求Node 14或16或者改用vue-cli-service对应的官方依赖版本。还有一类是npm run build时内存溢出会看到JavaScript heap out of memory的报错这时给Node加大内存NODE_OPTIONS--max-old-space-size4096 npm run build这个命令在Linux和Windows的cmd里写法略有区别Windows上可以先设置环境变量再执行构建。5.7 部署上线后的性能体感优化项目跑通之后如果感觉页面加载偏慢我建议做三件性价比很高的事。第一前端路由懒加载——每个页面组件单独打包成JS文件而不是全部塞进一个app.js里首屏体积能小一半还多。第二后端给查询频率高的接口加缓存比如公告列表、房屋下拉列表这种用简单的Caffeine本地缓存就够。第三MySQL里给常用的where条件的字段建索引比如house_info.owner_id、property_fee.house_id、repair_order.status这几个索引字段能明显加快查询速度。这三件事做完即使是几百户的小区数据量体验也会非常流畅。而且这些优化点都能写进简历项目描述里面试时能讲出“为什么要做”和“怎么做”的细节比只说“参与了开发”要有说服力得多。最后分享一点个人经验说句实在话这类管理系统项目在技术难度上不算高它考验的更多是工程规范性和细节处理能力。我见过太多人拿到源码只会闷头运行跑通就算完事。我更建议你按这个顺序去消化项目第一遍把项目跑起来熟悉整个链路如何串联第二遍断点调试核心流程比如登录后JWT如何生成和校验、缴费单生成的事务逻辑第三遍尝试自己加一个模块比如“访客管理”从建表、写接口、写页面全流程过一遍。三轮下来你对SpringBoot Vue这套体系的掌握程度会远超只会背面试题的人。等到你面试别人问“项目里怎么解决跨域”“动态路由怎么做的”“为什么MyBatis慢查询查不出数据”你都能从实际经历里举例子回答这才是这套源码真正值钱的地方。
返回列表