SpringBoot三端协同智能租房系统:前后端分离架构与权限实战拆解
2026/9/15 19:52:37 网站建设 项目流程

简介:基于SpringBoot+Vue前后端分离架构的智能租房全流程管理系统,面向需要完成毕业设计、课程设计或Java Web实战项目的开发者。系统覆盖管理端、屋主端、租客端三端协同,完整实现房源上架、编辑、下架,订单处理,预约看房,评价反馈,通知公告等核心业务,业务链路清晰,角色权限区分明确,能够有效提升租赁流程的信息化水平与交易透明度。资源共816个文件,压缩包26.43MB,包含Java后端源码、Vue组件、HTML页面、JavaScript脚本、CSS样式、SVG/PNG图标及GIF动效等前端素材;另含XML配置、数据库备份、启动/打包脚本、Markdown及Word说明文档,项目结构完整,便于直接导入开发环境运行。整体采用前后端分离架构,既适合学习SpringBoot接口开发、Vue页面交互与多角色权限设计,也可作为租房类管理系统的开发蓝本。目前已有45人学习,在此基础上扩展支付、地图或消息推送等模块也很方便。

1. 拆解三端协同的智能租房系统架构与 SpringBoot 前后端分离设计

拿到这套源码的时候,我的第一反应是去看它的目录结构——因为“智能租房全流程管理系统”这类毕设项目,市面上十有八九是单机版单体架构,管理端、屋主端、租客端全塞在一个 SpringBoot 应用里,页面用 Thymeleaf 渲染。但这个资源包里的文件分布完全不同:index.html.bakupdate-password.vue.bakIndexAsideStatic.vue.bak这些静态文件和 Vue 单文件组件明确告诉你,前端是独立工程,通过 Nginx 或 Node 服务托管,后端只暴露 JSON 接口。这意味着它的架构设计已经越过“能跑就行”的阶段,进入了“前后端分离 + 多端协同”的实战形态。

这套系统解决的核心问题,是传统租房管理中信息孤岛严重、角色权限边界模糊、业务链路断裂的痛点。管理端管全局、屋主端管房源、租客端管找房和交易,三端共用同一套 SpringBoot 后端服务,通过 JWT 令牌区分身份和权限。对于正在做 SpringBoot 相关课程设计或想学习前后端分离项目实战的开发者来说,这个包最值得拆解的地方不是业务有多复杂,而是三端权限如何设计、预约看房的时间冲突怎么处理、订单状态如何流转、前端打包后怎么与后端联调。

我建议你把下载好的压缩包解压后,先别急着用 IDEA 导入,跟着这篇拆解把工程结构、核心配置、典型业务链路逐个过一遍,再动手启动。这套系统里的业务模块覆盖了房东、租客、管理员三个角色的完整操作闭环,比单纯跑通一个 CRUD 案例有价值得多。下文我会从后端工程结构、前端构建配置、三端协同链路、安全权限拦截这几个维度展开,每个部分都给出可以直接套用的代码和参数说明。

2. 后端工程与数据模型:SpringBoot 主启动类、多模块仓储与表结构设计

2.1 主启动类与 Maven 依赖:先确认版本策略

先看后端工程的启动入口。SpringBoot 主启动类会被放在src/main/java下以公司域名反写的包结构中,例如com.rental.platform,类上标注@SpringBootApplication注解。这个注解组合了@Configuration@EnableAutoConfiguration@ComponentScan,所以它除了启动应用之外,还负责自动装配和组件扫描。你需要确认的是pom.xml中以下关键依赖是否齐全:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.13</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency> </dependencies>

我特意给出 2.7.x 这个版本,是因为看到你在搜索“springboot版本太高”的问题——如果你本机 JDK 是 8,而 pom 里用了 SpringBoot 3.x,启动时会直接报UnsupportedClassVersionError。2.7.13 是兼容 JDK 8 的最后几个版本之一,同时它和 MyBatis-Plus 3.5.3.1 的配合非常稳定。spring-boot-starter-security这里不一定直接用 Security 的过滤器链,很多三端项目只用它做密码加密(BCrypt),权限拦截器是手写的 HandlerInterceptor。

