☰
SpringBoot+Vue酒店客房管理系统实战:从房间状态机到前后端分离
2026/10/20 10:50:59 网站建设 项目流程

简介:酒店客房管理系统源码是一套完整的前后端分离项目,后端采用Spring Boot框架,前端使用Vue框架,数据库使用MySQL存储。系统分为管理后台与用户网页端,支持管理员、会员两类角色,功能包括会员管理、清洁工管理、客房管理、用户预约、入住办理和系统基础模块,适合Java与Web方向的学习者参考,也可用于课程设计或毕业设计。

资源包共包含一千七百八十八个文件,压缩后约三十九点三六兆大小。文件中以Java后端源码、Vue前端组件、JavaScript脚本、HTML与CSS页面为主,同时含有SQL数据库脚本、XML配置、图片素材及部分备份文件,方便从后端到前端进行整体阅读和二次开发。

目前已有九十二人学习下载。依靠这套源码可以理解前后端交互方式、角色权限控制和预约入住流程,备份文件也有助于对比调试和排错,无论是学习技术栈整合还是参考业务模块划分都很有帮助,是一份实用完整的项目参考资料。

1. 酒店客房管理系统能解决什么:前台三班倒的历史遗留难题

我拆这个项目时脑子里一直有个画面:某酒店前台,白班同事刚把手写台账合上,晚班接班就发现三间房的入住状态没同步,客人拿着预订记录来办入住,前台翻了两分钟 Excel 才确认房间还在。这种场景在中小型酒店太常见了——客房信息散落在纸质单、Excel 和口头交代里,订单和房间状态永远对不上。这套基于 Java + SpringBoot + Vue.js + MySQL 的酒店客房管理系统就是冲着这个痛点来的:把房间状态、预订订单、入住退房、客户信息全部收进一个前后端分离的 Web 系统里,前台在浏览器里点几下就能完成全部操作。

如果你是刚接触 SpringBoot + Vue 全栈开发的学生,或者想把内部管理工具从 Excel 升级成 Web 系统的开发者,这个项目的代码量、分层方式和接口设计都适合先跑通一遍,再改造成自己的版本。下面按我拆项目的顺序来讲:先看选型和结构,再拆核心链路,然后跑通,最后说改造。

2. 技术选型与目录结构:SpringBoot + Vue.js 前后端分离的一次完整落地

2.1 技术选型:为什么是这套组合

酒店客房管理系统的使用场景很明确:一个酒店前台三五台电脑,偶尔经理在办公室查房态,没有高并发,但业务状态多、操作频次高。这个场景下,SpringBoot + Vue.js + MySQL 几乎是最省事的选择。

后端用 SpringBoot(Java)的理由很实际:生态成熟、上手快、部署简单。SpringBoot 自带内嵌 Tomcat,mvn package打出一个 jar 包丢服务器上java -jar就能跑,不依赖单独的 Web 容器。对这种 CRUD 占比极高的管理系统,SpringBoot 的 Starter 机制能省掉大把配置——spring-boot-starter-web给 Web 能力,mybatis-spring-boot-starter接数据库访问,写业务代码的时间占比远高于配环境的时间。

前端选 Vue.js,核心原因是组件化。酒店管理界面看起来不多,但房间看板、订单列表、入住表单这些页面有大量重复渲染和状态联动。Vue 的双向绑定让表单处理非常顺手,组件复用又能把房间卡片、状态标签这类高频 UI 收敛成局部组件,改一处样式全局生效。数据库选 MySQL 是这类中小型系统的默认答案,InnoDB 的事务和行锁处理订单扣减、房间状态切换这种并发更新足够可靠。

ORM 层我建议保留项目里的 MyBatis,而不是换成 JPA。原因是酒店管理的查询条件变化太频繁了——"今天到店但明天离店的订单""三层所有可售大床房",这类动态 SQL 用 MyBatis 的 XML 写直观得多,出问题还能直接把 SQL 捞出来在客户端执行验证,不用猜框架生成的是什么。

2.2 后端分包与前端页面:按图索骥找到要改的文件

拿到源码先别急着跑,把目录结构过一遍,后面改什么都好定位。这个项目后端是经典的 controller / service / mapper 三层,前端按页面维度组织,两边通过/api前缀的 REST 接口对接。

