恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

SpringBoot+Vue房屋租赁管理系统:从数据库设计到部署全解析

  • 首页
  • 资讯中心
  • /
  • SpringBoot+Vue房屋租赁管理系统:从数据库设计到部署全解析

相关资讯

Prompt工程在接口测试中的实战:从用例生成到缺陷分析 2026/10/7 21:40:39
Claude Code深度配置指南:VS Code中安全执行AI代码的底层逻辑 2026/10/7 21:40:39
DevExpress VCL 20.2.4 for RAD Studio 10.4 安装配置与老项目维护指南 2026/10/7 21:40:39

最新资讯

AI Agent超时重试与状态清理的工程实践与踩坑指南
RL-10-TD算法-ActorCritic04-连续动作控制02:DDPG02【在线/目标Actor、在线/目标Critic、Replay Buffer、Noise与深度网络的完整作用】
openGym首次启动全流程:从克隆仓库到创建第一个Passkey档案
三极管恒流源电路设计:五种经典方案详解与选型指南
29个中文AI技能合集:从Chat到Agent,让AI真正会干活
Deep Research与Super Agent Harness:DeerFlow 2.0的Agent工程化实践

今日推荐

SSD不认盘怎么修?金士顿SV300板级排查与短接ROM进工厂模式
Unity 3D RPG开发:C#状态机与物理更新时机实战指南
AIoT开发工程师岗位全景:从嵌入式Linux到边缘计算与端侧AI部署

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

SpringBoot+Vue房屋租赁管理系统:从数据库设计到部署全解析