2.2 数据源配置与多租户预处理

application.yml是整个系统的命脉。三端共享同一套数据库,但通过tenant_idrole_type字段做数据隔离。你在配置数据源时按下述方式处理:

spring: datasource: url: jdbc:mysql://localhost:3306/rental_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

核心参数说明:serverTimezone=Asia/Shanghai解决 MySQL 8.x 与时区相关的报错;map-underscore-to-camel-case让数据库的create_time自动映射到实体类createTime,省略大量 XML 手写映射文件;deleted字段启用逻辑删除,房源下架时不物理删除数据,保留历史轨迹。Redis 如果你本机没有安装,建议先注释掉相关配置,因为有些版本会在启动时强校验 Redis 连接,导致整站起不来。这个设计对多租户的支撑在于:屋主端只查owner_id = 当前用户ID的房源,管理端则可以跨租户查询全部数据。

2.3 三端共享的领域模型:从表结构看懂业务边界

我在拆这种多端系统时,习惯先画一张表结构关系图,再去看代码,思路会清晰得多。这套系统至少包含以下核心表:

表名核心字段归属端作用
userid, username, password, role_type, phone三端共用登录认证基础表,role_type 区分 ADMIN / OWNER / TENANT
houseid, owner_id, title, address, price, status, audit_status屋主端 + 管理端房源信息,audit_status 区分待审/通过/驳回
house_imageid, house_id, url, sort屋主端房源多图展示
orderid, order_no, house_id, tenant_id, owner_id, status, pay_amount三端共用租赁订单,status 用数字表示待支付/已支付/已取消/已完成
appointmentid, house_id, tenant_id, owner_id, appoint_time, status, remark租客端 + 屋主端预约看房记录
commentid, house_id, tenant_id, content, rating, create_time租客端 + 管理端评价反馈
noticeid, title, content, publish_time, status管理端平台公告中心

house表里的statusaudit_status是两个不同业务字段。status表示房源当前是否上架可租,由屋主自己控制;audit_status表示管理员的审核结果,屋主编辑房源后需要重新审核。这两个字段分开设计,是为了避免“屋主改了一下价格,整个房源就被下架”的误操作。你在学习时注意区分这两个字段的赋值时机,这是租房系统里最容易出错的地方。

2.4 核心业务代码:房源按条件分页检索的 MyBatis-Plus 实现

三端中查询逻辑最重的是租客端的房源列表。常见做法是用LambdaQueryWrapper进行条件构造,但多维检索用 MPJLambdaWrapper(MyBatis-Plus Join)联表带聚合更直观:

public IPage<HouseVO> searchHouses(HouseQueryDTO query) { Page<House> page = new Page<>(query.getPageNum(), query.getPageSize()); MPJLambdaWrapper<House> wrapper = new MPJLambdaWrapper<>(); wrapper.select(House::getId, House::getTitle, House::getPrice, House::getAddress) .select(HouseImage::getUrl, HouseImage::getSort) .leftJoin(HouseImage.class, HouseImage::getHouseId, House::getId) .like(StringUtils.hasText(query.getKeyword()), House::getTitle, query.getKeyword()) .eq(query.getMinPrice() != null, House::getPrice, query.getMinPrice()) .le(query.getMaxPrice() != null, House::getPrice, query.getMaxPrice()) .orderByDesc(House::getCreateTime); return houseMapper.selectJoinPage(page, HouseVO.class, wrapper); }

这段代码的精髓在于两个条件构造器的使用:

  1. like(condition, column, value)第一个参数是 boolean 条件,StringUtils.hasText保证 keyword 为空时不拼 SQL,避免出现WHERE title LIKE '%%'这种无效查询。
  2. leftJoin联表查询获取房源封面图,select中指定了HouseImage::getSort,配合GROUP BY house_id可以取排序第一的图片作为封面缩略图。