hotel-admin/ ├── backend/ │ ├── pom.xml │ └── src/main/java/com/hotel/admin/ │ ├── AdminApplication.java # SpringBoot 启动类 │ ├── controller/ # 接口层,只收参数、返回结果 │ │ ├── RoomController.java │ │ ├── OrderController.java │ │ └── CheckInController.java │ ├── service/ # 业务层,事务边界和状态校验都在这里 │ │ ├── RoomService.java │ │ ├── OrderService.java │ │ └── CheckInService.java │ ├── mapper/ # MyBatis 数据访问接口 │ │ ├── RoomMapper.java │ │ ├── OrderMapper.java │ │ └── StayRecordMapper.java │ ├── entity/ # 与表结构对应的实体 │ │ ├── Room.java │ │ ├── Order.java │ │ └── Customer.java │ ├── common/ # 统一返回结构、全局异常处理 │ │ ├── Result.java │ │ └── GlobalExceptionHandler.java │ └── resources/ │ ├── application.yml # 数据源、端口、JSON 序列化配置 │ └── mapper/ # MyBatis XML 文件 │ ├── RoomMapper.xml │ └── OrderMapper.xml ├── frontend/ │ ├── package.json │ ├── vue.config.js # 端口和开发代理配置 │ └── src/ │ ├── main.js # Vue 入口,注册路由和 UI 组件库 │ ├── router/index.js # 页面路由 │ ├── api/ # axios 请求统一封装 │ │ ├── room.js │ │ └── order.js │ └── views/ │ ├── RoomBoard.vue # 房间状态看板,前台主界面 │ ├── OrderList.vue # 订单列表 │ └── CheckIn.vue # 入住登记页 └── sql/ └── init.sql # 建库建表 + 测试数据

这个结构最值得注意的一点是前后端映射关系:后端controller和前端views一一对应。改房间看板,前端动RoomBoard.vue,后端对应RoomController.java和RoomMapper.xml,顺着接口路径就能摸完整条链路,不需要全局搜索。

common/Result.java是所有接口的统一返回壳,格式固定为{ code, message, data },前端 axios 拦截器里判断code !== 200就直接弹错误提示,每个页面不用重复写错误处理逻辑。这个习惯建议你保留,后续加接口时能少写很多样板代码。

2.3 关键配置文件:pom.xml 与 application.yml 里的门道

pom.xml里的依赖不多,但有几个值得留意:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>

mysql-connector-j的 scope 是runtime,意思是编译时不需要,运行时才加载驱动,这是数据库驱动依赖的常规写法。MyBatis 的 Starter 版本和 SpringBoot 父版本有兼容矩阵,如果启动报NoSuchMethodError之类的错,先查这两个版本的搭配,再查代码。

application.yml是后端启动前必须改的文件,我在第 4 章会详细说每个参数的作用。这里先记住一个原则:凡是跟环境相关的配置——数据库账号密码、端口、时区——都收敛在这个文件里,代码里不要写死任何环境信息,否则换台机器部署就是一场灾难。

3. 核心模块拆解:从客房预订到入住登记的数据流链路

3.1 房间状态机:四个状态撑起整个业务流

酒店管理系统的核心不是订单表,而是房间状态。预订、入住、退房、维修这些业务动作,最终都反映为房间状态的变化。这个项目里房间状态是字符串常量,一共四个值:

AVAILABLE 可售 BOOKED 已预订 CHECKED_IN 已入住 REPAIR 维修中

流转规则很直接:可售房间被预订后变BOOKED,客人到店办了入住变CHECKED_IN,退房后回到AVAILABLE;REPAIR状态的房间不可预订不可入住。这个状态约束是业务正确性的最后一道防线,必须由后端 Service 层强校验,不能只靠前端按钮控制。

对应的实体类长这样:

