跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

一个 SpringBoot + Vue 酒店客房管理系统的实现记录

用 SpringBoot + Vue 实现的一个酒店客房管理系统,包含客房、订单、用户的数据表设计和后端 CRUD 代码示例。技术选型上后端用 SpringBoot 加 MyBatis Plus 简化开发,前端用 Vue 做 SPA。数据库设计较为直接,代码展示了使用 EntityWrapper 进行条件查询和分页,整体结构适合快速搭建中小型酒店管理应用。

dehua dong发布于 2026/6/30更新于 2026/8/1719 浏览
一个 SpringBoot + Vue 酒店客房管理系统的实现记录

酒店行业的管理需求越来越复杂,手工方式确实跟不上节奏。这个项目用 SpringBoot 和 Vue 搭了一套前后端分离的客房管理系统,覆盖预订、入住、退房这些核心流程,顺便把数据库设计和部分后端代码也整理出来。

数据库设计

数据库是跑在 MySQL 上的,表结构不算复杂,但足够支撑基本业务。

客房信息表

客房表以 room_id 做主键,记录类型、价格、状态这些字段。状态用简单的'空闲/占用'区分,实际生产环境可能会更细(比如维护中),这里先这样。create_time 由系统自动生成,不用管。

字段名数据类型是否为空描述
room_idINT否客房唯一编号(主键)
room_typeVARCHAR(20)否客房类型(如标准间)
room_priceDECIMAL(10,2)否客房每日价格
room_statusVARCHAR(10)否客房状态(空闲/占用)
room_descTEXT是客房描述信息
create_timeDATETIME否记录创建时间

订单信息表

订单表通过 user_id 和 room_id 关联用户与客房,记录入住退房时间、金额和状态。状态目前只区分了已支付/未支付,取消订单的逻辑可以后续再补。

字段名数据类型是否为空描述
order_idINT否订单唯一编号(主键)
user_idINT否客户关联编号
room_idINT否客房关联编号
check_in_timeDATETIME否入住时间
check_out_timeDATETIME否退房时间
order_statusVARCHAR(10)否订单状态(已支付/未支付)
total_amountDECIMAL(10,2)否订单总金额
create_timeDATETIME否订单创建时间

用户信息表

用户表存储账号、密码(加密)和角色。角色分管理员、前台、顾客三种,权限控制靠代码里区分,用了简单的 VARCHAR 字段。以后如果权限复杂了,可能得拆成 RBAC,不过目前够用。

字段名数据类型是否为空描述
user_idINT否用户唯一编号(主键)
usernameVARCHAR(50)否用户登录账号
passwordVARCHAR(100)否用户登录密码(加密)
user_roleVARCHAR(20)否用户角色(管理员/前台/顾客)
phoneVARCHAR(20)是用户联系电话
emailVARCHAR(50)是用户电子邮箱
create_timeDATETIME否用户注册时间

技术选型

后端选 SpringBoot 实在没什么好犹豫的,约定优于配置,少写一堆 XML,嵌个 Tomcat 直接打 jar 包部署,加上 MyBatis Plus 做 CRUD 也很顺手。前端用 Vue 是因为渐进式框架的灵活性,团队里不少人熟悉,组件化开发让交互改起来快一些。虽然没有用 Nuxt,但普通 SPA 对这个体量的系统足够了。

后端代码示例

下面这段是商品信息(对应某个实体)的控制层代码,展示了通用 CRUD 的写法。虽然项目是酒店客房,但结构上是类似的,可以用来参考分页、条件查询、排序这些常见操作。

package com.controller;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.utils.ValidatorUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.annotation.IgnoreAuth;
import com.entity.ShangpinxinxiEntity;
import com.entity.view.ShangpinxinxiView;
import com.service.ShangpinxinxiService;
import com.service.TokenService;
import com.utils.PageUtils;
import com.utils.R;
import com.utils.MD5Util;
import com.utils.MPUtil;
import com.utils.CommonUtil;
import java.io.IOException;
import com.service.StoreupService;
import com.entity.StoreupEntity;

/**
 * 商品信息
 * 后端接口
 */
@RestController
@RequestMapping("/shangpinxinxi")
public class ShangpinxinxiController {
    @Autowired
    private ShangpinxinxiService shangpinxinxiService;
    @Autowired
    private StoreupService storeupService;