Page<>分页参数会被 MyBatis-Plus 的 PaginationInnerInterceptor 自动改写 SQL,你在配置类里需要注册这个拦截器,否则分页不生效。如果你用的是 5.x 版本,注意需要额外引入mybatis-plus-jsqlparser依赖,因为分页和联表功能依赖 JSQLParser 做 SQL 解析。

3. 前后端分离实战落点:Vite 构建配置与 Vue 组件联调改造

3.1 资源包里的 .bak 文件说明了什么

压缩包里出现了IndexAsideStatic.vue.bakIndexHeader.vue.bakupdate-password.vue.bak这些备份文件。.bak后缀不是 SpringBoot 后端的产物,而是前端工程在迭代过程中保留的旧版本。它会出现在资源里,说明作者有过一次比较大的 UI 组件调整,把原来的静态布局改成了动态渲染模式。你在导入工程后,建议先不要急着删掉这些.bak文件,观察一下新旧版差异,能看到组件化改造的痕迹。

比如IndexAsideStatic.vue.bak很可能是侧边栏菜单的旧版,里面写死了菜单数组;对应的新版大概率把菜单改成了v-for动态渲染,菜单数据从后端登录接口返回,依据role_type动态过滤。这是三端共用同一套后台模板时最常用的做法——不拆三套前端,而是用权限控制菜单渲染。

3.2 Vue 请求层 Token 处理链路

搜索热词里反复出现“vue前后端分离请求token处理”,这是前后端分离项目里最关键的工程实践。下面给出这套系统里 axios 请求拦截器的标准写法:

// src/utils/request.js import axios from 'axios' import router from '@/router' import { ElMessage } from 'element-plus' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('rental_token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => { return Promise.reject(error) }) service.interceptors.response.use(response => { const res = response.data if (res.code === 401) { localStorage.removeItem('rental_token') router.push('/login') ElMessage.error('登录状态已过期,请重新登录') return Promise.reject(new Error('Unauthorized')) } return res }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('rental_token') router.push('/login') } return Promise.reject(error) }) export default service

这里的关键设计:

  1. baseURL设为/api而非完整域名,是为了配合 Vite 的 proxy 做跨域代理,避免浏览器直接向后端 8080 端口发请求产生 CORS 跨域。
  2. Authorization: Bearer <token>使用 Bearer Token 规范,后端过滤器从请求头解析Bearer前缀后取出 JWT 串。
  3. 响应拦截器统一处理 401,无论请求后端的哪个接口返回未授权,都会自动清除本地过期 token 并跳转登录页。如果后端返回的 JSON 结构使用status而非code字段,对应修改拦截条件即可。

3.3 Vite 代理与前端工程运行

这套系统大概率使用 Vue 3 + Vite,因为 Vue 2 的工程通常配合 webpack,目录结构会多出build/config/。Vite 的代理配置如下:

// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } } })

changeOrigin: true会修改请求头中的Host字段为target地址,防止后端某些框架做域名白名单校验时拦截。rewrite的作用是把前缀/api去掉,因为后端@RequestMapping上可能没有统一加/api前缀;如果后端所有接口都以/api开头,则不需要 rewrite。你在实际启动时,如果发现前端请求 404,优先排查这里是否匹配。

3.4 前端与 SpringBoot 的三种联调方式

在本地调试时,你不需要把前端打包后再放到 SpringBoot 的static目录下,那样效率太低。按场景选择联调方式:

联调方式启动命令适用场景
Vite 代理npm run dev访问 3000 端口日常开发调试,改前端代码热更新
前端打包npm run build产物放到 Nginx模拟生产环境,验证相对路径
丢进后端静态目录npm run build产物复制到src/main/resources/static最后交付演示时一个 JAR 包跑通

第三种方式适合课程设计最终提交,不用部署 Nginx。SpringBoot 会自动把classpath:/static/下的index.html作为欢迎页,注意 Vue Router 需要使用createWebHashHistory模式,否则刷新页面会出现 404。这是前后端分离项目打包集成时最容易踩的坑。

