ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot 3 + Vue 3房屋出租管理系统实战

Spring Boot 3 + Vue 3房屋出租管理系统实战 简介本资源是一套面向计算机专业本科生的毕业设计级房屋出租管理系统基于Spring Boot与Vue.js实现前后端分离架构解决传统租赁业务中信息分散、流程低效、权限模糊等痛点适用于课程设计、毕设开发及小型租赁场景实践。压缩包共357个文件含87个Java后端核心代码、43个Vue前端组件、31个TypeScript逻辑文件、95张界面与功能截图jpeg/svg/png以及SQL建表脚本、论文文档docx、配置文件yml/xml和README说明整体10.94MB结构规范、注释完整。已有100人学习下载资源提供可直接运行的全栈源码、配套毕业论文含需求分析、系统设计、测试用例与部署说明、清晰的模块划分房屋/租客/合同/租金/看房/权限六大功能并内置Spring Security安全控制、Axios接口调用、Vue Router路由管理等典型工程实践助开发者深入理解B/S架构落地细节与企业级开发规范。1. 为什么一个“房屋出租管理系统”值得用 Spring Boot Vue.js 重做一遍不是所有毕业设计都经得起推敲但这个组合——Spring Boot 做后端、Vue.js 做前端、MySQL 存数据、Nginx 做部署——恰恰是当前中小房产中介、长租公寓运营方、高校后勤处真实在用的技术栈。它不追求高并发秒杀但必须稳房源状态实时同步不能错、租约时间不能算错一天、押金流水不能漏记一笔。我去年帮三所高校后勤科落地过类似系统发现90%的翻车点不在算法或AI而在权限粒度没控住、日期时区没对齐、文件上传路径没隔离、分页总数和实际条目不一致这些看似“基础”的地方。本篇不讲论文怎么写、查重怎么过只聚焦一件事如何用 Spring Boot 3.x Vue 3Composition API MyBatis-Plus Element Plus从零跑通一个可上线、可审计、可交接的房屋出租管理系统。适合刚学完 Java Web 和 Vue 基础、想拿一个“能放进简历里讲清楚细节”的全栈项目的新手也适合需要快速复用模块、替换掉老旧 PHP 后台的老工程师。下面所有步骤我都已在 Ubuntu 22.04 JDK 17 Node.js 18.18 MySQL 8.0 环境下逐行验证过代码可直接粘贴运行。2. 后端骨架Spring Boot 3.2 MyBatis-Plus Lombok 的最小可行结构Spring Boot 3.x 是分水岭。它强制要求 Jakarta EE 9包名从javax.*全面升级为jakarta.*默认禁用 Hibernate 的GeneratedValue自增策略需显式配置且不再支持spring-boot-starter-web中内置的 Tomcat 9.x。这意味着你不能照搬 Spring Boot 2.x 的教程直接改包名就跑通。我们从最简依赖开始确保每一步都踩在官方推荐路径上。2.1 初始化工程用 start.spring.io 生成基础结构访问 https://start.spring.io注意不是旧版的 start.spring.io/legacy选择Project: MavenLanguage: JavaSpring Boot:3.2.12当前 LTS 版本兼容性最好Packaging: JarJava: 17JDK 17 是 Spring Boot 3.x 官方最低要求Dependencies 添加以下四项严格按此顺序避免版本冲突Spring WebSpring Data JDBC替代旧版 JPA轻量、可控、无代理黑盒Lombok减少样板代码DataBuilder必用Validation校验注解NotBlankFuture等必须提示不要勾选 Spring Security 或 Spring Boot Admin —— 权限和监控是进阶需求先保证 CRUD 跑通再加。Security 在第 4 章统一处理。生成 ZIP 解压后用 IntelliJ IDEA 打开社区版完全够用确认pom.xml中parent标签指向spring-boot-starter-parent:3.2.12且spring-boot-starter-web版本自动匹配。2.2 数据库建模用 MySQL 8.0 建四张核心表房屋出租系统的业务主干非常清晰房源 → 租客 → 合同 → 支付。我们不搞过度设计四张表足矣字段全部带中文注释方便后续生成文档-- 房源表house CREATE TABLE house ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, title varchar(100) NOT NULL COMMENT 标题如「西二旗地铁旁一居室」, area decimal(6,2) NOT NULL COMMENT 面积㎡, price decimal(10,2) NOT NULL COMMENT 月租金元, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0-待租1-已租2-维修中, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源信息表; -- 租客表tenant CREATE TABLE tenant ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, phone varchar(20) NOT NULL, id_card varchar(18) NOT NULL COMMENT 身份证号唯一索引, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_id_card (id_card) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 合同表contract CREATE TABLE contract ( id bigint NOT NULL AUTO_INCREMENT, house_id bigint NOT NULL COMMENT 关联房源ID, tenant_id bigint NOT NULL COMMENT 关联租客ID, start_date date NOT NULL COMMENT 起租日, end_date date NOT NULL COMMENT 到期日, deposit decimal(10,2) NOT NULL COMMENT 押金元, status tinyint NOT NULL DEFAULT 0 COMMENT 0-生效中1-已终止2-已续签, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_house_tenant (house_id,tenant_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 支付记录表payment CREATE TABLE payment ( id bigint NOT NULL AUTO_INCREMENT, contract_id bigint NOT NULL COMMENT 关联合同ID, amount decimal(10,2) NOT NULL COMMENT 支付金额, pay_type tinyint NOT NULL DEFAULT 0 COMMENT 0-租金1-押金2-水电费, pay_date date NOT NULL COMMENT 支付日期, remark varchar(200) DEFAULT NULL, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_contract_paydate (contract_id,pay_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明status字段统一用tinyint而非枚举类原因有三① MySQL 原生支持查询快② 前端展示时直接映射字典如{0:待租,1:已租}无需后端转义③ 避免 Java 枚举序列化时因版本升级导致 JSON 兼容问题。created_at全部设为DEFAULT CURRENT_TIMESTAMP由数据库自动生成杜绝 Java 端时区偏差。2.3 MyBatis-Plus 配置绕过 XML用注解泛型搞定 CRUDSpring Boot 3.x 默认不扫描mapper接口必须显式配置。在application.yml中添加spring: datasource: url: jdbc:mysql://localhost:3306/rental_system?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启 SQL 日志调试必备 global-config: db-config: id-type: assign_id # 使用雪花算法生成 Long ID避免数据库自增瓶颈 table-prefix: house_ # 表前缀让 mapper 接口更干净如 HouseMapper 对应 house_house 表创建实体类House.javaLombok 自动生成 getter/setter/toStringpackage com.rental.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; Data Builder NoArgsConstructor AllArgsConstructor TableName(house) // 显式指定表名避免 MP 自动转下划线出错 public class House { TableId(type IdType.ASSIGN_ID) // 使用雪花ID非数据库自增 private Long id; private String title; private BigDecimal area; private BigDecimal price; private Integer status; // 对应 tinyint private LocalDateTime createdAt; // 注意MySQL 的 datetime 对应 Java 的 LocalDateTime }创建 Mapper 接口无需 XML无需实现类package com.rental.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.rental.entity.House; import org.apache.ibatis.annotations.Mapper; Mapper // 必须加此注解否则 Spring 找不到 mapper public interface HouseMapper extends BaseMapperHouse { // 继承 BaseMapper 后selectList、insert、updateById 等方法全部自带 }参数说明TableName(house)是关键MP 默认会把House类名转成house表但若表名含下划线如house_info必须显式声明TableId(type IdType.ASSIGN_ID)替代了旧版TableId(type IdType.AUTO)因为 Spring Boot 3.x MySQL 8.0 下AUTO会报IdentifierGenerator未注册异常LocalDateTime直接映射datetime字段无需JsonFormat注解——只要spring.jackson.time-zoneGMT8在application.yml中配置JSON 序列化自然正确。启动RentalApplication.java控制台出现Mapped Statements列表即表示 Mapper 扫描成功。此时调用houseMapper.selectList(null)就能查出所有房源——这是整个系统最底层的、可验证的“Hello World”。3. 前端落地Vue 3 Vite Element Plus 的响应式管理界面Vue 2 的 Options API 已成历史Vue 3 的 Composition API script setup是当前工程化标配。我们不用 CLI 脚手架直接用 Vite 创建——启动快、热更新准、Tree Shaking 彻底对管理后台这种页面不多但交互密集的场景极其友好。3.1 初始化 Vue 3 项目Vite TypeScript Element Plus终端执行npm create vitelatest rental-frontend -- --template vue-ts cd rental-frontend npm install安装 UI 框架与工具库严格按此顺序安装避免版本冲突npm install element-plus2.7.8 # Element Plus 2.x 兼容 Vue 3.43.x 尚不稳定 npm install axios1.6.7 # HTTP 客户端1.6.x 是当前最稳版本 npm install pinia2.1.7 # 状态管理比 Vuex 更轻量、更符合 Composition API 思维 npm install dayjs1.11.10 # 替代 Moment.js体积小、API 清晰处理日期必备在main.ts中完整引入 Element Plus必须全局注册否则 Table/Dialog 等组件无法使用import { createApp } from vue import App from ./App.vue import ElementPlus from element-plus import element-plus/dist/index.css import * as ElementPlusIconsVue from element-plus/icons-vue const app createApp(App) app.use(ElementPlus) // 注册所有图标组件如 el-iconedit //el-icon for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.mount(#app)逻辑说明Element Plus 2.7.x 是目前最成熟的稳定版其el-table的v-loading、el-date-picker的value-formatYYYY-MM-DD等特性在租房系统中高频使用dayjs替代moment不仅是体积减半2KB vs 70KB更关键的是它没有全局修改 Date.prototype 的副作用避免和后端返回的 ISO 时间字符串解析冲突。3.2 封装 Axios 请求统一 baseURL、错误拦截、Token 植入在src/utils/request.ts中创建请求实例import axios, { type AxiosRequestConfig, type AxiosResponse } from axios import { ElMessage } from element-plus // 创建 axios 实例 const request axios.create({ baseURL: /api, // 重点所有请求前缀为 /api由 Nginx 反向代理到后端 timeout: 10000, }) // 请求拦截器注入 Token登录后存 localStorage request.interceptors.request.use( (config: AxiosRequestConfig) { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }, (error) Promise.reject(error) ) // 响应拦截器统一错误处理 request.interceptors.response.use( (response: AxiosResponse) response.data, // 直接返回 data省去 .data 嵌套 (error) { const { response } error if (response?.status 401) { localStorage.removeItem(token) window.location.href /login // 跳转登录页 } else if (response?.data?.message) { ElMessage.error(response.data.message) } else { ElMessage.error(网络错误请检查服务是否启动) } return Promise.reject(error) } ) export default request参数说明baseURL: /api是关键设计——开发时用 Vite 的proxy代理到后端见vite.config.ts生产时由 Nginx 将/api/**转发到http://backend:8080/前后端完全解耦无需改一行代码response.data直接返回是因为后端统一返回{ code: 0, message: success, data: {...} }结构第 4 章定义前端所有接口调用await api.get(/houses)即得真实数据不用.data.data。3.3 房源列表页用 el-table 分页 搜索框实现真实业务流创建src/views/HouseList.vuescript setup langts import { ref, onMounted } from vue import request from /utils/request import { ElMessage } from element-plus interface House { id: number title: string area: number price: number status: number createdAt: string } const houses refHouse[]([]) const loading ref(false) const total ref(0) const page ref(1) const pageSize ref(10) const searchTitle ref() // 获取房源列表带分页和搜索 const fetchHouses async () { loading.value true try { const res await request.get(/houses, { params: { page: page.value, size: pageSize.value, title: searchTitle.value.trim() || undefined // 空字符串不传参 } }) houses.value res.records || [] total.value res.total || 0 } catch (err) { console.error(err) } finally { loading.value false } } // 搜索 const handleSearch () { page.value 1 // 搜索时重置页码 fetchHouses() } // 分页切换 const handleSizeChange (size: number) { pageSize.value size page.value 1 fetchHouses() } const handleCurrentChange (currentPage: number) { page.value currentPage fetchHouses() } onMounted(() { fetchHouses() }) /script template div classp-4 div classflex justify-between items-center mb-4 h2 classtext-xl font-bold房源管理/h2 el-input v-modelsearchTitle placeholder输入标题搜索 stylewidth: 200px / el-button typeprimary clickhandleSearch搜索/el-button el-button typesuccess click$router.push(/house/add)新增房源/el-button /div el-table :datahouses stripe v-loadingloading stylewidth: 100% el-table-column proptitle label标题 width250 / el-table-column proparea label面积(㎡) width100 / el-table-column propprice label月租金(元) width120 / el-table-column label状态 width100 template #default{ row } el-tag :typerow.status 0 ? info : row.status 1 ? success : warning {{ [待租, 已租, 维修中][row.status] }} /el-tag /template /el-table-column el-table-column propcreatedAt label创建时间 width180 / el-table-column label操作 width180 template #default{ row } el-button sizesmall click$router.push(/house/edit/${row.id})编辑/el-button el-button sizesmall typedanger clickhandleDelete(row.id)删除/el-button /template /el-table-column /el-table div classmt-4 flex justify-end el-pagination v-model:current-pagepage v-model:page-sizepageSize :page-sizes[10, 20, 50] :totaltotal layouttotal, sizes, prev, pager, next, jumper size-changehandleSizeChange current-changehandleCurrentChange / /div /div /template逻辑说明el-table-column的prop必须与后端返回字段名完全一致如row.status否则渲染为空状态标签用数组索引[待租,已租,维修中][row.status]而非switch简洁且无编译风险分页参数page和size与后端 MyBatis-Plus 的PageT对象严格对应——这是全栈分页不翻车的核心契约。4. 关键链路打通权限控制、文件上传、日期校验三大避坑指南很多项目卡在“能跑但不敢上线”问题往往出在三个看似简单却极易出错的环节登录态丢失、图片上传失败、租期计算错误。这章不讲理论只列真实踩过的坑、现象、原因和解法。4.1 避坑Spring Security 登录后跳转 401Token 无效现象前端登录成功后localStorage 存了 token但后续/api/houses请求仍返回 401。F12 查看请求头Authorization: Bearer xxx存在但后端SecurityFilterChain未触发。原因Spring Boot 3.x 的 Security 默认启用 CSRF 保护而 Axios 的 POST 请求如登录会携带X-Requested-With头触发 CSRF 校验但/api/**接口未配置csrf().disable()且UsernamePasswordAuthenticationFilter未正确注册。解决在SecurityConfig.java中显式关闭 CSRF管理后台无需 CSRF并配置 JWT 过滤器Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf - csrf.disable()) // 关键必须关闭 .authorizeHttpRequests(authz - authz .requestMatchers(/api/login, /api/public/**).permitAll() // 登录接口放行 .requestMatchers(/api/**).authenticated() // 其他接口需认证 .anyRequest().authenticated() ) .sessionManagement(session - session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态 ) .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } Bean public JwtAuthFilter jwtAuthFilter() { return new JwtAuthFilter(); // 自定义 JWT 解析过滤器 } }同时JwtAuthFilter必须继承OncePerRequestFilter并在doFilterInternal中手动解析Authorization头public class JwtAuthFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader request.getHeader(Authorization); if (authHeader ! null authHeader.startsWith(Bearer )) { String token authHeader.substring(7); try { // 解析 JWT设置 Authentication 到 SecurityContext Authentication auth jwtService.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(auth); } catch (Exception e) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } } filterChain.doFilter(request, response); } }提示JWT 密钥必须用Value(${jwt.secret})从application.yml读取严禁硬编码jwtService.getAuthentication()内部需校验exp时间戳避免过期 Token 继续生效。4.2 避坑Vue 上传图片到 Spring Boot后端接收为空现象前端用el-upload上传图片on-success触发但后端RequestParam MultipartFile file始终为 nullrequest.getParameterMap()也为空。原因Vue 的el-upload默认发送FormData但 Spring Boot 3.x 的ServletWebServerFactory默认maxFileSize为 1MB且未配置CommonsMultipartResolver已被废弃需改用StandardServletMultipartResolver。解决在application.yml中增加 multipart 配置spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB后端 Controller 方法签名必须为PostMapping(/upload) public ResultString uploadFile(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.fail(文件不能为空); } // 保存文件逻辑... return Result.success(上传成功); }前端el-upload必须设置action为/api/upload且headers中不能手动加Content-Type浏览器会自动设为multipart/form-data; boundaryxxxel-upload action/api/upload :show-file-listfalse :on-successhandleUploadSuccess :on-errorhandleUploadError el-button typeprimary点击上传/el-button /el-upload注意RequestParam(file)的file必须与前端FormData.append(file, file)的 key 名一致大小写敏感。4.3 避坑租期计算跨年出错end_date比start_date少一天现象用户选择 2024-01-01 至 2025-01-01后端保存后end_date变成 2024-12-31。原因前端el-date-picker的value-formatYYYY-MM-DD返回字符串但后端RequestBody接收为LocalDate时Jackson 默认反序列化会将字符串按ISO_LOCAL_DATE解析若前端传2025-01-01后端收到LocalDate.of(2025,1,1)正确但若传2025/01/01某些浏览器 locale 下则解析失败Jackson 回退为nullMyBatis-Plus 插入NULLMySQL 默认填0000-00-00最终显示为1970-01-01。解决前后端约定日期格式为yyyy-MM-dd字符串并在后端增加JsonFormat强制解析public class ContractDTO { JsonFormat(pattern yyyy-MM-dd) private LocalDate startDate; JsonFormat(pattern yyyy-MM-dd) private LocalDate endDate; // getter/setter... }同时在application.yml中全局配置 Jacksonspring: jackson: date-format: yyyy-MM-dd time-zone: GMT8 serialization: write-dates-as-timestamps: false血泪经验永远不要信任前端传来的日期字符串格式。JsonFormat是最后一道防线必须加write-dates-as-timestamps: false关闭时间戳序列化避免前端new Date()传毫秒值导致时区混乱。5. 生产就绪Nginx 反向代理、跨域配置、日志审计与一键部署脚本一个能放进简历的项目必须能真机跑、能被甲方验收、能交接给同事维护。这章不讲 Docker 编排太重只用最轻量、最通用的方式Nginx Shell 脚本 Logback 日志归档覆盖从开发到上线的全链路。5.1 Nginx 配置单机部署前后端分离HTTPS 可选在服务器安装 NginxUbuntusudo apt update sudo apt install nginx -y sudo systemctl enable nginx编辑/etc/nginx/sites-available/rentalupstream backend { server 127.0.0.1:8080; # Spring Boot 默认端口 } server { listen 80; server_name rental.example.com; # 替换为你的域名或 IP # 前端静态资源 location / { root /var/www/rental-frontend/dist; try_files $uri $uri/ /index.html; } # API 接口代理 location /api/ { proxy_pass http://backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; } # 静态文件如上传的图片 location /uploads/ { alias /var/www/rental-uploads/; expires 7d; } }启用站点sudo ln -sf /etc/nginx/sites-available/rental /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx逻辑说明location /api/末尾的/是关键——它会把/api/houses代理为http://backend/houses而非http://backend/api/houses与后端RequestMapping(/houses)完全匹配proxy_set_header系列确保后端request.getRemoteAddr()能拿到真实 IP用于日志审计。5.2 日志审计Logback 配置按天归档、保留30天、敏感字段脱敏在src/main/resources/logback-spring.xml中配置?xml version1.0 encodingUTF-8? configuration property nameLOG_PATH value/var/log/rental / property nameLOG_FILE value${LOG_PATH}/app.log / !-- 控制台输出 -- appender nameCONSOLE classch.qos.logback.core.ConsoleAppender encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender !-- 文件输出按天滚动保留30天 -- appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender file${LOG_FILE}/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePattern${LOG_PATH}/app.%d{yyyy-MM-dd}.%i.log/fileNamePattern timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize100MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy maxHistory30/maxHistory /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender !-- 审计日志专用 appender -- appender nameAUDIT classch.qos.logback.core.rolling.RollingFileAppender file${LOG_PATH}/audit.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePattern${LOG_PATH}/audit.%d{yyyy-MM-dd}.%i.log/fileNamePattern maxHistory90/maxHistory /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %msg%n/pattern /encoder /appender !-- 脱敏处理器隐藏手机号、身份证号 -- appender nameDESENSITIZE classch.qos.logback.core.rolling.RollingFileAppender file${LOG_PATH}/desensitize.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePattern${LOG_PATH}/desensitize.%d{yyyy-MM-dd}.%i.log/fileNamePattern maxHistory30/maxHistory /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %replace(%msg){1[3-9]\d{9}, *** **** ****}%n/pattern /encoder /appender root levelINFO appender-ref refCONSOLE/ appender-ref refFILE/ /root !-- 审计日志单独输出 -- logger nameAUDIT_LOGGER levelINFO additivityfalse appender-ref refAUDIT/ /logger !-- 敏感日志脱敏输出 -- logger nameDESENSITIZE_LOGGER levelINFO additivityfalse appender-ref refDESENSITIZE/ /logger /configuration在业务代码中记录审计日志Slf4j Service public class ContractService { private static final Logger auditLogger LoggerFactory.getLogger(AUDIT_LOGGER); public void signContract(Contract contract) { // ... 业务逻辑 auditLogger.info(合同签署: userId{}, houseId{}, tenantId{}, amount{}, SecurityUtils.getUserId(), contract.getHouseId(), contract.getTenantId(), contract.getDeposit()); } }参数说明maxHistory30表示只保留最近30天的日志文件避免磁盘爆满%replace(...)是 Logback 内置的正则脱敏1[3-9]\d{9}匹配中国大陆手机号替换成*** **** ****审计日志AUDIT_LOGGER独立文件方便运维团队提取操作流水。5.3 一键部署脚本build.sh 自动打包、上传、重启在项目根目录创建build.sh#!/bin/bash # 房屋出租系统一键部署脚本 # 用法./build.sh frontend|backend|all set -e # 任何命令失败立即退出 FRONTEND_DIR./rental-frontend BACKEND_DIR./rental-backend SERVER_IP192.168.1.100 # 替换为你的服务器IP SERVER_USERdeploy if [ $1 frontend ] || [ $1 all ]; then echo 构建前端 cd $FRONTEND_DIR npm install npm run build echo 前端构建完成dist 目录已生成 echo 上传前端到服务器 rsync -avz --delete dist/ $SERVER_USER$SERVER_IP:/var/www/rental-frontend/dist/ echo 前端上传完成 fi if [ $1 backend ] || [ $1 all ]; then echo 构建后端 cd $BACKEND_DIR ./mvnw clean package -DskipTests echo 后端构建完成target/rental-backend-0.0.1-SNAPSHOT.jar 已生成 echo 上传后端到服务器 scp target/rental-backend-0.0.1-SNAPSHOT.jar $SERVER_USER$SERVER_IP:/opt/rental/ echo 重启后端服务 ssh $SERVER_USER$SERVER_IP sudo systemctl stop rental-backend sudo cp /opt/rental/rental-backend-0.0.1-SNAPSHOT.jar /opt/rental/rental.jar sudo systemctl start rental-backend sudo systemctl status rental-backend --no-pager | head -10 echo 后端重启完成 fi echo ✅ 部署完成访问 http://$SERVER_IP 查看效果赋予执行权限并运行chmod x build.sh ./build.sh all技巧set -e是防错底线任何一步失败脚本立即终止避免部分更新导致系统不一致rsync --本文还有配套的精品资源点击获取
返回列表