做房屋租赁管理系统这类项目,很多人第一反应是“搞个表格增删改查”,但真正把“房东发房源、租客下单、管理员审核、合同生成”这一整套业务跑通,还要能部署上线给客户演示,难度并不小。我这次基于SpringBoot + Vue + MyBatis + MySQL这套组合,完整实现了前后端分离的房屋租赁管理系统,从数据库设计、后端接口开发,到Vue前端页面联调,再到本地打包和服务器部署,整个过程踩了不少坑,也沉淀了一些非常实用的经验。这篇文章就把整个项目的核心设计思路、关键代码实现、部署细节和排查技巧完整梳理一遍,无论是用来做毕业设计、自学项目实战,还是接私活做演示项目,都很有参考价值。
1. 项目整体架构设计
1.1 为什么这套系统要选择前后端分离
如果你只是做一个内部管理后台,用传统的Thymeleaf模板渲染其实更快,把Java代码和页面写在同一个工程里,开发成本确实低。但房屋租赁系统有一个明显的特点:使用角色多、访问场景杂。租客可能用手机浏览器访问,房东可能用PC管理房源,管理员需要看数据看板,未来还可能扩展小程序端。如果所有页面都靠后端渲染,每来一个端就要改一套模板,维护成本会直线上升。
前后端分离的核心价值在于:后端只需要专注输出数据,前端负责呈现和交互。后端把接口写好,返回JSON数据,前端可以用Vue做桌面端页面,也可以抽一套接口给小程序的H5页面复用。我做这套系统时,后端接口设计完全基于RESTful风格,前端只需要关心怎么调用接口、怎么渲染数据,两边各管各的,联调起来非常清爽。
另一个实际好处是开发效率。Vue有热更新,改完页面代码浏览器自动刷新,后端接口用Postman测好之后,前端直接对接,不需要反复重启Tomcat等页面渲染。多人协作时,前端攻城狮和后端DBA可以并行开发,只要提前约定好接口文档格式就行。
1.2 技术选型:SpringBoot、Vue、MyBatis、MySQL各自的角色
这套技术栈在今天可以说是Java Web开发的标准组合,每个组件的选型都有明确的理由:
| 技术组件 | 在本项目中的角色 | 为什么选它 |
|---|---|---|
| SpringBoot | 后端基础框架,负责接口发布、业务编排、依赖管理 | 简化配置,内嵌Tomcat,一个jar包就能跑起来,极大降低部署成本 |
| Vue | 前端框架,构建页面组件和交互逻辑 | 轻量、上手快、生态成熟,Element UI配合做后台管理页面效率极高 |
| MyBatis | 持久层框架,处理SQL与Java对象的映射 | 相比JPA,SQL是显式可控的,像房屋搜索这种复杂查询和排序能精细优化 |
| MySQL | 数据存储,保存用户、房源、订单、合同等核心数据 | 开源免费、使用面广,招聘市场大量岗位要求熟悉MySQL,项目通用性强 |
这套组合还有一层考虑是人才匹配度。招聘平台上搜Java开发岗位,SpringBoot、Vue、MyBatis、MySQL几乎是标配关键词,很多求职者看到这套技术栈就有兴趣上手,做出来的项目写在简历上认可度也高。如果你用一些冷门的ORM框架或者国内小众前端框架,虽然技术含量不低,但面试官不一定熟悉,反而解释成本高。
2. 数据库与业务模型设计
2.1 房屋租赁的核心业务角色与流程
开工之前一定要把业务角色理清楚,否则后面做权限控制、状态管理会乱成一锅粥。这套房屋租赁系统里有三类角色:
- 租客:注册登录后搜索房源、查看房源详情、下单租房、查看自己的订单状态。
- 房东:发布房源、管理自己名下的房源上下架、查看租客的租房订单、确认合同。
- 管理员:审核房东发布的房源、管理用户、统计平台房源和订单总量。
核心业务流转是这样的:房东发布房源 → 管理员审核通过 → 房源上架展示 → 租客浏览并下单租房 → 租客支付(本项目用模拟支付) → 双方签署合同 → 租房生效 → 租期到期或提前退租 → 订单关闭。
这里有个很关键的设计点:房源、订单、合同的状态要联动但又要独立维护。比如房源状态有“待审核”、“上架中”、“已出租”,订单状态有“待支付”、“已支付”、“履约中”、“已完成”、“已取消”。租客支付成功后,订单变成“已支付”,此时房源状态要同步变为“已出租”,防止其他人重复下单。这部分我放在事务里处理,确保状态一致性。
2.2 核心表结构设计
数据库设计是项目的根基,表设计不好,后面写SQL会非常痛苦。我按业务模块拆分成四张核心表:用户表、房源表、订单表、合同表。
用户表(user)关键字段:
CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '用户名', `password` VARCHAR(100) NOT NULL COMMENT 'MD5加密后的密码', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `role` TINYINT NOT NULL DEFAULT 0 COMMENT '角色:0租客 1房东 2管理员', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '账号状态:0禁用 1正常', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';密码不建议明文存储,我用MD5配合盐值做了加密。生产项目建议用BCrypt,这个项目为了演示方便用MD5,但文章里要提醒读者升级到BCrypt处理。
房源表(house)是核心业务表,字段要覆盖展示和检索两方面的需求:
CREATE TABLE `house` ( `id` INT NOT NULL AUTO_INCREMENT, `landlord_id` INT NOT NULL COMMENT '房东用户ID', `title` VARCHAR(100) NOT NULL COMMENT '房源标题', `cover` VARCHAR(255) DEFAULT NULL COMMENT '封面图URL', `images` TEXT COMMENT '多张图片URL,用逗号分隔', `area` DECIMAL(10,2) NOT NULL COMMENT '面积(平方米)', `price` DECIMAL(10,2) NOT NULL COMMENT '月租金(元)', `address` VARCHAR(255) NOT NULL COMMENT '小区地址', `city` VARCHAR(50) DEFAULT NULL COMMENT '城市', `description` TEXT COMMENT '房源描述', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待审核 1上架中 2已出租 3已下架', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_landlord` (`landlord_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源表';查询性能上,房源表一定不要只依赖主键。租客搜索房源时,通常会用“城市 + 租金区间 + 面积”作为过滤条件,所以我给status和landlord_id都建了索引。随着房源数据增长到几十万条时,这些索引会明显提升分页查询速度。
订单表(order)的核心字段必须记录下单快照,因为房源价格可能调整,合同要按下单时的价格签署:
CREATE TABLE `rent_order` ( `id` INT NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单编号', `house_id` INT NOT NULL, `tenant_id` INT NOT NULL COMMENT '租客ID', `landlord_id` INT NOT NULL COMMENT '房东ID', `month` INT NOT NULL COMMENT '租期月数', `unit_price` DECIMAL(10,2) NOT NULL COMMENT '下单时月租金', `total_price` DECIMAL(10,2) NOT NULL COMMENT '总金额', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待支付 1已支付 2履约中 3已完成 4已取消 5已退租', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `pay_time` DATETIME DEFAULT NULL COMMENT '支付时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_tenant` (`tenant_id`), KEY `idx_house` (`house_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租房订单表';跨表关联时,外键约束我建议在表设计阶段保留,但实际生产环境如果并发量高,可以考虑逻辑外键代替物理外键。演示项目用物理外键更直观,方便面试时解释表关系。
2.3 订单状态机与房源状态的联动
很多初学者做这类系统喜欢用一堆if/else去判断状态,结果逻辑越写越乱。更规范的做法是先定义状态机,再写代码。我只允许以下状态流转发生:
- 房源状态:待审核(0) → 上架中(1),待审核(0) → 已下架(3),上架中(1) → 已出租(2),已出租(2) → 已下架(3)
- 订单状态:待支付(0) → 已支付(1) → 履约中(2) → 已完成(3),以及待支付(0) → 已取消(4)、履约中(2) → 已退租(5)
状态机一旦定下来,后端接口里就不要随意越级修改状态。我在Service层里写了一个状态校验方法,每次更新前先查当前库里的状态,再判断是否允许跳到目标状态,不允许则抛出业务异常。这样即使前端误操作、接口被恶意调用,后台也不会产生脏数据。
3. 后端开发:SpringBoot + MyBatis实战解析
3.1 后端工程结构与分层思路
后端工程我用的是标准Maven多模块结构,不过这个体量不需要过度拆分,单模块按包名分层就够了:
src/main/java/com/example/rent ├── config // 全局配置类:跨域、拦截器、MyBatis配置 ├── controller // 接口层:接收请求,返回统一结果 ├── service // 业务层:处理业务逻辑与事务 ├── mapper // 数据访问层:MyBatis的Mapper接口 ├── entity // 数据库实体类 ├── dto // 请求/响应参数对象 ├── common // 通用类:统一返回结果、异常处理、工具类 └── RentApplication.java // 启动类分层核心原则是:Controller不写业务代码,Service不写SQL,Mapper只做数据访问。很多毕设代码Controller里直接写一大坨业务逻辑,虽然没有链路问题,但后续想加一个角色权限控制,会发现几乎无从下手。
统一返回结果是一个容易被忽视但非常重要的设计。我定义了一个R类:
public class R { private Integer code; // 200成功,500业务异常,401未登录 private String message; private Object data; public static R ok() { return new R(200, "success", null); } public static R ok(Object data) { return new R(200, "success", data); } public static R error(String message) { return new R(500, message, null); } }前端axios拦截器拿到code后统一处理,200走成功逻辑,401跳登录页,500弹出错误提示。这个约定越早定越好,前后端联调时能省下很多无意义的沟通。
3.2 房屋分页搜索接口:动态SQL与排序
房屋搜索是整个系统的核心接口,要求支持关键词模糊查询(标题、地址)、租金区间过滤、面积过滤、城市过滤,还要支持按租金排序和按时间排序。这种查询条件动态变化的需求,正是MyBatis动态SQL最擅长的场景。
Mapper XML里的核心实现:
<select id="pageHouse" resultType="com.example.rent.entity.House"> SELECT * FROM house <where> <if test="keyword != null and keyword != ''"> AND (title LIKE CONCAT('%', #{keyword}, '%') OR address LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="city != null and city != ''"> AND city = #{city} </if> <if test="minPrice != null"> AND price <![CDATA[ >= ]]> #{minPrice} </if> <if test="maxPrice != null"> AND price <![CDATA[ <= ]]> #{maxPrice} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY <choose> <when test="sort == 'price_asc'">price ASC</when> <when test="sort == 'price_desc'">price DESC</when> <when test="sort == 'time_desc'">create_time DESC</when> <otherwise>create_time DESC</otherwise> </choose> </select>排序这里我要重点说一个很多人踩过的坑:ORDER BY后面的字段不能直接用#{}占位符,因为MyBatis对预编译参数会加引号,你拼进去price DESC会变成字符串字面量,SQL直接报错。有些教程让你用${sort}硬拼,这确实能跑,但存在SQL注入风险,用户传一个恶意字段名就能炸库。我推荐的做法是上面这种白名单映射,在Java代码里定义允许的排序字段,前端传参后先做个格式校验,再映射到固定SQL片段,既安全又清晰。
分页选择上,我一开始用手写的LIMIT分页,因为数据量不大完全够用。但如果你开发时用了PageHelper,要特别注意它和自定义动态SQL一起使用时的count查询优化问题,处理不好会导致分页总数统计异常。
3.3 JWT登录认证与权限控制
登录认证这块,我没用传统的Session方案,而是选择JWT。原因很直接:前后端分离后,后端接口可能被多个前端调用,Session共享在分布式部署时很麻烦,JWT的Token机制天然解决了这个问题。
实现逻辑三步走:
- 用户登录成功后,后端生成一个Token,里面封装用户ID、用户名、角色,用HMAC密钥签名,设置有效期(我设置了24小时)。
- 前端拿到Token后存到localStorage,每次axios请求在拦截器里自动加上
Authorization: Bearer xxx请求头。 - 后端写一个拦截器,放行登录、注册、房源列表公开接口,其余接口统一解析Token,解析失败直接返回401。
唯一要提醒的是JWT最大的弱点是无法注销,用户改密码或者被管理员封号后,已签发的Token在有效期内依然能用。我项目里通过用户表的status字段做了二次校验:每次请求拦截器解析出用户ID后,查一下账号是否被禁用,禁用就拒绝访问。这个思路在面试中很加分,体现出你对安全的深入理解。
权限控制我是通过自定义注解@RequireRole实现的,在需要房东权限的接口上加@RequireRole("landlord"),拦截器里解析注解并根据当前登录用户的角色决定是否放行。这样比在方法内部写死角色判断要优雅得多。
3.4 MyBatis开发中的几个高频坑
第一个坑是实体类字段与数据库字段映射不上。数据库字段用下划线命名(create_time),Java字段用驼峰命名(createTime),MyBatis默认不能自动映射,结果查出来的字段全是null。解决办法是在application.yml里开启下划线转驼峰配置:
mybatis: configuration: map-underscore-to-camel-case: true这个配置加上后,只要数据库字段和Java字段风格一致,就不需要每个实体都写一大串resultMap了。
第二个坑就是热词里经常有人问的mybatis条件不生效。常见原因有几个:<if>标签的test条件写错(比如判断String非空用了!= null而不是!= null and != '');或者Java方法参数没有加@Param注解,XML里引用参数名对不上。我这边统一规范:方法参数多于一个的,Mapper接口必须明确注解参数名,避免编译时参数名丢失导致绑定失败。
第三个坑是SQL日志打印问题。排查SQL时,如果控制台没有任何SQL输出,很难定位问题。我推荐在配置里加上:
mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这样执行的每条SQL、参数、结果数都会打印到控制台,开发阶段非常有帮助。生产环境再关掉即可。
4. 前端开发:Vue + Element UI实战解析
4.1 Vue工程初始化与环境配置
前端工程我用的Vue CLI创建,Node.js版本建议用16.x或18.x LTS。很多人在这个环节卡住,原因基本是Node版本和依赖包版本不匹配。比如Node 20配旧版node-sass会编译失败,Vue CLI 5配某些webpack插件也有兼容问题。
依赖安装常用的命令:
npm install -g @vue/cli vue create rent-frontend cd rent-frontend npm install element-ui axios vue-router vuex --save在国内网络环境,npm直接安装依赖经常卡在下载环节。我的建议是提前把仓库源切换成淘宝镜像:
npm config set registry https://registry.npmmirror.com实测下来,切源之后安装速度能从几十分钟降到两三分钟。如果项目里已经生成了package-lock.json,安装时加上--registry参数或者直接删掉锁文件重新装,避免锁文件里的旧地址拖慢速度。
工程目录里,我习惯把页面组件、公共组件、路由、状态管理分开:
src ├── api // 接口请求封装模块 ├── assets // 静态资源 ├── components // 公共组件(搜索栏、分页组件等) ├── router // vue-router路由配置 ├── store // vuex状态管理 ├── views // 页面组件:登录页、注册页、房源列表、房源详情、个人中心、后台管理 ├── App.vue ├── main.js └── vue.config.js4.2 路由守卫与axios请求封装
路由守卫的核心作用是前端权限控制。用户没登录可以访问房源列表和详情,但进入“个人中心”、“我的订单”这些页面之前,必须检查本地是否有Token。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } })这段代码虽然简单,但关键点是redirect参数的保存。用户被拦截去登录页,登录成功后应该自动跳回原本想去的页面,而不是每次都回首页,这个小细节体验差异很大。
axios请求封装要统一处理三件事:请求头注入Token、响应码统一处理、错误提示统一弹出。
const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.message || '系统异常') if (res.code === 401) { router.push('/login') } return Promise.reject(new Error(res.message)) } return res }, error => { Message.error(error.message || '网络异常') return Promise.reject(error) } )4.3 房源发布与订单支付的关键页面逻辑
房源发布页面,前端要处理图片上传。Element UI的Upload组件可以传图片到后端的文件上传接口,我们项目里用的是本地文件夹存储,Nginx做静态映射访问。图片上传成功后会返回一个URL,前端保存到表单的images字段里,提交房源时一并传给后端。
后端文件上传接口有一个安全细节:校验文件类型和大小。我限制了图片只能jpg、png、webp格式,大小不超过2MB,服务端再次校验,避免恶意上传可执行文件。
订单支付页面做的是模拟支付。真实场景对接微信或支付宝需要商户号和三方SDK,演示项目里我简化成:用户点击“确认支付”,前端调后端支付接口,后端设置订单为“已支付”,房源状态同步改成“已出租”,订单状态流转到“已支付”。页面上再做5秒倒计时反馈,增加真实感。
这里前端要注意的一个常见问题是重复提交。用户快速双击支付按钮,会连续调两次接口,可能导致订单状态异常。我的解决方案是按钮加loading状态,提交后立即禁用,防止重复点击。
5. 环境搭建与部署上线:从本地到服务器
5.1 本地开发环境版本搭配
技术栈的版本搭配直接决定了你是否会踩坑。我在这套项目里用的版本组合是:
| 软件 | 推荐版本号 | 备注 |
|---|---|---|
| JDK | 1.8(8u201+) | 稳定可靠,生态兼容性最好 |
| Maven | 3.6.3 | 不要用太新的3.9.x配旧项目可能有兼容问题 |
| SpringBoot | 2.7.18 | 最后支持JDK8的版本线 |
| Node.js | 18.x LTS | 兼容Vue CLI 5 |
| MySQL | 8.0.x | 性能好,但要注意驱动参数 |
| Vue CLI | 5.x | 对应Webpack 5 |
很多人问“springboot版本太高”怎么办。核心矛盾是:SpringBoot 3.x强制要求JDK17,而你本机是JDK8,跑3.x直接启动失败。解决方案有两个:一是把JDK升到17,但很多老项目依赖(比如一些MyBatis增强工具包)不兼容;二是我推荐的,老老实实选SpringBoot 2.7.x,它是2.x系列的最后一个版本,安全补丁也比较全,演示项目和面试评价都不吃亏。
5.2 MySQL安装与数据库初始化
MySQL 8.0在Windows上安装,网上教程质量参差不齐。如果你不想用安装包向导,推荐直接用ZIP解压方案,可控性更强:
# 下载 mysql-8.0.xxx-winx64.zip 解压后执行: mysqld --initialize-insecure # 初始化数据目录,root空密码 mysqld -install # 注册Windows服务 net start mysql # 启动服务 mysql -u root -p # 密码直接回车进入初始化之后,SQL执行文件准备好,包含建库、建表、初始数据三个脚本,依次执行即可。
有一个非常关键的细节:MySQL 8的驱动参数。JDBC连接串必须配置多个参数,否则会遇到各种连接失败:
spring: datasource: url: jdbc:mysql://localhost:3306/rent?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver其中allowPublicKeyRetrieval=true是MySQL 8的典型坑。如果缺少这个参数,连接时会报Public Key Retrieval is not allowed,这是MySQL 8默认的caching_sha2_password认证方式导致的,用了mysql_native_password或sha256_password以外的插件,就必须配置这个参数。
字符集方面,数据库连接串里characterEncoding=utf8是JDBC层保持UTF-8传输,真正保证中文不乱码的关键是建表时指定utf8mb4。我建库默认排序规则都用utf8mb4_general_ci,这样中文排序和显示不会出错。
5.3 后端打包与配置
后端打包标准流程是:
mvn clean package -DskipTests打成jar包后,不依赖外部Tomcat,直接:
java -jar rent-backend-1.0.0.jar有一个部署细节值得注意:不同环境的配置应该用SpringBoot的多Profile机制解决。开发环境用application-dev.yml,生产环境用application-prod.yml,启动时指定:
java -jar rent-backend.jar --spring.profiles.active=prod生产环境我通常会把数据库密码通过启动参数传入,而不是写死在yml文件里。比如:
java -jar rent-backend.jar --spring.datasource.password='你的密码'这样做的好处是配置文件和代码一起打包后,密码不会泄露在代码仓库中,密码修改也只需要重启时换参数。
5.4 Vue打包放进SpringBoot的两种部署方案
前端打包用:
npm run build生成dist目录,里面的静态文件就是整个前端。部署时有两种主流方案:
方案一:单独部署Vue到Nginx,后端jar包单独跑(推荐)
Nginx配置大概长这样:
server { listen 80; server_name yourdomain.com; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里try_files $uri $uri/ /index.html;是Vue的history路由模式必须的配置,否则刷新页面时路由是/house/detail/1,Nginx会去找这个真实路径,找不到就404。不加这行,页面首次打开没问题,F5刷新就白屏。
方案二:把dist打包进SpringBoot的resources/static目录
这种方式适合单机演示场景,不用装Nginx,一个jar包全搞定。把dist下的所有静态资源复制到src/main/resources/static/,重新打包后启动jar包,直接访问http://localhost:8080/index.html就能看到前端页面。
但这里有一个强制注意事项:如果选择方案二,Vue路由必须改用hash模式,不能使用history模式。因为history模式的路由路径在服务端没有对应的转发规则,jar包内部的SpringBoot不会做前端路由的回退处理,刷新时必然404。改成hash模式后,路径上的#让浏览器只向服务端请求首页,路由切换完全由前端控制,就不会有404问题。
const router = new VueRouter({ mode: 'hash', routes })方案二的另一个麻烦是:前端接口请求路径的语境会变化。如果Nginx方案里用了/api做代理转发,那方案二里前端axios的baseURL就不能再写/api了,应该直接写/或者干脆用相对路径,让请求打到SpringBoot本身的接口上。两个方案的本质区别就是:谁来处理静态资源和接口转发那层逻辑。
6. 常见问题排查与避坑指南
6.1 SpringBoot版本太高导致的启动失败
启动报错Error creating bean with name 'xxx'或者UnsupportedClassVersionError,十有八九是JDK版本和SpringBoot版本不匹配。3.x的SpringBoot必须用JDK17+,如果项目配置里还有javax.*的老包,也需要迁移成jakarta.*。处理这种问题最快的方式是做“版本降级”,把SpringBoot降到2.7.x系列,而不是硬着头皮改代码适配新版本。
另外要检查Maven仓库里有没有缓存冲突。下载过3.x版本依赖后,如果不小心把本地的默认SpringBoot版本改掉,重新构建时会拉一堆3.x的jar包。我建议在pom.xml的parent里显式写版本号:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent>6.2 MyBatis查询条件不生效
这个问题的排查路径我总结为三步。第一步,确认SQL日志里有执行语句,如果没有说明Mapper映射没绑定成功,检查XML里的namespace是否和接口全限定名一致。第二步,确认参数真的传进来了,在test条件里输出参数看是否为空。第三步,检查XML里的<if>条件写法,最常见的问题是用了<if test="keyword != null">但没判空字符串,前端传了空串过来,条件不生效导致查询结果异常。
代码示例:
<!-- 错误写法:空字符串时条件不生效 --> <if test="keyword != null"> AND title LIKE CONCAT('%', #{keyword}, '%') </if> <!-- 正确写法 --> <if test="keyword != null and keyword != ''"> AND title LIKE CONCAT('%', #{keyword}, '%') </if>6.3 Vue依赖安装慢与打包失败
npm安装依赖慢,可以先看是否已经切换了淘宝源。打包失败常见的错误是JavaScript heap out of memory,这是Node内存默认限制太小导致的。可以临时加大内存:
NODE_OPTIONS=--max_old_space_size=4096 npm run buildWindows下临时指定环境变量还需要set命令,更省事的办法是修改package.json里的build脚本:
"scripts": { "build": "node --max_old_space_size=4096 node_modules/vue-cli-service/bin/vue-cli-service.js build" }6.4 接口跨域与404问题速查
以下是这个项目联调和部署阶段最常见问题的排查速查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 前端调用接口报CORS错误 | 后端没有配置跨域 | 后端增加CorsConfig全局配置,或前端用代理方式规避开发环境跨域 |
| 接口返回404 | 后端接口路径和前端请求路径不一致 | 检查Controller的RequestMapping和前端axios路径,建议统一用/api前缀 |
| 打包后页面白屏 | 资源路径使用了绝对路径/js/xxx.js | Vite/Vue CLI里配置publicPath: './',改为相对路径 |
| 刷新出现404 | history路由未配置回退 | Nginx加try_files,或改用hash模式 |
| 上传图片无法访问 | 静态资源映射路径不对 | 检查文件存储路径和访问URL,确认没有穿越目录 |
| Token过期后跳转循环 | 401错误触发后未清token | 跳登录页前清除localStorage里的token,并带上redirect参数 |
有动手能力的同学,可以把这套系统继续往两个方向扩展:一是接一个ECharts数据大屏,给管理员端展示房源分布、订单走势;二是适当引入Redis做热门房源的缓存,减轻数据库压力。这两个增强点对面试聊深度的帮助会非常大。
我个人在实际操作中的一个体会是:做这种前后端分离项目,最花时间的往往不是业务功能本身,而是环境的“版本编排”和联调阶段的细节对齐。JDK版本、Node版本、MySQL驱动参数、前端路由模式,每一个环节出问题都会让人卡很久。所以如果你准备复刻这套系统,我的建议是:严格按照本文给出的版本组合来搭环境,先跑通Hello World级别的接口和页面,确认链路通畅后再填充完整业务功能。千万不要一上来就把所有代码写完再统一调环境,那样遇到问题定位起来非常困难。把地基打牢,后面填业务代码就会顺很多。