发布时间:2026/10/7 21:40:39
SpringBoot+Vue房屋租赁管理系统:从数据库设计到部署全解析 做管理类系统最怕什么不是功能不够多而是做出来没人用。前阵子一个做二房东生意的朋友找我说自己手里的房源、租客、合同全记在Excel里押金抵扣、水电分摊、合同到期全靠脑子记结果连续出了两起“房子明明租出去了又带人看房”的尴尬事。我帮他梳理完需求后直接给他做了一套基于SpringBootVue的房屋租赁管理系统。这套系统用Java做后端、MySQL存数据、MyBatis做持久层最终交付了完整源码前端配合Vue写管理后台从房源上架、租客签约到账单生成、到期提醒全部打通。这篇文章我不打算只贴代码。我会把整套系统的需求拆解、技术选型逻辑、数据库表设计、后端核心逻辑、前端页面落地、以及源码从环境准备到跑通的全过程都讲透。正在做毕业设计、课程设计或者想接私活但没有系统做过前后端分离项目的朋友可以按这篇文章的路线直接复现。如果你只是想把项目跑起来第六节的踩坑记录和运行步骤也足够让你少走很多弯路。1. 租房管理这个场景比你想的更复杂需求拆解与模块划分1.1 一个真实业务里的“隐藏需求”很多第一次做管理系统的朋友一听到“房屋租赁”这四个字第一反应就是“房源增删改查、合同增删改查、完事了”。真去做才发现租赁业务是个典型的“数据相互纠缠”场景。我朋友的实际工作流是这样的他手上有二十多套房每套房有不同的租金、押金、付款方式租客有的月初交房租有的按季度交还有几套房子最近到期了要重新挂牌。平时最常干的事是查某套房“现在租给谁了、合同到什么时候、还有多少押金没退”以及月底统计“这个月收了多少钱”。这里有几个非常容易被忽略的点合同到期不是静态的需要持续提醒。如果等到期那天再处理房子大概率已经空置了一两周。押金和租金是两套逻辑。押金是“可退还”的退租时还要考虑水电欠费、物品损坏不能和租金混在一起。同一个房源在同一时间只能有一个有效合同。这在人脑记录时代没事但一旦系统做不好并发控制就会出现“一房多租”的严重问题。账单不是合同签了就结束而是每个月都要生成一次逾期了还要有状态标记。我让朋友把这些流程全部写下来然后做成一张简单的表格才真正定义了系统的功能范围。1.2 角色与功能矩阵三类人、三条不同的使用路径房屋租赁管理系统不是只有“房东”一个角色在用的。不同角色使用的路径完全不同这决定了权限设计必须一开始就想清楚。角色核心诉求主要操作系统管理员管理整个系统分配账号、查看全局数据用户管理、角色配置、数据统计、日志查看房东/业务管理员管房源、管合同、管钱房源发布与下架、合同签约/退租、账单生成、收款登记、报修处理租客看房、签约、交租、报修浏览房源、提交租房申请、查看自己的合同与账单、提交报修我做这套系统时把租客端做成了轻量版不需要太多花哨页面核心就三个我要看房、我签了什么合同、我这个月要交多少钱。房东端反而是功能最重的每个房源的状态要随时清楚每个合同的进度要可追溯账单要有“已出账、已收款、已逾期”三态。1.3 第一版先做什么模块边界与优先级很多小白做项目喜欢一开始就把功能堆满结果做到一半发现维护不了。我的建议是第一版只做“业务闭环”。什么是闭环就是“房源出现 → 被租客看到 → 签约 → 生成账单 → 收款 → 到期退房 → 房源重新上架”这一整条链路都能在系统里跑通。第一版我建议包含这些模块房源管理发布、编辑、上下架、状态流转未出租/已出租/锁定/维修租客信息管理租客档案维护与合同关联合同管理新建签约、到期处理、退租、押金退还登记账单管理按合同周期生成账单、收款登记、逾期标记用户与权限登录、角色区分至于数据看板、报表导出、在线支付、短信通知这些属于“有了能加分没有也不影响跑通”的模块放到第二期再迭代不迟。做项目最忌讳的就是第一版想做成完美产品最后连核心流程都没跑通。2. 技术选型不是跟风SpringBootVueMyBatis组合凭什么能打2.1 为什么是SpringBoot而不是传统SSH早些年做Java Web项目SSHSpring SpringMVC Hibernate是主流但配置地狱真的不是开玩笑的XML配置文件一个接一个Tomcat要手动部署依赖版本对不上就要折腾半天。SpringBoot最核心的价值是“约定大于配置”。它内嵌了Tomcat启动就是main方法跑起来依赖管理用starter机制引入一个依赖连带把配套版本问题都解决掉自动配置帮你把数据源、事务管理器、Web容器全部装配好你只需要关心自己的业务代码。对于这种规模的管理系统SpringBoot几乎是当前阶段综合成本最低的方案。有朋友问我为什么不直接上Spring Cloud微服务我的回答很简单一个单体应用能解决的问题强行拆微服务只会让你的开发成本、运维成本和排查问题成本全部翻倍。2.2 MyBatis与Spring Data JPA的取舍这个灵魂拷问几乎每个做Java后端的人都经历过。我在这套系统里用了MyBatis理由是具体的管理类系统的查询条件非常灵活比如“根据城市、价格区间、面积区间、出租状态筛选房源”这种多条件动态SQL是MyBatis的强项。你可以把SQL完全掌控在自己手里写where和if标签做动态拼接性能出了问题也一眼能看穿。Spring Data JPA适合实体关系极其复杂的领域建模比如那种几十个实体互相引用的系统。但租赁管理系统说白了核心就是用户、房源、合同、账单这几张表领域模型并不复杂用JPA反而会因为对象映射的隐式查询让人看不懂SQL是怎么生成的。另外我直接用了MyBatis-Plus。它不是把MyBatis替换掉而是在MyBatis之上提供了通用Mapper、分页插件、条件构造器这些效率工具。对于一个100行代码就能写完的CRUD接口你用原生MyBatis要写XML、写接口、写参数映射用MyBatis-Plus一行selectPage就搞定了。2.3 Vue版本怎么选Vue2还是Vue3这是个只能给个人建议的话题。如果你刚学Vue我的建议是直接用Vue3 Element Plus Vite原因很简单Vite启动速度快到离谱开发调试体验比Webpack时代的Vue CLI好非常多。Element Plus是Element UI的Vue3版本管理后台的表格、表单、弹窗组件全部现成。Vue3的组合式API写业务逻辑更集中一个房源管理页面的筛选条件、分页参数、数据请求写在同一个setup片段里逻辑虽然长但都在一个视野内。如果你项目要求必须用Vue2那也没问题搭配Element UI和Vue CLI也一样能跑。只是从维护角度Vue3会是你2025年以后更舒服的选择。2.4 核心依赖清单pom.xml和package.json里有什么后端pom.xml里我主要引入了这些依赖dependencies 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 groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency /dependencies前端package.json里的关键依赖也列一下{ dependencies: { vue: ^3.3.4, vue-router: ^4.2.4, pinia: ^2.1.4, axios: ^1.5.0, element-plus: ^2.4.2 }, devDependencies: { vite: ^4.4.9, vitejs/plugin-vue: ^4.2.3 } }这里有个细节jjwt 0.9.1这个版本在JDK 8之后因为Java模块化的问题需要额外引入javax.xml.bind依赖很多人在这踩坑。如果你不想折腾可以直接换用io.jsonwebtoken:jjwt-api、jjwt-impl、jjwt-jackson这套0.11.x系列的三个依赖。我自己的项目里为了省事最终用了0.11.5版本。3. 数据库模型设计租赁业务的数据表怎么定才不走弯路3.1 核心表梳理与关系做数据库设计先画关系再定字段。租赁管理系统里最核心的几张表以及它们的关系是这样的user账号表存放所有能登录系统的人。通过role字段区分管理员、房东、租客。house房源表每条记录是一套可以出租的房子。contract租赁合同表一次签约对应一条记录。bill账单表一个合同在租赁期间会生成多条账单每月一条。再加上一个repair报修表处理租客提交的维修需求。关系上user和house是一对多同一个房东可以有多套房house和contract是一对多一套房在不同历史时期可以有多条合同contract和bill是一对多一份合同有多个账单。这里最容易被忽略的是house和contract并不是一对一而是一对多。因为一套房子会经历多次租赁周期每次签约都会生成新的合同记录。如果你设计成一对一历史合同全都查不到了。3.2 合同表必须做“数据快照”这个设计救了我一次很多初学数据库设计的人会把合同表设计成只存house_id和user_id租金、押金这些字段觉得“从房源表查就行”。这是典型的错误。我在这套系统里给合同表做了“数据快照”也就是把签约时刻的关键业务数据直接冗余存储到合同表monthly_rent签约时的月租金deposit签约押金settlement_rule结算规则比如“押一付三”rent_period租期contract_start_date/contract_end_date起止日期为什么必须这样做因为房源表里的租金是动态的。我今天把一套房的月租金从2000涨到2200之前所有已签约合同的价格不能跟着变。如果合同价格实时从房源表读取那房东一改价历史合同全部“被涨价”这就是严重的数据事故。这个思路跟电商系统的“订单快照”是一个道理你下单时商品是什么价格、什么标题、什么描述就要原样存进订单里之后商品怎么改都不影响已经生成的订单。租赁合同就是租赁场景里的“订单”。3.3 状态字段用整数还是字符串以及索引设计状态字段是这类管理系统的灵魂。我的习惯是用tinyint存状态码然后在Java代码里用枚举做映射而不是直接在数据库里存“已出租”“未出租”这种中文字符串。原因有两点一是中文字符串占空间且容易改出笔误二是在代码里用枚举做业务逻辑判断比字符串比较更安全。我用的状态定义如下house.status0未出租1已出租2已锁定暂停出租3维修中contract.status0生效中1已到期2已退租3已解约bill.status0待收款1已收款2已逾期状态字段建议加上索引。因为“查所有未出租的房源”“查所有生效中的合同”“查所有已逾期的账单”是这类系统最高频的查询场景不加索引表数据一多立刻卡住。数据库字符集直接设为utf8mb4而不是utf8否则房源描述里出现一个生僻字或者Emoji表情就写入失败。我早期用utf8吃过这个亏页面显示什么都没问题但租客备注里填一个Emoji就直接报Incorrect string value排查了半天。3.4 三个高频SQL分页筛选、到期提醒、账单统计房源的多条件分页筛选是后台最常用的接口核心SQL长这样SELECT id, title, address, area, monthly_rent, status, cover_image FROM house WHERE status #{status} AND monthly_rent BETWEEN #{minPrice} AND #{maxPrice} AND area #{minArea} AND address LIKE CONCAT(%, #{keyword}, %) ORDER BY create_time DESC合同到期提醒的SQL我通过DATEDIFF计算剩余天数筛选出30天内到期的生效合同SELECT c.id, c.contract_no, c.contract_start_date, c.contract_end_date FROM contract c WHERE c.status 0 AND DATEDIFF(c.contract_end_date, CURDATE()) BETWEEN 0 AND 30账单按月统计用于房东的财务总览SELECT DATE_FORMAT(bill_date, %Y-%m) AS month, SUM(amount) AS total_amount, SUM(CASE WHEN status 1 THEN amount ELSE 0 END) AS received_amount FROM bill WHERE create_time 2024-01-01 GROUP BY DATE_FORMAT(bill_date, %Y-%m) ORDER BY month4. 后端核心逻辑认证、事务、并发与状态流转4.1 登录认证为什么直接用JWT而不是Session前后端分离之后最尴尬的问题就是Session的会话管理。如果后端部署在一个域名前端部署在另一个域名或者移动端也要接接口用Session就需要处理跨域共享、Cookie携带等一系列问题。JWT的方案是登录成功后服务端签发一个携带用户信息的加密Token前端每次请求都带上这个Token后端通过拦截器解析Token确认用户身份。JWT的核心是“无状态”。服务端不需要保存会话记录Token本身就是一个自包含的数据包里面包含了用户ID、角色、过期时间。验证时只需要做签名校验和过期时间判断。在这套系统里我用两个拦截器配合实现认证流程JwtAuthenticationFilter解析请求头里的Authorization拿到Token后解析出用户信息放入SecurityContextHolder。JwtAuthorizationFilter校验Token是否过期、用户是否有效。登录接口的密码校验不能拿明文比数据库里存的是BCryptPasswordEncoder加密后的密码。BCrypt比MD5的优势在于加密时自动生成随机盐即使用户密码相同两次加密结果也不同能有效防御彩虹表攻击。我见过一些项目用MD5加固定盐其实安全性远低于BCrypt。4.2 合同创建的并发问题这个坑差点造成“一房两租”合同创建是整个系统里最容易出事的地方。业务逻辑看起来很简单校验房子状态是未出租然后创建合同把房子状态改成已出租。但如果你直接这样写House house houseService.getById(houseId); if (house.getStatus() ! 0) { throw new BusinessException(该房源已被出租); } Contract contract buildContract(...); contractService.save(contract); house.setStatus(1); houseService.updateById(house);这段代码在单线程测试下没有任何问题但真实场景下如果有两个管理员同时操作或者一个接口被前端重复提交会怎样请求A读房子状态发现是0准备创建合同。请求B读房子状态发现也是0也准备创建合同。A创建合同成功B也创建合同成功同一套房出现两条生效合同。这就叫并发竞态解决的方案是“先检查后更新”必须放在同一个事务里并且更新操作要带上状态条件。我把房源状态更新SQL改成这样UPDATE house SET status 1 WHERE id #{houseId} AND status 0这条SQL返回影响行数如果影响行数为0说明房源已经被抢占了直接抛异常回滚。这就是典型的乐观锁思路。然后整个流程加上Transactional一旦中途异常合同和房源状态一起回滚。另外一个容易踩的坑Transactional注解的失效问题。当你在同一个类内部通过this调用事务方法时事务注解是不生效的因为Spring事务基于代理对象内部自调用不经过代理。我一开始就把创建合同的方法写在ContractServiceImpl里然后直接调同一个类的私有方法结果回滚就是不生效。正确的做法是要么把事务方法拆到另一个Bean里面要么在调用处通过注入的代理类调用。4.3 合同到期的自动处理定时任务加查询兜底合同到期后状态要从“生效中”变成“已到期”房源状态要恢复成“未出租”。这个动作可以靠定时任务跑也可以靠查询时判断。我的方案是两者结合每日凌晨1点通过Scheduled(cron 0 0 1 * * ?)执行批量任务Scheduled(cron 0 0 1 * * ?) Transactional public void processExpiredContracts() { ListContract expiredList contractMapper.selectExpired(); for (Contract contract : expiredList) { contract.setStatus(1); contractMapper.updateById(contract); House house houseService.getById(contract.getHouseId()); house.setStatus(0); houseService.updateById(house); } }同时在查询接口里做兜底每次查房源列表如果发现某套已出租的房子其合同到期日已经过去立刻纠正状态。这是为了防止定时任务漏跑或者服务器在那段时间停机。定时任务的粒度处理也要注意如果合同是月底到期而系统1点就跑任务那当天凌晨合同就被置为到期了。这在业务上其实是可接受的但如果你不想这样可以用“当前时间大于合同结束时间”作为判断条件而不是“当天是否到期”。4.4 一个完整接口的代码示例创建合同加生成首月账单我把创建合同这个核心接口完整贴出来你可以直接参考这个分层写法Service public class ContractServiceImpl implements ContractService { Autowired private HouseMapper houseMapper; Autowired private ContractMapper contractMapper; Autowired private BillMapper billMapper; Override Transactional(rollbackFor Exception.class) public Long createContract(ContractCreateRequest request) { // 1. 乐观锁更新房源状态保证同一房源不会被重复签约 int updated houseMapper.compareAndSetStatus(request.getHouseId(), 0, 1); if (updated 0) { throw new BusinessException(房源已被锁定或出租无法签约); } // 2. 创建合同业务字段做快照 Contract contract new Contract(); contract.setHouseId(request.getHouseId()); contract.setTenantId(request.getTenantId()); contract.setContractNo(generateContractNo()); contract.setMonthlyRent(request.getMonthlyRent()); contract.setDeposit(request.getDeposit()); contract.setContractStartDate(request.getStartDate()); contract.setContractEndDate(request.getEndDate()); contract.setStatus(0); contractMapper.insert(contract); // 3. 生成首月账单 Bill bill new Bill(); bill.setContractId(contract.getId()); bill.setBillAmount(request.getMonthlyRent()); bill.setBillPeriod(request.getStartDate()); bill.setStatus(0); billMapper.insert(bill); return contract.getId(); } }generateContractNo()我建议生成规则是前缀GL 日期 随机数比如GL202506011234这样合同号天然有序且不会重复。字符串的house.address和contract.monthly_rent拆分出来单独列一个字段是另一个常用的设计把地址拆成“省市区”和“详细地址”两段后续做区域筛选统计非常方便。5. 前端落地Vue页面、接口封装与联调细节5.1 项目初始化与目录结构前端我用Vite初始化项目命令很简单npm create vitelatest house-lease-admin -- --template vue cd house-lease-admin npm install然后把项目目录组织成这样src/ ├── api/ # 接口请求统一封装 │ ├── house.js │ ├── contract.js │ └── bill.js ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── views/ # 页面组件 │ ├── Login.vue │ ├── HouseList.vue │ ├── ContractList.vue │ └── BillList.vue ├── layout/ # 后台布局侧边栏 顶栏 └── utils/ # 工具函数axios实例在这里这个结构不复杂但足够清晰。开发管理后台最忌把页面逻辑全部塞在App.vue里那后面加一个功能模块就要头皮发麻。5.2 Axios封装统一带上Token统一处理返回码前端和后端对接最乱的地方往往不是业务逻辑而是请求的“标准化”。我在utils/request.js里封装了一个Axios实例把公共逻辑全部沉淀在这个文件里面import axios from axios; import { ElMessage } from element-plus; import router from /router; const request axios.create({ baseURL: /api, timeout: 10000, }); // 请求拦截器自动携带Token request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); // 响应拦截器统一处理业务码 request.interceptors.response.use( response { const res response.data; if (res.code ! 200) { ElMessage.error(res.msg || 请求失败); return Promise.reject(new Error(res.msg)); } return res; }, error { if (error.response error.response.status 401) { localStorage.removeItem(token); router.push(/login); } ElMessage.error(error.message || 网络异常); return Promise.reject(error); } ); export default request;这里有个特别重要的约定后端接口的统一返回结构必须约定好我用的结构是{ code: 200, data: {}, msg: success }。不管什么接口都按这个壳子返回前端拦截器才能统一处理。如果你后端的返回格式不统一有的接口直接返回对象有的带code壳子前端代码里到处是if (res.success)或if (res.code 1)维护起来非常痛苦。通用返回类在SpringBoot里就一行Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(success); r.setData(data); return r; } public static T ResultT fail(String msg) { ResultT r new Result(); r.setCode(500); r.setMsg(msg); return r; } }5.3 路由守卫与角色权限控制管理后台的路由守卫核心逻辑是没登录的跳到登录页登录了的判断角色有没有权限访问目标页面。我用router.beforeEach来做router.beforeEach((to, from, next) { const token localStorage.getItem(token); const userInfo JSON.parse(localStorage.getItem(userInfo) || {}); if (!token to.path ! /login) { next(/login); return; } if (to.meta.roles userInfo.role) { if (!to.meta.roles.includes(userInfo.role)) { next(/403); return; } } next(); });这个方案的优点是简单、直观适合中小型系统。权限要求再复杂的话就要引入动态路由、按钮级权限控制但那对这套系统来说属于过度设计。5.4 房源列表页筛选、分页、表格的完整写法我贴一段Vue3组合式API的房源管理页面核心逻辑这是大家直接抄作业的部分script setup import { ref, onMounted } from vue; import request from /utils/request; const loading ref(false); const houseList ref([]); const total ref(0); const queryParams ref({ keyword: , status: null, minPrice: null, maxPrice: null, pageNum: 1, pageSize: 10, }); async function fetchList() { loading.value true; try { const res await request.get(/house/page, { params: queryParams.value }); houseList.value res.data.records; total.value res.data.total; } finally { loading.value false; } } function handleSearch() { queryParams.value.pageNum 1; fetchList(); } function handleStatusChange(row) { // 上下架操作调用后端接口后刷新列表 request.put(/house/status/${row.id}, { status: row.status }).then(() { fetchList(); }); } onMounted(fetchList); /script template el-card el-form inline el-form-item label关键词 el-input v-modelqueryParams.keyword placeholder小区/地址 clearable / /el-form-item el-form-item label状态 el-select v-modelqueryParams.status clearable placeholder全部 el-option label未出租 :value0 / el-option label已出租 :value1 / el-option label锁定 :value2 / /el-select /el-form-item el-form-item el-button typeprimary clickhandleSearch查询/el-button /el-form-item /el-form el-table :datahouseList v-loadingloading el-table-column proptitle label房源标题 / el-table-column propaddress label地址 / el-table-column propmonthlyRent label月租金元 / el-table-column proparea label面积㎡ / el-table-column label状态 template #default{ row } el-tag :typerow.status 1 ? success : info {{ [未出租, 已出租, 锁定][row.status] }} /el-tag /template /el-table-column /el-table el-pagination v-model:current-pagequeryParams.pageNum v-model:page-sizequeryParams.pageSize :totaltotal layouttotal, prev, pager, next current-changefetchList / /el-card /template这里我特意把状态显示用了一个计算映射[未出租, 已出租, 锁定][row.status]简单直接。如果状态多了建议用字典表或者前端常量对象避免写这么长的数组。5.5 字段命名规范的统一问题前后端联调最不起眼但最烦人的问题就是字段命名。Java后端习惯用驼峰命名比如monthlyRentMySQL表字段用下划线命名比如monthly_rent而前端拿到JSON数据后可能看到的是monthlyRent。MyBatis-Plus默认开启了map-underscore-to-camel-case: true这会把数据库的monthly_rent自动映射成monthlyRent非常好用。但要注意一个坑如果某个数据库字段本身就叫rentPrice没有下划线MyBatis-Plus是能直接映射的。如果你有house_id这种带下划线的字段又用了别名查询返回SQL里最好起好驼峰别名避免Jackson序列化后字段名不一致导致前端取值取不到。我在前端实际开发时统一约定所有接口返回字段用驼峰前端所有数据模型都按驼峰写。如果你习惯了数据库风格也可以用JsonProperty做映射但那样每个实体类都要加注解太繁琐不推荐。6. 源码从下载到跑通环境准备与踩坑记录6.1 环境版本对照表不同版本组合的结果这套系统开发和部署验证过的环境组合我直接列出来组件推荐版本备注JDK8 或 11SpringBoot 2.7.x最高支持到JDK 21JDK 8最稳Maven3.6建议配置阿里云镜像加速SpringBoot2.7.x与JDK 8/11都兼容最稳MySQL5.7 或 8.08.0需要配置好时区Node.js16.20Vite 4要求Node 14.18Node 16以上最稳Vue3.3搭配Element PlusVite4.x启动速度快一个真实的踩坑SpringBoot 3.x要求JDK 17起步而且javax.servlet换成了jakarta.servlet。如果你项目里某些依赖还在用老版本升级到SpringBoot 3.x会直接编译报错。所以我建议老老实实用SpringBoot 2.7.x等新手期过了再去折腾新版。6.2 数据库初始化的三个高频问题数据库导入这块三个问题出现的频率最高第一个是字符集问题。建库语句直接写CREATE DATABASE house_lease DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;导入SQL时请确认文件里有这一句不然默认可能是latin1中文全部乱码。第二个是MySQL 8的时区问题。连接字符串里我没有加serverTimezone时直接报错The server time zone value йʱ is unrecognized。解决方案是在数据库连接串上加参数spring: datasource: url: jdbc:mysql://localhost:3306/house_lease?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8useUnicodetrueallowPublicKeyRetrievaltrue其中allowPublicKeyRetrievaltrue是MySQL 8在连接时经常需要的配置不加会报Public Key Retrieval is not allowed的错。第三个是SQL文件执行顺序。如果你的源码包里有多个SQL文件比如schema.sql和data.sql请一定先执行建表再执行数据初始化。有些朋友直接一次全选执行导致报错其实不是代码问题是执行顺序问题。6.3 跨域问题三种解法我推荐统一用后端CORS配置前后端分离项目跑起来第一件事必定遇到跨域。开发环境前端跑在5173端口后端跑在8080端口浏览器默认会拦截跨域请求。解决跨域有三种常见方式我用表格总结一下方案原理优缺点后端CorsFilter在响应头加Access-Control-Allow-Origin推荐一处配置全接口生效前端Vite代理请求发送到同源地址由代理转发仅开发环境有效生产依赖NginxNginx反向代理统一入口生产环境最规范配置稍复杂我在这套系统的开发环境用的是Vite代理生产部署用Nginx反代。但为了本地测试能直接跑通我也在后端加了一个统一的CORS配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns(*)和allowCredentials(true)要配合使用在较新的Spring版本里用allowedOrigins(*)配allowCredentials(true)会直接报错。6.4 前端打包后放进SpringBoot一条命令部署很多单体部署的场景不想单独弄一台Nginx最直接的做法是把前端打包产物塞进SpringBoot的静态资源目录。步骤很简单前端执行npm run build生成dist目录。把dist目录里的内容复制到后端项目的src/main/resources/static/目录下。重新打包后端的jar启动即可。这里有个问题Vue Router如果用的是history模式打包进SpringBoot后页面刷新会404。原因是浏览器刷新时会向Tomcat请求真实的路径而后端没有这个路由映射。解决办法有两个一是最简单粗暴路由用hash模式URL变成/#/house/list刷新不会发请求到服务器。二是配置后端路由转发把非/api/的路径全部转发到index.htmlConfiguration public class WebMvcConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{path:[^\\.]*}).setViewName(forward:/index.html); } }我个人推荐开发调试时先用hash模式等项目真正交付上线再根据实际情况选路径模式。6.5 几个让我印象深刻的“奇怪问题”最后分享几个我在这套系统实际运行过程中遇到过的、不那么容易查到答案的问题。第一个是图片上传后访问404。房源封面我用的是本地磁盘存储数据库里存的是/upload/xxx.jpg这种路径前端直接访问localhost:8080/upload/xxx.jpg结果404。原因很简单SpringBoot默认不映射磁盘上的绝对路径。解决方法是加一个资源映射Configuration public class ResourceConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file:D:/upload/); } }第二个是分页接口在MyBatis-Plus里查出来的total对不上。因为你配置了PaginationInnerInterceptor但它必须保证数据库方言正确同时实体类的主键类型必须和数据库一致。如果主键用的是Long数据库却是int类型分页就会出现诡异问题。第三个是前端明明部署了最新代码页面却还是旧的。这不是代码问题是浏览器缓存。如果走Nginx部署建议在Nginx配置中给静态资源加上add_header Cache-Control no-cache, no-store或者构建时给静态资源文件名加hash。我个人在实际操作中的体会是这类管理系统的源码本身并不难真正难的是把“业务约束”翻译成“代码逻辑”。比如合同不能重复签、房租不能随意改历史、账单不能平白消失这些约束如果一开始没在设计层面想清楚后面测试阶段会反复出bug。拿到完整源码后我建议第一件事不是急着启动而是先读三处数据库建表脚本、JWT过滤器、合同Service。把这三块看懂了这套系统你就掌握了一大半。之后再去改页面、加模块才会真正顺手。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号