4. 三端协同链路实战:预约看房接口设计与订单流转的完整闭环

4.1 预约看房:时间冲突检测的幂等控制

租客端最典型的操作是“预约看房”。它的业务逻辑不是前端校验一下子就能落库的,要考虑同一房源同一时间段不能被多个租客预约,且同一租客不能对同一房源重复预约。下面给出 Controller 和 Service 的核心代码:

@PostMapping("/appointment") public R<String> createAppointment(@RequestBody AppointmentCreateDTO dto) { // 从 JWT 上下文获取当前登录租客 ID Long tenantId = SecurityContextHolder.getTenantId(); // 1. 校验该房源是否处于上架且审核通过状态 House house = houseService.getById(dto.getHouseId()); if (house == null || house.getStatus() != 1 || house.getAuditStatus() != 1) { return R.failed("房源不可预约"); } // 2. 幂等校验:同一租客同一房源同一时间段不能重复预约 long count = appointmentService.count(new LambdaQueryWrapper<Appointment>() .eq(Appointment::getTenantId, tenantId) .eq(Appointment::getHouseId, dto.getHouseId()) .eq(Appointment::getAppointTime, dto.getAppointTime()) .in(Appointment::getStatus, Arrays.asList(0, 1))); // 待确认、已确认 if (count > 0) { return R.failed("该时间段已有预约记录"); } // 3. 冲突检测:查询同房源同时段其他预约 long conflictCount = appointmentService.count(new LambdaQueryWrapper<Appointment>() .eq(Appointment::getHouseId, dto.getHouseId()) .eq(Appointment::getAppointTime, dto.getAppointTime()) .eq(Appointment::getStatus, 1)); // 只有已确认的预约才产生冲突 if (conflictCount > 0) { return R.failed("该时段已被其他租客预约"); } Appointment appointment = new Appointment(); appointment.setTenantId(tenantId); appointment.setOwnerId(house.getOwnerId()); appointment.setHouseId(dto.getHouseId()); appointment.setAppointTime(dto.getAppointTime()); appointment.setStatus(0); // 待屋主确认 appointmentService.save(appointment); // 4. 通知屋主(WebSocket 或站内信) notifyOwner(house.getOwnerId(), "您有新的看房预约,请及时确认"); return R.success("预约成功,等待屋主确认"); }

这个接口的四个步骤对应了完整的预约看房闭环。步骤 2 的幂等校验防止租客手抖多次点击按钮产生重复单;步骤 3 的冲突检测是关键,它的判断依据是status = 1(已确认),而不是所有记录都算冲突——如果屋主未确认的预约也占坑,就会被恶意预约刷掉其他真实租客。LambdaQueryWrapper.in(status, 0, 1)表示待确认和已确认的记录都参与幂等判断,但只有已确认的参与冲突判断,两者边界要分清。

4.2 管理端审核:从待审到上架的事件驱动

管理端处理房源审核时,用到了 SpringBoot 的事件发布机制,而不是在 Service 里直接同步调用通知逻辑。下面给出事件驱动实现:

// 自定义业务事件 public class HouseAuditEvent extends ApplicationEvent { private Long houseId; private Integer auditResult; // 1 通过,2 驳回 private String rejectReason; public HouseAuditEvent(Object source, Long houseId, Integer auditResult, String rejectReason) { super(source); this.houseId = houseId; this.auditResult = auditResult; this.rejectReason = rejectReason; } // getter/setter 省略 } // 审核服务中发布事件 @Service public class AuditService { @Autowired private ApplicationEventPublisher publisher; @Transactional public void auditHouse(Long houseId, Integer result, String reason) { House house = houseService.getById(houseId); house.setAuditStatus(result); houseService.updateById(house); // 审核通过后自动上架 if (result == 1) { house.setStatus(1); houseService.updateById(house); } // 发布事件,异步通知屋主 publisher.publishEvent(new HouseAuditEvent(this, houseId, result, reason)); } } // 事件监听者,异步发送站内信 @Component public class AuditEventListener { @Async @EventListener public void onAuditEvent(HouseAuditEvent event) { Notice notice = new Notice(); notice.setType(2); // 类型:审核通知 notice.setTargetUserId(houseService.getOwnerId(event.getHouseId())); notice.setContent(event.getAuditResult() == 1 ? "您的房源已审核通过并自动上架" : "您的房源被驳回:" + event.getRejectReason()); noticeService.save(notice); } }

@Async注解让监听器在独立线程池执行,用户无感知,不会阻塞审核请求主链路。注意需要在启动类上加@EnableAsync开启异步支持,否则@Async不生效,事件会在发布线程同步执行。审核通过后自动把status置为 1,这是“审核通过自动上架”业务规则的落地方式,屋主不需要再手动操作一次。

4.3 订单状态机流转

订单模块是三个端交互最频繁的地方。租客下单、屋主接单、租客支付、管理端仲裁,每一步都涉及状态变更。我建议用状态机枚举来管理订单状态流转,而不是在业务代码里散落if (status == 0) { status = 1 },后者后期几乎必定出错:

public enum OrderStatus { PENDING_PAYMENT(0, "待支付"), PAID(1, "已支付待签约"), COMPLETED(2, "已完成"), CANCELLED(3, "已取消"), REFUNDING(4, "退款中"), CLOSED(5, "已关闭"); private final int code; private final String desc; // 可流转状态表 private static final Map<Integer, List<Integer>> TRANSITIONS = new HashMap<>(); static { TRANSITIONS.put(PENDING_PAYMENT.code, Arrays.asList(PAID.code, CANCELLED.code, CLOSED.code)); TRANSITIONS.put(PAID.code, Arrays.asList(COMPLETED.code, REFUNDING.code)); TRANSITIONS.put(REFUNDING.code, Arrays.asList(CLOSED.code, PAID.code)); } public static boolean canTransit(int from, int to) { return TRANSITIONS.getOrDefault(from, Collections.emptyList()).contains(to); } }

状态机的好处是让非法流转在第一时间报错。比如从“已完成”直接改为“待支付”,调用canTransit会返回 false,业务层直接拒绝操作。三端系统中,管理端是唯一拥有强转权限的角色,可以在特殊情况下强制关闭订单,但屋主和租客的操作必须严格走状态机。订单号建议在 Service 层生成,规则可以参考yyyyMMddHHmmss + 随机4位数字 + 用户ID后四位,避免前端传入伪造订单号。

4.4 三端通知公告的消息推送

通知公告模块在三端协同中承担信息桥接作用,不只是一个简单的公告 CRUD。从业务体感看,管理端发布公告后,需要让不同角色看到不同的首页横幅;屋主收到预约提醒;租客收到审核结果。传统做法是轮询请求后端,但更合理的实现是 WebSocket。简单方案下,可以先用 WebSocket 做全局广播和点对点推送:

@Component public class NoticeWebSocketHandler extends TextWebSocketHandler { // 维护 userId -> WebSocketSession 的映射 private static final Map<Long, WebSocketSession> SESSIONS = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { Long userId = (Long) session.getAttributes().get("userId"); SESSIONS.put(userId, session); } // 点对点推送通知 public void sendToUser(Long userId, String message) { WebSocketSession session = SESSIONS.get(userId); if (session != null && session.isOpen()) { session.sendMessage(new TextMessage(message)); } } }

SESSIONS使用ConcurrentHashMap是必须的,因为 WebSocket 连接建立和断开都在不同线程中执行。在 WebSocket 握手拦截器中从 token 解析出 userId 并放入 session attributes,就能实现精准推送。如果不想在项目里引入独立的消息中间件,这种基于 Spring WebSocket 原生 API 的方式足够支撑课程设计规模。搜索词里已经有“springboot整合activemq”,如果你后续想升级为更健壮的消息推送架构,可以沿这个方向演进。

5. 权限与安全:JWT 过滤器链、角色校验与敏感数据加密的实践加固

5.1 JWT 认证过滤器:从请求头解析到上下文注入

三端系统区别于普通单角色系统,JWT 里必须携带角色信息,且过滤器要保证未登录用户无法访问任何受保护接口。下面给出自定义 JWT 过滤器的核心实现:

@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private StringRedisTemplate redisTemplate; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); try { DecodedJWT jwt = JWT.require(Algorithm.HMAC256("你的密钥")) .build() .verify(token); Long userId = jwt.getClaim("userId").asLong(); String role = jwt.getClaim("role").asString(); // 将用户信息存入 ThreadLocal,供 Service 层获取 UserContext.set(new CurrentUser(userId, role)); // 双重校验:Redis 中 token 必须存在(支持服务端主动登出) String redisKey = "login:token:" + userId; if (!token.equals(redisTemplate.opsForValue().get(redisKey))) { throw new RuntimeException("Token 已失效"); } } catch (Exception e) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType("application/json"); response.getWriter().write("{\"code\":401,\"message\":\"认证失败\"}"); return; } } chain.doFilter(request, response); } }

这个过滤器有两个关键设计:一是 JWT 无状态认证 + Redis 存 token 的组合,无状态保证服务可以横向扩展,Redis 保证权限变更或密码修改时可以主动踢人下线;二是OncePerRequestFilter确保一次请求只过滤一次,避免在重定向或转发场景重复执行。生产环境密钥不能写死在代码里,放到application.yml并通过@Value注入是基本操作。

5.2 拦截器注册与角色粒度的 URL 权限控制

有了 JWT 过滤器还不够,每个接口还需要角色校验。常见做法是实现HandlerInterceptor,在 preHandle 方法中通过 Ant 路径匹配来校验角色:

@Component public class RoleInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { CurrentUser user = UserContext.get(); if (user == null) { throw new BusinessException(401, "未登录"); } String uri = request.getRequestURI(); if (uri.startsWith("/admin") && !"ADMIN".equals(user.getRole())) { throw new BusinessException(403, "无管理员权限"); } if (uri.startsWith("/owner") && !"OWNER".equals(user.getRole())) { throw new BusinessException(403, "无屋主权限"); } if (uri.startsWith("/tenant") && !"TENANT".equals(user.getRole())) { throw new BusinessException(403, "无租客权限"); } return true; } }

同时注册到 WebMvcConfigurer 中,注意放行登录和验证码相关路径:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private RoleInterceptor roleInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(roleInterceptor) .addPathPatterns("/admin/**", "/owner/**", "/tenant/**") .excludePathPatterns("/admin/login", "/owner/login", "/tenant/login"); } }