    /**
     * 后端列表
     */
    @RequestMapping("/page")
    public R page(@RequestParam Map<String, Object> params, ShangpinxinxiEntity shangpinxinxi,
                  @RequestParam(required = false) Double pricestart,
                  @RequestParam(required = false) Double priceend, HttpServletRequest request) {
        String tableName = request.getSession().getAttribute("tableName").toString();
        if (tableName.equals("shangjia")) {
            shangpinxinxi.setShangpubianhao((String) request.getSession().getAttribute("username"));
        }
        EntityWrapper<ShangpinxinxiEntity> ew = new EntityWrapper<ShangpinxinxiEntity>();
        if (pricestart != null) ew.ge("price", pricestart);
        if (priceend != null) ew.le("price", priceend);
        PageUtils page = shangpinxinxiService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, shangpinxinxi), params), params));
        return R.ok().put("data", page);
    }

    /**
     * 前端列表
     */
    @IgnoreAuth
    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params, ShangpinxinxiEntity shangpinxinxi,
                  @RequestParam(required = false) Double pricestart,
                  @RequestParam(required = false) Double priceend, HttpServletRequest request) {
        EntityWrapper<ShangpinxinxiEntity> ew = new EntityWrapper<ShangpinxinxiEntity>();
        if (pricestart != null) ew.ge("price", pricestart);
        if (priceend != null) ew.le("price", priceend);
        PageUtils page = shangpinxinxiService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, shangpinxinxi), params), params));
        return R.ok().put("data", page);
    }

    /**
     * 列表
     */
    @RequestMapping("/lists")
    public R list(ShangpinxinxiEntity shangpinxinxi) {
        EntityWrapper<ShangpinxinxiEntity> ew = new EntityWrapper<ShangpinxinxiEntity>();
        ew.allEq(MPUtil.allEQMapPre(shangpinxinxi, "shangpinxinxi"));
        return R.ok().put("data", shangpinxinxiService.selectListView(ew));
    }

    /**
     * 查询
     */
    @RequestMapping("/query")
    public R query(ShangpinxinxiEntity shangpinxinxi) {
        EntityWrapper<ShangpinxinxiEntity> ew = new EntityWrapper<ShangpinxinxiEntity>();
        ew.allEq(MPUtil.allEQMapPre(shangpinxinxi, "shangpinxinxi"));
        ShangpinxinxiView shangpinxinxiView = shangpinxinxiService.selectView(ew);
        return R.ok("查询商品信息成功").put("data", shangpinxinxiView);
    }

    /**
     * 后端详情
     */
    @RequestMapping("/info/{id}")
    public R info(@PathVariable("id") Long id) {
        ShangpinxinxiEntity shangpinxinxi = shangpinxinxiService.selectById(id);
        shangpinxinxi.setClicknum(shangpinxinxi.getClicknum() + 1);
        shangpinxinxi.setClicktime(new Date());
        shangpinxinxiService.updateById(shangpinxinxi);
        return R.ok().put("data", shangpinxinxi);
    }

    /**
     * 前端详情
     */
    @IgnoreAuth
    @RequestMapping("/detail/{id}")
    public R detail(@PathVariable("id") Long id) {
        ShangpinxinxiEntity shangpinxinxi = shangpinxinxiService.selectById(id);
        shangpinxinxi.setClicknum(shangpinxinxi.getClicknum() + 1);
        shangpinxinxi.setClicktime(new Date());
        shangpinxinxiService.updateById(shangpinxinxi);
        return R.ok().put("data", shangpinxinxi);
    }

    /**
     * 赞或踩
     */
    @RequestMapping("/thumbsup/{id}")
    public R vote(@PathVariable("id") String id, String type) {
        ShangpinxinxiEntity shangpinxinxi = shangpinxinxiService.selectById(id);
        if (type.equals("1")) {
            shangpinxinxi.setThumbsupnum(shangpinxinxi.getThumbsupnum() + 1);
        } else {
            shangpinxinxi.setCrazilynum(shangpinxinxi.getCrazilynum() + 1);
        }
        shangpinxinxiService.updateById(shangpinxinxi);
        return R.ok("投票成功");
    }

    /**
     * 后端保存
     */
    @RequestMapping("/save")
    public R save(@RequestBody ShangpinxinxiEntity shangpinxinxi, HttpServletRequest request) {
        shangpinxinxi.setId(new Date().getTime() + new Double(Math.floor(Math.random() * 1000)).longValue());
        shangpinxinxiService.insert(shangpinxinxi);
        return R.ok();
    }

    /**
     * 前端保存
     */
    @RequestMapping("/add")
    public R add(@RequestBody ShangpinxinxiEntity shangpinxinxi, HttpServletRequest request) {
        shangpinxinxi.setId(new Date().getTime() + new Double(Math.floor(Math.random() * 1000)).longValue());
        shangpinxinxiService.insert(shangpinxinxi);
        return R.ok();
    }

    /**
     * 修改
     */
    @RequestMapping("/update")
    @Transactional
    public R update(@RequestBody ShangpinxinxiEntity shangpinxinxi, HttpServletRequest request) {
        shangpinxinxiService.updateById(shangpinxinxi);
        return R.ok();
    }

    /**
     * 删除
     */
    @RequestMapping("/delete")
    public R delete(@RequestBody Long[] ids) {
        shangpinxinxiService.deleteBatchIds(Arrays.asList(ids));
        return R.ok();
    }

    /**
     * 前端智能排序
     */
    @IgnoreAuth
    @RequestMapping("/autoSort")
    public R autoSort(@RequestParam Map<String, Object> params, ShangpinxinxiEntity shangpinxinxi,
                      HttpServletRequest request, String pre) {
        EntityWrapper<ShangpinxinxiEntity> ew = new EntityWrapper<ShangpinxinxiEntity>();
        Map<String, Object> newMap = new HashMap<String, Object>();
        Map<String, Object> param = new HashMap<String, Object>();
        Iterator<Map.Entry<String, Object>> it = param.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, Object> entry = it.next();
            String key = entry.getKey();
            String newKey = entry.getKey();
            if (pre.endsWith(".")) {
                newMap.put(pre + newKey, entry.getValue());
            } else if (StringUtils.isEmpty(pre)) {
                newMap.put(newKey, entry.getValue());
            } else {
                newMap.put(pre + "." + newKey, entry.getValue());
            }
        }
        params.put("sort", "clicknum");
        params.put("order", "desc");
        PageUtils page = shangpinxinxiService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(ew, shangpinxinxi), params), params));
        return R.ok().put("data", page);
    }
}