public class Room { private Long id; private String roomNo; // 房号,如 301 private String type; // 房型:大床房/双床房/套房 private BigDecimal price; // 门市价 private String status; // AVAILABLE / BOOKED / CHECKED_IN / REPAIR private String floor; // 楼层 private String remark; // 备注 // getter / setter 省略 }

字段说明:status是全表最关键的字段,Service 层反复读取和校验它;price必须用BigDecimal而不是double,这是金额字段的底线——二进制浮点数在比较和累加时有精度误差,房价涉及结算和报表统计,用BigDecimal能避免"显示 99.99 实际是 99.99000001"这种玄学差异。type和floor是看板分组和筛选的条件,预留了按楼层看房态的扩展空间。

3.2 入住登记接口:从 Controller 到 Mapper 的完整调用链

入住登记是整个系统最典型的业务操作,一次请求要同时影响订单、房间、入住记录三张表。完整拆一遍,这个项目的分层协作方式就清楚了。

先看 Controller,它只负责接收参数、调用服务、返回结果:

@RestController @RequestMapping("/api/checkin") public class CheckInController { @Autowired private CheckInService checkInService; @PostMapping public Result checkIn(@RequestBody CheckInRequest request) { checkInService.checkIn(request.getOrderId()); return Result.success(); } }

CheckInRequest是前端传入的 JSON 请求体,这里只取了orderId——入住一定对应一条已预订的订单,由订单找到房间,而不是直接传房间号。这样设计是为了防止出现"订单 A 的房间被订单 B 办了入住"的数据错乱。

业务逻辑全部在 Service 层,加上@Transactional保证多表更新的原子性:

@Service public class CheckInService { @Autowired private RoomMapper roomMapper; @Autowired private OrderMapper orderMapper; @Autowired private StayRecordMapper stayRecordMapper; @Transactional(rollbackFor = Exception.class) public void checkIn(Long orderId) { // 1. 查订单,状态必须是已预订 Order order = orderMapper.selectById(orderId); if (order == null) { throw new BusinessException("订单不存在"); } if (!"BOOKED".equals(order.getStatus())) { throw new BusinessException("订单状态不是已预订,无法办理入住"); } // 2. 查房间,状态必须是已预订 Room room = roomMapper.selectById(order.getRoomId()); if (!"BOOKED".equals(room.getStatus())) { throw new BusinessException("房间当前不可入住"); } // 3. 更新订单状态和房间状态 orderMapper.updateStatus(orderId, "CHECKED_IN"); roomMapper.updateStatus(room.getId(), "CHECKED_IN"); // 4. 写入入住记录,留审计痕迹 StayRecord record = new StayRecord(); record.setOrderId(orderId); record.setRoomId(room.getId()); record.setCheckInTime(new Date()); stayRecordMapper.insert(record); } }

四个步骤对应四层逻辑:第一步和第二步是"先查后改",订单和房间两个状态同时满足才继续,防止脏数据被写进去。第三步连着两个updateStatus,必须同时成功,任一步失败就整体回滚。第四步写入入住记录,后续退房、换房、开发票都基于这条记录,相当于给每次入住留了账簿。

@Transactional(rollbackFor = Exception.class)这里有个细节:rollbackFor必须显式指定为Exception.class,否则 Spring 默认只对RuntimeException回滚。如果自定义的BusinessException继承的是Exception,不加这个参数事务就不会回滚,我在第 5 章专门讲这个坑。

Mapper 层是接口定义,注意多参数方法必须加@Param注解:

@Mapper public interface RoomMapper { Room selectById(Long id); int updateStatus(@Param("id") Long id, @Param("status") String status); }

对应的 XML 里,updateStatus用动态 SQL 只更新传入的字段:

<update id="updateStatus"> UPDATE room <set> <if test="status != null">status = #{status}</if> </set> WHERE id = #{id} </update>

#{}是预编译占位符,MyBatis 会转成?走 PreparedStatement,既能防 SQL 注入,又能在status为 null 时不覆盖原值。这里绝对不能写成${},那是字符串拼接,SQL 注入风险极大,这是写 MyBatis 的底线。

3.3 前端房间看板:Vue 组件如何刷新状态

前端最核心的页面是房间看板RoomBoard.vue,整层楼的房间铺成格子,每个格子显示房号、房型和状态标签。前台一天里 90% 的时间盯的就是这个页面。

<template> <div class="room-board"> <el-row :gutter="10"> <el-col v-for="room in roomList" :key="room.id" :span="4"> <el-card :class="'room-card room-' + room.status" @click.native="handleRoomClick(room)"> <div class="room-no">{{ room.roomNo }}</div> <div class="room-type">{{ room.type }}</div> <el-tag :type="statusTagType(room.status)"> {{ statusText(room.status) }} </el-tag> </el-card> </el-col> </el-row> </div> </template> <script> import { listRooms } from '@/api/room' export default { data() { return { roomList: [], timer: null } }, created() { this.fetchRooms() // 30 秒轮询房态,保证多台前台电脑看到的是一致的 this.timer = setInterval(this.fetchRooms, 30000) }, methods: { async fetchRooms() { const res = await listRooms({ status: '' }) if (res.code === 200) { this.roomList = res.data } }, statusText(status) { const map = { AVAILABLE: '可售', BOOKED: '已预订', CHECKED_IN: '已入住', REPAIR: '维修中' } return map[status] || status }, handleRoomClick(room) { // 按状态跳转到对应操作:可售去下单,已预订去入住 if (room.status === 'AVAILABLE') { this.$router.push({ path: '/order/create', query: { roomId: room.id } }) } else if (room.status === 'BOOKED') { this.$router.push({ path: '/checkin', query: { orderId: room.currentOrderId } }) } } }, beforeDestroy() { clearInterval(this.timer) } } </script>

几个关键点:roomList来自listRooms接口,实际请求是GET /api/room/list,后端按状态过滤返回 JSON 数组。看板用 30 秒轮询而不是 WebSocket 推送,这是复杂度取舍——酒店前台操作频率低,30 秒刷一次房态足够,没必要为"实时"引入 WebSocket 的心跳和重连逻辑。想做秒级刷新,改setInterval的时间就行,后端接口不用动。

statusTagType把状态映射成 UI 标签颜色:可售绿、已预订橙、已入住蓝、维修中灰,前台扫一眼颜色就知道哪些房能卖。点击格子按状态路由到不同操作页,符合前台的肌肉记忆——看到绿格子点进去就是下单,不用去订单列表翻。

注意timer在beforeDestroy里必须清除,否则组件销毁后定时器还在跑,接口一直请求,这是 Vue 组件开发最常见的内存泄漏点,页面切走再切回来就多一个定时器。

4. 本地跑通全流程:建库、启后端、起前端的三步联调

4.1 初始化数据库:五张表的关系与建库脚本

源码里自带sql/init.sql,这是整个项目的地基。执行前先建一个专用库,字符集必须用 utf8mb4:

mysql -uroot -p # 密码验证通过后执行: CREATE DATABASE hotel_admin DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hotel_admin; SOURCE /path/to/sql/init.sql;

utf8mb4不要图省事换成utf8,MySQL 的utf8最多存 3 字节,遇到生僻字或 Emoji 会直接报错或落库成问号。酒店客户姓名里有生僻字的概率不低,这一步省不得。

init.sql 建了五张表,关系如下:

表名作用关键字段关联关系
room房间信息id, room_no, type, price, status订单表通过 room_id 关联
orders预订订单id, room_id, customer_name, check_in_date, check_out_date, statusroom_id 指向 room.id
customer客户信息id, name, phone, id_cardorders 冗余了姓名电话,减少联表
stay_record入住记录id, order_id, room_id, check_in_time, check_out_timeorder_id 指向 orders.id
sys_user系统登录用户id, username, password, role独立,不参与业务流转

orders表冗余存了customer_name和customer_phone而不是 joincustomer表,这是刻意的取舍——订单列表是查询频率最高的页面,不 join 能少一次表关联,客户名和电话变更频率极低,冗余存储的代价可以忽略。

init.sql 里还埋了测试数据:三层楼共 21 间房,每层 7 间,四个状态都有覆盖,保证你第一次启动就能看到完整的房态看板效果,不用自己再补数据。

4.2 启动 SpringBoot 后端:配置数据源与自检接口

后端是标准 Maven 工程,启动前先改application.yml的数据源指向本地 MySQL:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hotel_admin?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.hotel.admin.entity

参数逐个说:serverTimezone=Asia/Shanghai必须加,MySQL 8 以上的驱动默认按 UTC 处理时间,不加的话查出来的日期比北京时间早 8 小时。useUnicode=true&characterEncoding=utf8保证中文参数和返回值不乱码。mapper-locations指定 XML 文件位置,type-aliases-package让 XML 里写resultType="Room"时能自动找到实体类,省去写全限定类名。

然后启动:

cd backend mvn spring-boot:run

日志出现Tomcat started on port(s): 8080就说明起来了。上线部署时改成mvn clean package然后java -jar target/hotel-admin-0.0.1-SNAPSHOT.jar一样的效果。启动后用 curl 自检接口:

curl http://localhost:8080/api/room/list?status=AVAILABLE

返回 JSON 里code是 200 且data里有房间数组,说明数据库连接和 MyBatis 映射都正常。这一步是联调前必须做的自检,能把大量"前端白屏其实是后端没起来"的排查时间省掉。

4.3 启动 Vue 前端:代理配置与最小验证闭环

前端是 Vue CLI 工程,先装依赖再起开发服务器:

cd frontend npm install npm run serve

默认端口是 8080,和后端冲突,Vue CLI 会提示换端口,输入y自动改到 8081。浏览器访问http://localhost:8081打开登录页。

这里有个关键配置:前端开发服务器必须把/api请求代理到后端 8080,否则浏览器直接请求跨域端口会被同源策略拦下。改vue.config.js:

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

changeOrigin: true表示代理时改写请求头里的 Host,让后端认为请求来自本机。配置之后,前端代码里axios.get('/api/room/list')实际被转发到http://localhost:8080/api/room/list,前后端联调就通了。注意代理配置改了要重启npm run serve,它不走热更新,这是最容易踩的空跑。

跑通之后做最小验证闭环:打开房间看板,确认 21 个房间的格子颜色和状态文字正确;点一个"可售"房间下单,回看板确认格子变成"已预订"橙色;再点这个房间办理入住,刷新后确认变"已入住"蓝色。这三步全过,说明前端到后端再到数据库的链路、状态机流转、页面刷新逻辑都没问题。

提示:开发阶段前后端分两个端口跑靠代理转发,生产部署时用 Nginx 把/api反向代理到后端 SpringBoot 服务,前端打包后的静态文件由 Nginx 直接托管,同一端口对外,省掉跨域问题。

5. 避坑指南:联调期最容易翻车的五个场景

5.1 跨域报错:接口明明能通,页面却一片空白

现象:后端接口用 curl 测完全正常,前端页面请求时浏览器控制台报No 'Access-Control-Allow-Origin' header is present,页面数据加载不出来。

原因:前端在 8081,后端在 8080,浏览器同源策略拦截了跨端口请求。多数情况是vue.config.js里的代理路径前缀和 axios 实际请求的路径不一致,或者代理文件改了没重启。

解决:先确认前端请求路径以/api开头,再确认代理配置的 key 也是/api,两者完全一致才命中。改完配置必须重启npm run serve。生产环境用 Nginx 反向代理统一端口,比在后端放开 CORS 更安全——放开跨域等于允许任意网站调你的接口。

5.2 日期序列化:时间变成一串数字

现象:前端拿到checkInTime显示成1720000000000这种毫秒时间戳,或者格式是2024-07-01T10:00:00.000+00:00,跟设计稿完全不一样。

原因:后端返回的java.util.Date被 Jackson 默认序列化成时间戳;以及时区没对齐,序列化出来是 UTC 时间。

解决:在application.yml统一配置格式,实体字段上用@JsonFormat兜底:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private Date checkInDate;

注意date-format只对java.util.Date生效,对 Java 8 的LocalDateTime/LocalDate不生效,需要额外注册 JavaTimeModule。这个项目用的是Date,按上面配就行。如果你二次开发用了LocalDateTime,记得单独处理。

5.3 事务不生效:订单和房间各改各的

现象:接口内部抛了异常,日志里有报错,但数据库里订单状态变了、房间状态没变,或者反过来,数据成了半成品。

原因:@Transactional只对 Spring 管理的 Bean 外部调用生效。两个最经典的翻车点:一是同一个类里 A 方法直接调 B 方法,B 上的@Transactional完全失效,因为走的是this调用而不是代理对象;二是rollbackFor没写,自定义异常继承自Exception而不是RuntimeException,默认不回滚。

解决:事务方法入口必须从外部调用,代码里不要同类自调用,需要自调用就拆成两个 Bean 或注入自己。rollbackFor = Exception.class写死,不留默认值。验证方法很暴力但有效:在事务方法里故意抛一个自定义异常,跑完看数据有没有变化,没变说明事务生效。

5.4 状态枚举不一致:前端显示与数据库存储脱节

现象:房间看板某个格子状态显示空白或乱码,点操作按钮后提示"非法状态"。

原因:前后端对状态值的叫法不统一。前端某处用了中文当 value,后端存的是英文枚举;或者后端两个地方分别写了"CHECKED_IN"和"CHECK_IN"拼写不一致,查出来的值前端不认识。

解决:数据库永远存英文字符串,前端只做显示层映射。把状态常量收敛到一个类里统一管理:

public class RoomStatus { public static final String AVAILABLE = "AVAILABLE"; public static final String BOOKED = "BOOKED"; public static final String CHECKED_IN = "CHECKED_IN"; public static final String REPAIR = "REPAIR"; }

前端同样建一个状态映射常量文件,两边各守一份,但以数据库存储的值为唯一真相。加新状态时先改数据库可接受的取值范围,再改后端常量,最后动前端映射,顺序不能反。

5.5 MySQL 时区偏移:入住时间差 8 小时

现象:前端显示入住时间是2024-06-30 16:00:00,实际北京时间应该是2024-07-01 00:00:00,所有时间记录都偏了 8 小时。

原因:JDBC 连接串里的serverTimezone没配,或配成了UTC。MySQL 服务端时区和驱动时区不一致,驱动在读写DATETIME时做了额外偏移。

解决:连接串固定写serverTimezone=Asia/Shanghai,同时检查 MySQL 服务端时区:

SELECT @@global.time_zone;

如果返回SYSTEM,建议在 MySQL 配置文件里显式设置default-time-zone = '+08:00'并重启服务。连接串和服务端两处一致,时间就不会再漂。这个坑最隐蔽的地方在于:不查具体时间记录根本发现不了,因为所有日期看起来都"差不多对"。

6. 二次开发思路:给系统加一个会员积分模块

跑通之后最有价值的练习是加一个真实需求。我推荐加"会员积分":它不改变现有订单和房间的核心流程,但要动表结构、接口、前端页面,能完整走一遍开发闭环。

先设计数据模型。积分挂在客户名下,每次完成入住获得积分,下次预订可抵扣。建一张新表:

CREATE TABLE member_point ( id BIGINT AUTO_INCREMENT PRIMARY KEY, customer_id BIGINT NOT NULL, total_point INT DEFAULT 0, used_point INT DEFAULT 0, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );

然后改造退房接口。在退房事务里追加积分累计逻辑,按订单金额 1 元积 1 分:

@Transactional(rollbackFor = Exception.class) public void checkout(Long stayRecordId) { // 原有退房逻辑:房间回可售、订单置完成、写入退房时间 StayRecord record = stayRecordMapper.selectById(stayRecordId); roomMapper.updateStatus(record.getRoomId(), RoomStatus.AVAILABLE); orderMapper.updateStatus(record.getOrderId(), "FINISHED"); stayRecordMapper.updateCheckOutTime(stayRecordId, new Date()); // 新增:累计积分 Order order = orderMapper.selectById(record.getOrderId()); int points = order.getAmount().intValue(); memberPointMapper.addPoint(order.getCustomerId(), points); }

积分抵扣放在下单接口:前端下单页加"使用积分抵扣"选项,后端收到usePoint参数后校验积分余额、计算抵扣金额、扣减积分,再生成订单。这样一套改造下来,你实际动了五层——SQL 表、Mapper、Service、Controller、Vue 页面,覆盖了这个项目的全部套路。

验证也分两步走:用测试客户开房、入住、退房,查member_point表确认积分增加;同一客户再下新订单传usePoint=100,确认订单金额减了、积分余额也扣了。两个链路跑通,你对这个项目的掌控力就从"能跑"变成了"能改"。

说句实在话,我每次拿到别人写的管理类系统,第一件事不是通读代码,而是先给自己加一个小需求——加的过程中,框架的约束、数据模型的问题、接口设计的别扭之处全部现形。从那以后我每次接手新项目都强制先走一遍这个流程:跑通、加需求、再回头读代码。希望帮到你。

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

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

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

立即咨询