addPathPatterns指定哪些路径规则走拦截器,excludePathPatterns放行登录接口。如果后端 Controller 里没有按/admin/owner/tenant前缀区分接口,则改为按 Controller 方法上的自定义注解校验,比如@RequireRole("OWNER"),拦截器通过反射获取 HandlerMethod 上的注解再做判断,两种方案按你的目录结构灵活选择。

5.3 Bootstrap 三端 Session 配置与密码加密

租客端和管理端的会话时长建议分开。管理员后台安全级别高,token 有效期设短;租客端体验优先,token 有效期设长。在application.yml中配置:

auth: admin-token-expire: 7200 # 管理端 2 小时 owner-token-expire: 86400 # 屋主端 24 小时 tenant-token-expire: 604800 # 租客端 7 天

密码加密不能使用 MD5,MD5 已被彩虹表攻破。使用 BCrypt:

public class PasswordUtil { public static String encode(String rawPassword) { return BCrypt.hashpw(rawPassword, BCrypt.gensalt(12)); } public static boolean matches(String rawPassword, String encodedPassword) { return BCrypt.checkpw(rawPassword, encodedPassword); } }

gensalt(12)中的 12 是迭代成本,数值越大计算越慢、越安全。对课程设计项目,12 是性和性能的平衡点;生产环境如果用户量不大,可以用到 13 或 14。你从网上下载的很多源码直接用了MD5Util,建议替换为 BCrypt,这个改动在简历上也可以写为“加固了用户密码存储安全性”。

