计算机毕业设计264—基于Springboot+Vue3+协同过滤的房屋租赁管理系统(源代码+数据库+万字论文+设计文档)

毕设所有选题:
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图表统计,更直观的看出系统的运行数据

五、浏览地址

前台地址:http://localhost:3000

  • 用户账号密码:zhangsan/123456

  • 房东账户密码:landlord2/123456

  • 管理员账户密码:admin/123456

六、部署教程

  1. 使用Navicat或者其它工具,在mysql中创建对应名称的数据库,并执行项目的sql文件

  2. 使用IDEA/Eclipse导入springboot项目,若为maven项目请选择maven,等待依赖下载完成

  3. 修改application.yml里面的数据库配置,src/main/java/org/example/springboot/SpringbootApplication.java启动后端项目

  4. vscode或idea打开vue3项目

  5. 在编译器中打开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());
        }
        }
        }
相关推荐
qq_297574679 小时前
【实战】POI 实现 Excel 多级表头导出(含合并单元格完整方案)
java·spring boot·后端·excel
码界筑梦坊9 小时前
327-基于Django的兰州空气质量大数据可视化分析系统
python·信息可视化·数据分析·django·毕业设计·数据可视化
风流倜傥唐伯虎18 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
fuquxiaoguang18 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
WangYaolove131419 小时前
基于python的在线水果销售系统(源码+文档)
python·mysql·django·毕业设计·源码
毕设源码_廖学姐19 小时前
计算机毕业设计springboot招聘系统网站 基于SpringBoot的在线人才对接平台 SpringBoot驱动的智能求职与招聘服务网
spring boot·后端·课程设计
顾北1219 小时前
MCP服务端开发:图片搜索助力旅游计划
java·spring boot·dubbo
昀贝20 小时前
IDEA启动SpringBoot项目时报错:命令行过长
java·spring boot·intellij-idea
indexsunny21 小时前
互联网大厂Java面试实战:Spring Boot微服务在电商场景中的应用与挑战
java·spring boot·redis·微服务·kafka·spring security·电商