毕设所有选题:
https://blog.csdn.net/2303_76227485/article/details/131104075
基于Springboot+Vue3+协同过滤的房屋租赁管理系统(源代码+数据库+万字论文+设计文档)
项目编号:264
一、系统介绍
本项目前后端分离,分为用户、房东、管理员3种角色。
1、用户:
- 注册登录、房屋推荐(协同过滤算法)、公告、房屋搜索、房屋信息浏览、房屋租赁(支付、取消)、我的订单、修改密码、个人信息修改
2、房东:
- 登录、房屋管理(增删改查、上下架、导出)、订单管理、订单确认、收支明细、租赁管理、合同生成下载、修改密码、个人信息修改
2、管理员:
- 首页大屏数据图表统计:用户数、房源数、订单数、交易总额、月度订单统计柱状图、房源类型分布环状图
- 房屋管理(增删改查、上下架)、房屋类型管理、订单管理、收支管理、用户管理、租赁管理、轮播图管理、公告管理
3、亮点:
- 使用协同过滤算法(基于用户行为)为用户推荐房屋
- 后台首页大屏使用echarts图表统计,更直观的看出系统的运行数据
二、所用技术
后端技术栈:
- Springboot3
- mybatisPlus
- Jwt
- SpringSecurity
- Mysql
- Maven
前端技术栈:
- Vue3
- Vue-router
- axios
- elementPlus
- echarts
三、环境介绍
基础环境 :IDEA/eclipse, JDK1.8, Mysql5.7及以上, Maven3.6, node14, navicat
所有项目以及源代码本人均调试运行无问题 可支持远程调试运行
四、页面截图
文档截图:


1、用户:















2、房东:








3、管理员:










4、亮点:
- 使用协同过滤算法(基于用户行为)为用户推荐房屋
- 实现ai对话优化用户体验
- 后台首页大屏使用echarts图表统计,更直观的看出系统的运行数据
五、浏览地址
-
用户账号密码:zhangsan/123456
-
房东账户密码:landlord2/123456
-
管理员账户密码:admin/123456
六、部署教程
-
使用Navicat或者其它工具,在mysql中创建对应名称的数据库,并执行项目的sql文件
-
使用IDEA/Eclipse导入springboot项目,若为maven项目请选择maven,等待依赖下载完成
-
修改application.yml里面的数据库配置,src/main/java/org/example/springboot/SpringbootApplication.java启动后端项目
-
vscode或idea打开vue3项目
-
在编译器中打开terminal,执行npm install 依赖下载完成后执行 npm run serve,执行成功后会显示访问地址
七、房屋协同过滤推荐部分代码
java
/**
* 协同过滤推荐房屋(入参仅status、currentPage、size)
* @param param 入参对象(包含status、currentPage、size)
* @return 分页推荐房屋列表
*/
public IPage<House> recommendHouses(RecommendParam param) {
// 获取当前登录用户
User currentUser = JwtTokenUtils.getCurrentUser();
// 构建分页对象
Page<House> page = new Page<>(param.getCurrentPage(), param.getSize());
// 基础查询条件:仅查询指定状态(待出租)的房屋
LambdaQueryWrapper<House> baseQuery = new LambdaQueryWrapper<House>()
.eq(House::getStatus, param.getStatus())
.orderByDesc(House::getCreateTime);
// 1. 有登录用户:执行双层协同过滤推荐
if (currentUser != null) {
Long userId = currentUser.getId();
// 获取用户历史所有订单
List<Order> userOrders = getCurrentUserOrders(userId);
// 提取当前用户的房屋偏好特征(类型ID、价格区间)
UserHousePreference userPreference = extractUserHousePreference(userOrders);
// 第一层推荐:基于当前用户自身订单(原有逻辑)
IPage<House> personalRecommendPage = recommendByPersonalPreference(param.getStatus(), userPreference, page);
if (personalRecommendPage.getTotal() > 0) {
personalRecommendPage.getRecords().forEach(this::fillHouseBasicInfo);
return personalRecommendPage;
}
// 第二层推荐:基于相似用户行为(新增核心逻辑)
if (userPreference.hasPreference() || !userOrders.isEmpty()) {
IPage<House> similarUserRecommendPage = recommendBySimilarUser(
userId, param.getStatus(), userPreference, page);
if (similarUserRecommendPage.getTotal() > 0) {
similarUserRecommendPage.getRecords().forEach(this::fillHouseBasicInfo);
return similarUserRecommendPage;
}
}
}
// 3. 兜底层:无登录/无偏好/无推荐结果 → 返回最新待出租房屋
IPage<House> hotHousePage = houseMapper.selectPage(page, baseQuery);
hotHousePage.getRecords().forEach(this::fillHouseBasicInfo);
return hotHousePage;
}
// ====================== 第一层:基于当前用户自身订单推荐 ======================
private IPage<House> recommendByPersonalPreference(Integer status, UserHousePreference preference, Page<House> page) {
LambdaQueryWrapper<House> query = new LambdaQueryWrapper<House>()
.eq(House::getStatus, status);
// 拼接个人偏好条件:同类型 或 价格在区间内
if (preference.hasPreference()) {
query.and(wrapper -> {
if (!preference.getHouseTypeIds().isEmpty()) {
wrapper.in(House::getTypeId, preference.getHouseTypeIds());
}
if (preference.getMinPrice() != null && preference.getMaxPrice() != null) {
wrapper.or().between(House::getPrice, preference.getMinPrice(), preference.getMaxPrice());
}
});
}
query.orderByDesc(House::getCreateTime);
return houseMapper.selectPage(page, query);
}
// ====================== 第二层:基于相似用户行为推荐(新增) ======================
private IPage<House> recommendBySimilarUser(Long currentUserId, Integer status,
UserHousePreference currentPref, Page<House> page) {
// 1. 查找与当前用户偏好相似的其他用户ID(排除自己)
Set<Long> similarUserIds = findSimilarUserIds(currentUserId, currentPref);
if (similarUserIds.isEmpty()) {
return new Page<>(page.getCurrent(), page.getSize(), 0);
}
// 2. 提取相似用户的所有订单,过滤出有效房屋订单
List<Order> similarUserOrders = getSimilarUserOrders(similarUserIds, status);
if (similarUserOrders.isEmpty()) {
return new Page<>(page.getCurrent(), page.getSize(), 0);
}
// 3. 提取相似用户订单中的房屋ID(去重,排除当前用户已订房屋)
Set<Long> currentUserHouseIds = getCurrentUserHouseIds(currentUserId);
Set<Long> recommendHouseIds = similarUserOrders.stream()
.map(Order::getHouseId)
.filter(houseId -> !currentUserHouseIds.contains(houseId))
.collect(Collectors.toSet());
if (recommendHouseIds.isEmpty()) {
return new Page<>(page.getCurrent(), page.getSize(), 0);
}
// 4. 查询相似用户偏好的房屋,按订单数量排序(下单越多越推荐)
LambdaQueryWrapper<House> query = new LambdaQueryWrapper<House>()
.eq(House::getStatus, status)
.in(House::getId, recommendHouseIds)
.orderByDesc(House::getCreateTime);
return houseMapper.selectPage(page, query);
}
// 查找相似用户ID:匹配房屋类型 或 价格区间重叠
private Set<Long> findSimilarUserIds(Long currentUserId, UserHousePreference currentPref) {
// 获取所有有订单的用户(排除当前用户)
List<Order> allUserOrders = orderMapper.selectList(new LambdaQueryWrapper<Order>()
.ne(Order::getTenantId, currentUserId));
if (allUserOrders.isEmpty()) {
return Collections.emptySet();
}
// 按用户ID分组,提取每个用户的房屋偏好
Map<Long, UserHousePreference> userPreferenceMap = allUserOrders.stream()
.collect(Collectors.groupingBy(Order::getTenantId))
.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> extractUserHousePreference(entry.getValue())
));
// 筛选出与当前用户偏好相似的用户
Set<Long> similarUserIds = new HashSet<>();
for (Map.Entry<Long, UserHousePreference> entry : userPreferenceMap.entrySet()) {
UserHousePreference userPref = entry.getValue();
if (isUserSimilar(currentPref, userPref)) {
similarUserIds.add(entry.getKey());
// 限制相似用户数量,避免查询过多
if (similarUserIds.size() >= MAX_SIMILAR_USER) {
break;
}
}
}
return similarUserIds;
}
// 获取当前用户已订的房屋ID(去重)
private Set<Long> getCurrentUserHouseIds(Long userId) {
return getCurrentUserOrders(userId).stream()
.map(Order::getHouseId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
// 提取用户房屋偏好(核心:类型ID+价格区间)→ 封装为实体类
private UserHousePreference extractUserHousePreference(List<Order> orders) {
Set<Long> houseTypeIds = new HashSet<>();
List<BigDecimal> prices = new ArrayList<>();
for (Order order : orders) {
House house = houseMapper.selectById(order.getHouseId());
if (house == null) {
continue;
}
// 提取房屋类型ID
if (house.getTypeId() != null) {
houseTypeIds.add(house.getTypeId());
}
// 提取房屋价格
if (house.getPrice() != null && house.getPrice().compareTo(BigDecimal.ZERO) > 0) {
prices.add(house.getPrice());
}
}
// 计算价格区间:平均价±20%
BigDecimal avgPrice = calculateAvgPrice(prices);
BigDecimal minPrice = avgPrice != null ? avgPrice.multiply(BigDecimal.ONE.subtract(PRICE_RANGE_RATIO)) : null;
BigDecimal maxPrice = avgPrice != null ? avgPrice.multiply(BigDecimal.ONE.add(PRICE_RANGE_RATIO)) : null;
return new UserHousePreference(houseTypeIds, minPrice, maxPrice);
}
// 计算价格平均值
private BigDecimal calculateAvgPrice(List<BigDecimal> prices) {
if (prices.isEmpty()) {
return null;
}
BigDecimal sum = prices.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(new BigDecimal(prices.size()), 2, BigDecimal.ROUND_HALF_UP);
}
// 判断两个用户是否相似:类型交集≥1 或 价格区间重叠
private boolean isUserSimilar(UserHousePreference pref1, UserHousePreference pref2) {
// 类型匹配:有至少一个相同的房屋类型
if (!pref1.getHouseTypeIds().isEmpty() && !pref2.getHouseTypeIds().isEmpty()) {
Set<Long> intersection = new HashSet<>(pref1.getHouseTypeIds());
intersection.retainAll(pref2.getHouseTypeIds());
if (intersection.size() >= SIMILAR_USER_MIN_MATCH) {
return true;
}
}
// 价格匹配:两个用户的价格区间有重叠
if (pref1.getMinPrice() != null && pref1.getMaxPrice() != null
&& pref2.getMinPrice() != null && pref2.getMaxPrice() != null) {
return pref1.getMinPrice().compareTo(pref2.getMaxPrice()) <= 0
&& pref2.getMinPrice().compareTo(pref1.getMaxPrice()) <= 0;
}
return false;
}
// 提取相似用户的所有有效订单
private List<Order> getSimilarUserOrders(Set<Long> similarUserIds, Integer status) {
// 提取相似用户订单,并关联房屋状态为待出租
List<Order> orders = orderMapper.selectList(new LambdaQueryWrapper<Order>()
.in(Order::getTenantId, similarUserIds));
if (orders.isEmpty()) {
return Collections.emptyList();
}
// 过滤出订单对应的房屋为待出租的订单
Set<Long> houseIds = orders.stream().map(Order::getHouseId).collect(Collectors.toSet());
List<House> validHouses = houseMapper.selectList(new LambdaQueryWrapper<House>()
.eq(House::getStatus, status)
.in(House::getId, houseIds));
Set<Long> validHouseIds = validHouses.stream().map(House::getId).collect(Collectors.toSet());
return orders.stream()
.filter(order -> validHouseIds.contains(order.getHouseId()))
.collect(Collectors.toList());
}
/**
* 获取当前用户所有历史订单
*/
private List<Order> getCurrentUserOrders(Long userId) {
LambdaQueryWrapper<Order> query = new LambdaQueryWrapper<Order>()
.eq(Order::getTenantId, userId);
return orderMapper.selectList(query);
}
/**
* 填充房屋基础信息:类型名称、房东姓名/头像(复用HouseService的核心逻辑)
*/
private void fillHouseBasicInfo(House house) {
if (house == null) {
return;
}
// 填充房屋类型名称
if (house.getTypeId() != null) {
HouseType houseType = houseTypeMapper.selectById(house.getTypeId());
if (houseType != null) {
house.setTypeName(houseType.getName());
}
}
// 填充房东信息
if (house.getLandlordId() != null) {
User landlord = userMapper.selectById(house.getLandlordId());
if (landlord != null) {
house.setLandlordName(landlord.getName());
house.setLandlordImg(landlord.getAvatar());
}
}
}
/**
* 填充房屋类型名称和房东姓名
* @param house 房屋信息
*/
private void fillHouseInfo(House house) {
if (house == null) {
return;
}
// 填充房屋类型名称
if (house.getTypeId() != null) {
HouseType houseType = houseTypeMapper.selectById(house.getTypeId());
if (houseType != null) {
house.setTypeName(houseType.getName());
}
}
// 填充房东姓名
if (house.getLandlordId() != null) {
User landlord = userMapper.selectById(house.getLandlordId());
if (landlord != null) {
house.setLandlordName(landlord.getName());
house.setLandlordImg(landlord.getAvatar());
}
}
}