6. 资源落地排查:构建脚本执行顺序适应性与高性能查询的另类提速方案

6.1 构建脚本的本质理解与顺序调整

压缩包里有三个.bat文件:1-install.bat2-run.bat3-build.bat,外加一个update.bat。先看路径下的内容再执行,不要双击跑完不管,因为每台机器环境不同。规范的本地启动顺序是:

# 1. 安装后端依赖 mvn clean install -DskipTests # 2. 初始化数据库脚本 mysql -uroot -p rental_system < sql/init.sql # 3. 安装前端依赖 cd frontend && npm install # 4. 启动后端 mvn spring-boot:run # 5. 启动前端开发服务器 cd frontend && npm run dev

那个3-build.bat大概率执行的是mvn clean package+npm run build,它的产物是后端 jar 包和前端静态文件。但注意前端npm install的镜像源如果是国外源,下载可能极慢,命令需要加--registry=https://registry.npmmirror.com,或者写一个.npmrc文件指定registry。这三个批处理文件的意义不在命令本身,而在顺序编排——先装依赖再起服务,依赖不装好直接2-run必挂。你可以在 IDEA 中直接右键运行启动类,绕过批处理排查问题。

6.2 端身份在跨端跳转中的 session 路由逻辑

三端虽然共用后端,但前端登录后的落地路由各有不同。这里的通行设计是在登录接口返回roleTyperedirectUrl,前端根据角色值做动态路由跳转,而不是写死跳转地址:

