智慧租房管理系统
项目简介
一个基于 Vue 3 + Spring Boot 的在线租房管理平台,支持租户、房东、管理员三种角色,集成 DeepSeek AI 实现智能房源推荐。
目录
1. 项目流程总览
用户注册/登录 → 角色路由分发 → 各端功能页面
│
┌───────────────┼───────────────┐
▼ ▼ ▼
租客端 房东端 管理端
- 浏览房源 - 发布房源 - 用户管理
- 下单租房 - 订单管理 - 房源管理
- 租房攻略 - 数据概览 - 订单管理
│
▼
DeepSeek AI 智能推荐
(根据偏好匹配房源)
整体架构
├── 后端 (backend/) ← Spring Boot + MyBatis-Plus + MySQL
│ ├── controller/ ← RESTful API,18 个端点
│ ├── service/ ← 业务逻辑 + DeepSeek AI 推荐
│ ├── entity/ ← 4 个实体(User/House/Order/VerificationCode)
│ ├── mapper/ ← MyBatis-Plus 数据访问层
│ ├── dto/ ← 推荐请求/响应 DTO
│ ├── config/ ← CORS + MyBatis-Plus 分页插件
│ └── util/ ← MD5 加盐加密工具
├── 前端 (frontend/) ← Vue 3 + Vite + Element Plus
│ ├── api/ ← axios 封装 + 3 个 API 模块
│ ├── router/ ← 路由表 + 导航守卫(RBAC)
│ └── views/ ← 按角色分目录:tenant / landlord / admin
├── 初始化数据库.py ← 删库重建 + 导入 SQL
├── 启动前端.py / 启动后端.py ← 一键启动脚本
└── README.md
2. 用户认证体系
注册流程
用户填写注册表单 → 输入邮箱 → 点击"获取验证码"
│
├── 前端校验:邮箱格式 → 60 秒倒计时防重复点击
├── POST /api/user/send-code
│ ├── 后端 60 秒频控检查(同一邮箱不可重复发送)
│ ├── 生成 6 位随机验证码 → 写入 verification_code 表
│ └── Spring Mail 发送邮件
│
├── 用户输入验证码 + 密码 → POST /api/user/register
│ ├── 校验用户名唯一性
│ ├── 校验验证码:邮箱 + 验证码 + REGISTER类型 + 未使用 + 未过期
│ ├── 标记验证码为已使用
│ └── MD5 加盐加密密码 → 写入 user 表
│
└── 注册成功 → 自动跳转登录页
登录流程
POST /api/user/login { username, password }
│
├── 密码 MD5 加盐加密 → 与数据库密文比对
├── 校验用户状态 enabled = 1(停用账户不可登录)
├── 返回用户 JSON(密码字段清空)
│
└── 前端:localStorage.setItem('user', JSON.stringify(user))
→ router.push 跳转到角色对应首页
密码存储:MD5 加盐
// MD5Util.java
private static final String SALT = "smartrental2026";
public static String md5(String plainText) {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest((plainText + SALT).getBytes());
// 转 32 位小写十六进制
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
邮箱验证码设计
verification_code 表
├── email -- 目标邮箱
├── code -- 6 位数字验证码
├── type -- REGISTER(可扩展:RESET_PASSWORD 等)
├── expires_at -- 5 分钟过期
├── used -- 0=未使用, 1=已使用(防重复利用)
└── create_time -- 用于 60 秒频控判断
防刷机制:
- 60 秒内同一邮箱不可重复发送(
createTime + 60s > now则拒绝) - 验证码 5 分钟过期(
expiresAt < now则拒绝) - 验证码使用后标记
used = 1,不可二次使用
3. 角色权限控制
三角色 RBAC 模型
┌───────────────┐
│ 未登录用户 │
│ 只能访问 /login │
└───────┬───────┘
│ 登录
┌────────────┼────────────┐
▼ ▼ ▼
TENANT LANDLORD ADMIN
租客端 房东端 管理端
/tenant/* /landlord/* /admin/*
前端路由守卫
// router/index.js — 全局前置导航守卫
router.beforeEach((to, from, next) => {
// 1. 登录页 → 放行
if (to.path === '/login') { next(); return }
// 2. 未登录 → 强制跳转登录页
const user = JSON.parse(localStorage.getItem('user') || 'null')
if (!user) { next('/login'); return }
// 3. 角色不匹配 → 重定向到角色对应首页
if (to.meta.role && to.meta.role !== user.role) {
const roleMap = { TENANT: '/tenant', LANDLORD: '/landlord', ADMIN: '/admin' }
next(roleMap[user.role] || '/login'); return
}
// 4. 全部通过 → 放行
next()
})
路由懒加载
// 所有页面组件都使用动态 import,Vite 自动代码分割
{ path: 'dashboard', component: () => import('../views/tenant/TenantDashboard.vue') }
效果:用户只加载当前角色的页面代码,租客不会下载房东/管理端的 JS。
4. 数据层设计
4 张核心表
user -- 用户表 (username, password, role, email, enabled, ...)
house -- 房源表 (title, price, area, type, status, landlord_id, ...)
rental_order -- 订单表 (house_id, tenant_id, landlord_id, start_date, end_date, total_amount, status, ...)
verification_code -- 验证码表 (email, code, type, expires_at, used, ...)
MyBatis-Plus:极简数据访问层
MyBatis-Plus 的核心价值——零 SQL 实现标准 CRUD:
// UserServiceImpl — 继承 ServiceImpl 后自动获得:
// save(entity) — 插入
// updateById(entity) — 更新
// removeById(id) — 逻辑删除
// getById(id) — 按主键查询
// page(page, wrapper) — 分页查询
// list(wrapper) — 条件列表查询
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 只写自定义业务逻辑(登录、注册、发验证码)
// 标准 CRUD 全部继承自 ServiceImpl,一行代码都不用写
}
HouseService 和 RentalOrderService 更是极端案例——完全使用默认 CRUD,类体只有两行:
@Service
public class HouseServiceImpl extends ServiceImpl<HouseMapper, House> implements HouseService {
// 空类体,MyBatis-Plus 提供的默认实现已满足所有需求
}
分页查询模式
// 典型的分页接口模式:Lambda 条件 + Page 对象 → 自动生成 LIMIT + COUNT
@GetMapping("/page")
public Result<Page<House>> page(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String title,
@RequestParam(required = false) String status) {
LambdaQueryWrapper<House> wrapper = new LambdaQueryWrapper<>();
if (title != null && !title.isEmpty()) wrapper.like(House::getTitle, title);
if (status != null && !status.isEmpty()) wrapper.eq(House::getStatus, status);
wrapper.orderByDesc(House::getCreateTime);
return Result.success(houseService.page(new Page<>(current, size), wrapper));
}
Lambda 条件构造器的好处:字段名用 House::getTitle 而不是字符串 "title",重构时 IDE 自动改名,杜绝字符串拼写错误。
逻辑删除
@TableLogic
private Integer deleted; // 0=未删除, 1=已删除
所有 removeById() 调用自动转换为 UPDATE SET deleted=1,永不物理删除数据。查询自动追加 WHERE deleted=0。
自动填充
@TableField(fill = FieldFill.INSERT) // 插入时自动填当前时间
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填
private LocalDateTime updateTime;
免去每次手动 setCreateTime(LocalDateTime.now())。
5. 统一响应与错误处理
Result 泛型封装
// com.smartrental.common.Result<T>
public class Result<T> {
private Integer code; // 200=成功, 500=失败
private String message; // 提示信息
private T data; // 泛型数据
// 静态工厂方法,调用方代码极简洁
public static <T> Result<T> success(T data) → Result(200, "操作成功", data)
public static <T> Result<T> error(String msg) → Result(500, msg, null)
}
使用效果
// Controller 层——每个方法都返回 Result,格式完全统一
@PostMapping("/login")
public Result<User> login(...) {
User user = userService.login(username, password);
if (user != null) return Result.success("登录成功", user);
return Result.error("用户名或密码错误");
}
前端收到的 JSON 格式永远一致:
{ "code": 200, "message": "登录成功", "data": { "id": 1, "username": "zhangsan", "role": "TENANT" } }
错误处理策略
// Service 层抛 RuntimeException → Controller 层 try/catch → Result.error(e.getMessage())
try {
User registered = userService.register(user, code);
return Result.success("注册成功", registered);
} catch (RuntimeException e) {
return Result.error(e.getMessage()); // "用户名已存在" / "验证码错误" / "验证码已过期"
}
没有全局异常处理器——每个 Controller 方法自己做 try/catch。对于教学项目来说足够清晰,生产环境建议用 @ControllerAdvice 统一处理。
6. DeepSeek AI 智能推荐
为什么集成 AI?
传统房源筛选只能按价格/面积/户型做硬匹配,但用户的真实需求往往是模糊的——“想找一个安静、交通方便、适合养宠物的房子”。DeepSeek 大模型可以理解这种自然语言需求,从可租房源中智能匹配。
推荐流程
POST /api/house/recommend { budget, area, roomType, location, description }
│
├── 1. 查询所有状态为 AVAILABLE(可租)的房源
├── 2. 构造 Prompt:
│ ├── System: "你是专业的租房推荐助手,按 JSON 格式返回"
│ └── User: 用户偏好 + 房源列表(序号、标题、户型、面积、价格、地址、描述)
├── 3. 调用 DeepSeek Chat API(HTTP POST, Bearer Token 认证)
│ └── temperature=0.3(低温度,输出更确定)
├── 4. 解析 AI 返回的 JSON → List<RecommendResult>
│ └── 每个结果:房源对象 + 匹配度评分(0-100) + 推荐理由(2-4条)
└── 5. 失败降级:API 异常时自动切换为规则匹配
Prompt 设计
System Prompt:
你是一个专业的租房推荐助手。根据用户的偏好和文字描述,从房源列表中推荐最合适的房源。
请严格按以下JSON格式返回,不要输出其他内容:
{"results":[{"houseIndex":1,"score":85,"reasons":["预算匹配","交通便利"]}]}
houseIndex是房源列表中的序号(从1开始),score是0-100的匹配度评分,reasons是2-4个推荐理由(用中文)。
只返回得分>=30的房源,按score降序排列。
User Prompt:
用户偏好:预算 2000-3000元/月、面积 50-80㎡、位置 朝阳区
用户描述:希望房子采光好,周边有地铁站
可租房源列表:
[1] 阳光花园温馨一居 | 一室一厅 | 55㎡ | ¥2500/月 | 朝阳区望京 | 精装修...
[2] ...
降级方案
// DeepSeekService.java: fallbackRecommend()
private List<RecommendResult> fallbackRecommend(RecommendRequest req, List<House> houses) {
for (House h : houses) {
int score = 10; // 基础分
// 位置匹配:地址包含用户位置关键词 → +30
if (h.getAddress().contains(req.getLocation())) score += 30;
// 户型匹配:类型包含用户户型偏好 → +30
if (h.getType().contains(req.getRoomType())) score += 30;
// 有描述需求 → +5
if (req.getDescription() != null) score += 5;
if (score > 10) results.add(new RecommendResult(h, Math.min(score, 70), reasons));
}
}
降级保证:即使 DeepSeek API 挂了,系统仍然能给出基于规则的推荐结果。
7. 前后端通信
Vite 开发代理
// vite.config.js — 开发环境代理配置
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080', // 后端地址
changeOrigin: true,
}
}
}
效果:
浏览器请求 → http://localhost:3000/api/user/login
↓ Vite 代理转发
http://localhost:8080/api/user/login
开发环境下前后端同域,不存在跨域问题。生产环境用 CORS 配置兜底。
axios 封装
// api/index.js
const api = axios.create({
baseURL: '/api', // 所有请求以 /api 开头
timeout: 10000, // 10 秒超时
})
// 响应拦截器:自动解包 response.data
api.interceptors.response.use(r => r.data, err => { ... })
调用方代码极简:
const res = await userApi.login({ username, password })
// res 已经是 { code: 200, message: "登录成功", data: {...} }
// 不需要再写 res.data.code,拦截器已经解包了一层
CORS 配置
// CorsConfig.java — 允许任意来源跨域请求
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
开发环境宽松配置,生产环境应限制具体域名。
8. 前端架构
路由结构
/login → Login.vue (公开页面)
/tenant → TenantLayout.vue (租客端布局)
/tenant/dashboard → 首页
/tenant/houses → 房源列表
/tenant/orders → 我的订单
/tenant/guidance → 租房攻略
/landlord → LandlordLayout.vue(房东端布局)
/landlord/dashboard → 首页
/landlord/houses → 我的房源
/landlord/orders → 订单管理
/admin → AdminLayout.vue (管理端布局)
/admin/dashboard → 首页
/admin/users → 用户管理
/admin/houses → 房源管理
/admin/orders → 订单管理
设计模式:Layout + 子路由
每个角色有一个 Layout 组件(提供侧边栏/顶栏壳子)+ <router-view> 嵌套子路由:
TenantLayout.vue
├── 侧边导航栏(仪表盘 / 浏览房源 / 我的订单 / 租房攻略)
└── <router-view /> ← 子路由页面渲染在这里
好处:切换子页面时只会替换内容区,侧边栏保持不动,避免页面整体刷新。
登录状态管理
没有用 Vuex/Pinia——直接用 localStorage:
// 登录:写入
localStorage.setItem('user', JSON.stringify(res.data))
// 读取:路由守卫和各个页面都从 localStorage 读
const user = JSON.parse(localStorage.getItem('user') || 'null')
9. 技术栈清单
| 模块 | 技术 | 选型理由 |
|---|---|---|
| 后端框架 | Spring Boot 3.4.3 | Java 生态最主流,自动配置 + 内嵌 Tomcat |
| ORM | MyBatis-Plus 3.5.10 | 零 SQL 基础 CRUD,Lambda 条件构造器,分页插件 |
| 数据库 | MySQL 8.0 | 关系型数据(用户/房源/订单天然适合关系模型) |
| 前端框架 | Vue 3 (Composition API) | 响应式 + 组件化,<script setup> 写法简洁 |
| 构建工具 | Vite 8 | 开发秒级热更新,比 Webpack 快一个数量级 |
| UI 组件库 | Element Plus | Vue 3 生态最成熟的后台 UI 库 |
| 路由 | Vue Router 4 | 嵌套路由 + 导航守卫,天然支持 Layout 模式 |
| HTTP 客户端 | Axios | 拦截器机制,请求/响应统一处理 |
| AI 推荐 | DeepSeek Chat API | 成本极低,中文能力强,API 兼容 OpenAI 格式 |
| 邮件服务 | Spring Mail (JavaMailSender) | Spring Boot 自动配置,几行代码发邮件 |
10. 功能清单
用户系统
- 用户注册
- 用户登录
租客端
- 浏览房源列表
- AI 智能推荐
- 下单租房
- 查看我的订单
房东端
- 房源管理
- 订单管理
管理端
- 用户管理
- 房源管理
- 订单管理
如果对你有帮助可以给个 Star ⭐,欢迎提 Issue 讨论。
GitHub:https://github.com/LuckLuffy/smart-rental
更多推荐

所有评论(0)