整体来看,这个系统把基础的业务流跑通了,表结构设计得比较直接,没有过度设计。代码里用了 MyBatis Plus 的 EntityWrapper 来拼查询条件,前端排序也留了接口,但那种用 HashMap 拼前缀的方式有点绕,实际项目里我会考虑换成更直白的前后端参数约定。如果你正准备搭一个类似的酒店管理系统,或者只是想知道 SpringBoot + Vue 怎么快速起一个项目,这份记录应该能给你一些参考。

目录

  1. 数据库设计
  2. 客房信息表
  3. 订单信息表
  4. 用户信息表
  5. 技术选型
  6. 后端代码示例
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 前端 pnpm workspace 架构详解
  • 力扣 Hot100 普通数组经典题目 Python 实现
  • GLM 语言模型实战指南:构建智能写作系统
  • 动态规划:单词拆分问题详解
  • AIGC 内容创作方法论:从爆款短片《牌子》解析技术路径
  • 攻防演练中 DTcms 代码审计与密码解密实战
  • 大模型算法岗面试核心知识点与真题汇总
  • 分治算法实战:归并排序与逆序对问题
  • 二分答案专题实战:木材加工与砍树问题详解
  • WhisperLiveKit 实时语音识别指南:从安装到生产部署
  • Trae IDE 配置全局 Maven 和 JDK 详解
  • Trae IDE Java 项目全局 Maven 与 JDK 配置指南
  • 大模型医疗落地现状:顶刊研究显示辅助诊疗能力与 CPU 部署实践
  • C++26 并发编程新特性:任务队列容量优化
  • MySQL 详细安装配置完整教程
  • 语义化 AI 驱动器与提示词工程的技术演进
  • 二叉树 DFS、回溯与几道经典题的写法
  • Mac mini M4 部署 OpenClaw 与 Ollama 本地大模型接入飞书机器人
  • Web Unlocker API 与 SERP 工具构建 AI 训练数据集实战
  • 滑动窗口与前缀和:从和为 K 到最小覆盖子串

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online