if (res.data.roleType === 'ADMIN') { window.location.href = '/admin/dashboard' } else if (res.data.roleType === 'OWNER') { window.location.href = '/owner/house-list' } else if (res.data.roleType === 'TENANT') { window.location.href = '/tenant/house-search' }

如果你看到源码中有根据路由地址前缀去推断角色的逻辑,且后端接口按/admin/owner/tenant做了 Controller 拆分,那这个方案就是一致的。还有一种较少的做法是后端直接返回 Vue 组件路径字符串,前端router.addRoute动态注册,这种方案更灵活,但排查问题时路由表是动态生成的,不太直观。

6.3 布隆过滤器预判房源可见性的另类方案

租客端首页和搜索页的流量远高于管理端和屋主端,一次搜索请求可能涉及房源表、图片表、标签表三张表关联,频繁查库压力大。在你的业务体量还没到上 Elasticsearch 的规模时,我建议用 Redis + 布隆过滤器做前置拦截,只让综合热度高的房源走详细查询链路:

@Configuration public class BloomFilterConfig { @Bean public BloomFilter<String> houseBloomFilter() { // 预期插入量 10000,误判率 1% BloomFilter<String> filter = BloomFilter.create( Funnels.stringFunnel(StandardCharsets.UTF_8), 10000, 0.01); return filter; } @Bean public CommandLineRunner bloomInit(BloomFilter<String> filter, RedisTemplate<String, String> redisTemplate) { return args -> { List<String> hotIds = redisTemplate.opsForList() .range("hot:house:ids", 0, -1); if (hotIds != null) { hotIds.forEach(filter::put); } }; } }

布隆过滤器的误判率参数设为 0.01 意味着有 1% 的房源可能被错误过滤掉——真实存在的房源显示“不存在”。所以布隆过滤器只适合拦截“一定不存在”的请求,命中过滤器的继续走数据库,未命中的直接返回空结果,而数据初始化时只放入近 7 天下的房源 ID 即可。这个方案对课程设计而言是加分项,你可以把它作为“网关层预判”写在简历项目描述里,比单纯写 Redis 缓存有细节可信度。启动时加载热点 ID 的入口,和1-install.bat执行顺序配合好,就能在本地完整验证这套链路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询