收藏点赞不迷路 关注作者有好处
文末获取源码
项目编号:BS-XX-166
一,项目简介
随着我国经济发展水平的迅速提高,酒店行业作为服务行业出的重要组成部分,已经越来越显示出其强劲的发展势头。纵观国内外酒店业信息化发展轨迹和趋势,我们不难看出,随着洒店业竞争的加剧,酒店之间客源的争夺越来越激烈,客房销售的利润空间越来越小,酒店需要使用更有效的信息化手段,拓展经营空间,降低运营成本,提高管理和决策效率。而经济型酒店通过信息化管理提高收益的需求更加突出。尤其是在疫情的影响下,酒店之间的竞争更加激烈,而线上预定能够使中小型酒店在同类型酒店更具竞争。
本系统分为前台、后台两部分。前台用户需要通过手机短信验证,进行注册后,再通过手机号、密码进行登录。未登录时,可以看到展示的酒店信息,包括酒店环境、客房展示图;登陆前台后,用户可以进行客房预定,预定后进行订单支付、订单查询,还可以点击右上角查看用户中心,在用户中心,用户可以修改头像、密码等信息。后台用户分为两种角色:酒店员工与酒店管理员。员工可以进行房间信息的管理,例如添加新的房间、修改房间状态、房间信息批量导入等,也可以进行房客登记、退房、查看订单等操作。前台系统功能图如图3-1所示吗,后台系统功能图如图3-2所示。
图3-1 前台系统功能图
图3-2 后台系统功能图
二,环境介绍
语言环境:Java: jdk1.8
数据库:Mysql: mysql5.7
应用服务器:Tomcat: tomcat8.5.31
开发工具:IDEA或eclipse
后台开发技术:Mybatis-plus持久层框架处理数据+SpringBoot整合依赖;
前端页面开发技术:Vue+ElementUI
三,系统展示
5.1 前台用户登录注册模块
5.1.1 注册
用户在浏览器进入注册界面,按照提示输入用户名、密码、手机号后,发起短信验证。这个过程,前台会判断输入是否为空、以及两次密码输入是否一致。用户接收到短信验证码后,输入验证码,点击注册,向后台注册接口/register发起请求。后台接收到数据后,进行验证码验证,通过验证后再查询手机号是否已经注册。如果都通过,则注册成功,页面跳转至登陆界面。界面实现效果如图5-1所示。
图5-1 注册界面
5.1.2 登录
用户在注册完毕后,进入酒店登陆界面。输入自己的注册所用的手机号、密码,以及程序生成的验证码,点击登录按钮。前端先验证验证码是否正确,确定正确之后,向后台接口地址/lo/login发起ajax请求,后台验证手机号是否存在,手机号对应密码是否正确。手机号、密码验证正确后,登陆成功,反之失败。
界面实现效果如图5-2所示。
图5-2 登录界面
5.2.1 查看用户信息
用户进行登录后,点击右上角用户名出现的下拉框内中户中心选项,进入用户中心界面。在这个界面,用户可以查看到用户名、性别、头像等信息。界面实现效果如图5-3所示。
图5-3 查看用户信息界面
5.2.2 修改用户信息
在用户中心界面,用户可以对自己的用户名、性别、头像等信息进行修改。点击资料框中的头像,弹出文件选择框,选择图片文件,上传到阿里云oss文件存储服务器,返回一个图片地址。点击更改资料后,前端向后端发起请求前判断用户名是否为空,非空就继续发起请求。后端将数据库数据更改后,返回成功码,前端接受修改成功信息,弹出提示框,刷新页面。界面实现效果如图5-4所示。
图5-4 修改用户信息界面
用户查看客房类型详细信息时,可在此页面填写入住日期、退房日期、姓名、预留手机号等预定信息。点击预定,前端对信息进行非空判定,如果为空则无法向后端发起请求。通过判定后,向/createOrder接口发起请求,生成预定订单。
界面实现效果如图5-5所示。
图5-5 预定客房界面
用户在点击预定后,进入订单核对页面,核对后点击支付订单,进行支付宝沙箱支付。扫码或登录支付宝沙箱账号支付成功后,发起异步通知,修改数据库订单状态,页面跳至成功支付界面。界面实现效果如图5-6所示。
图5-6 预定客房界面
进入订单列表,页面查询所有订单,分页展示。后台用户可以在搜索条件框中输入想要查询的条件,店家查询按钮进行条件分页查询。后端收到查询请求,对数据库查询数据,得到数据后,返回给前端,前端进行数据展示。界面实现效果如图5-7所示。
图5-7 订单查询界面
在订单列表,后台用户点击不同的操作按钮,可以对订单进行不同的操作。对于已经支付完成的订单,点击退款操作按钮,输入小于房费的金额,发起退款请求,退款成功后,修改订单状态为已退款。对于待支付的订单,点击关闭操作按钮,将订单状态改为已取消。对于任意订单记录点击删除,可以将选中记录逻辑删除。点击修改按钮,可以修改订单中的房费信息。退款界面实现效果如图5-8所示;关闭界面实现效果如图5-9所示;
图5-8 订单退款界面
图5-9 订单关闭界面
后台用户点击入住信息,分页查看入住信息列表。在此页面的条件搜索框中输入条件信息,点击查询按钮,进行条件分页查询。界面实现效果如图5-10所示。
图5-10 查询入住信息界面
后台用户点击新增用户,跳转至入住信息新增界面。用户在此输入房客登记的信息,点击保存,即可将此条入住信息传至后端,由后端程序将数据插入数据库保存。界面实现效果如图5-11所示。
当入住一间房的房客大于一人时,在添加一条入住记录后,后台用户可以点击添加随客按钮,为此条入住记录添加随客记录。输入随客信息,视入住人数情况,选择保存或是保存并再次添加,将数据通过后端代码插入数据库。界面实现效果如图5-12所示。
图5-12 添加随客信息界面
后台用户在进入房间添加界面,可以填写房间信息,如房间号、房间类型、备注信息等。点击保存,将数据传输到后端,保存至数据库。界面实现效果如图5-13所示。
图5-13 房间信息添加界面
在进入房间信息导入界面后,后台用户可以点击下载模板,查看模板,然后编写一份与模板格式相同的excel文件,选取该文件,提交。后端程序将读取文件内容,将数据库不存在的房间信息存入。界面实现效果如图5-14所示。
图5-14 房间信息导入界面
四,核心代码展示
package com.tsx.boot.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.tsx.boot.entity.Admin; import com.tsx.boot.entity.query.adminQuery; import com.tsx.boot.entity.vo.admin.adminVo; import com.tsx.boot.service.AdminService; import com.tsx.boot.utils.R; import io.swagger.annotations.ApiOperation; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * <p> * 前端控制器 * </p> * * @author testjava * @since 2022-07-21 */ @RestController @RequestMapping("/admin") @CrossOrigin public class AdminController { @Autowired private AdminService adminService; @DeleteMapping("/delete/{id}")//通过路径传id值(逻辑删除需要在实体类的逻辑删除的成员属性上加注解@TableLogic) //效果:在字段上加上这个注解再执行BaseMapper的删除方法时,删除方法就会变成修改 public R removeAdmin(@PathVariable String id){ boolean flag = adminService.removeById(id); if(flag){ return R.ok(); } else { return R.error(); } } //修改 @PostMapping("/updateAdmin") public R updateAdmin(@RequestBody Admin admin){ boolean flag = adminService.updateById(admin); if(flag){ return R.ok(); }else { return R.error(); } } //添加方法 @PostMapping("/addAdmin") public R addMember(@RequestBody Admin admin){ boolean flag=adminService.save(admin); if(flag){ return R.ok(); }else { return R.error(); } } //根据id获取信息,用于回显修改 @GetMapping("/getInfo/{id}") public R getInfoById(@PathVariable String id){ QueryWrapper<Admin> wrapper=new QueryWrapper<>(); wrapper.eq("id",id); Admin admin = adminService.getOne(wrapper); return R.ok().data("admin",admin); } //登陆 @PostMapping("/login") public R login(@RequestBody adminVo adminVo){ Admin admin = adminService.login(adminVo); if(admin != null){ return R.ok().data("token",admin.getId()); }else { return R.error(); } } //退出登录 @PostMapping("/logout") public R logout(){ return R.ok(); } @GetMapping("/info/{id}") public R info(@PathVariable String id){ List<String> list=new ArrayList<>(); QueryWrapper<Admin> wrapper=new QueryWrapper<>(); wrapper.eq("id",id); Admin admin=adminService.getOne(wrapper); if(admin.getStatus()==1){ list.add("admin"); }else { list.add("staff"); } return R.ok().data("roles",list).data("name",admin.getName()).data("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"); } //分页查询后台用户 @ApiOperation(value = "用户分页查询") @PostMapping("/pageAdmin/{current}/{limit}")//当前页与每页数目 public R pageListTeacher(@PathVariable long current, @PathVariable long limit, @RequestBody(required = false) adminQuery adminQuery){ Page<Admin> pageAdmin = new Page<>(current, limit); //构建条件 QueryWrapper<Admin> wrapper=new QueryWrapper<>(); //wrapper. String name = adminQuery.getName(); Integer status = adminQuery.getStatus(); String begin = adminQuery.getBegin(); String end = adminQuery.getEnd(); if(!StringUtils.isEmpty(name)){ wrapper.like("name",name); } if(!StringUtils.isEmpty(status)){ wrapper.eq("status",status); } if(!StringUtils.isEmpty(begin)){ wrapper.ge("create_time",begin);//大于表中起始时间字段 } if(!StringUtils.isEmpty(end)){ wrapper.le("update_time",end);//小于表中起始时间字段 } //排序 wrapper.orderByDesc("update_time"); //实现条件分页查询 adminService.page(pageAdmin,wrapper); long total=pageAdmin.getTotal(); List<Admin> records=pageAdmin.getRecords(); return R.ok().data("total",total).data("rows",records); } }
package com.tsx.boot.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.tsx.boot.entity.Checkin; import com.tsx.boot.entity.other.Pie; import com.tsx.boot.service.CheckinService; import com.tsx.boot.utils.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; @RestController @RequestMapping("/charts") @CrossOrigin public class ChartController { @Autowired private CheckinService checkinService; @GetMapping("/getWeekIncome") public R getWeekIncome() throws ParseException { List<Float> list=checkinService.countWeekIncount(); List<String> timeList=checkinService.getTimeWeek(); return R.ok().data("list",list).data("times",timeList); } /* 获取当月的不同房型入住情况 */ @GetMapping("/getMonthType") public R getMonthType(){ //获取所有房型集合 List<String> types =checkinService.countype(); //获取不同房型在当月的入住次数 List<Integer> Typelist=checkinService.countMonthTypeNum(types); //封装成图标需要的数据形式 List<Pie> list=new ArrayList<>(); for(int i=0;i<types.size();i++){ Pie pie=new Pie(); pie.setName(types.get(i)); pie.setValue(Typelist.get(i)); list.add(pie); } return R.ok().data("list",list).data("types",types); } /* 计算今日收入 */ @GetMapping("/getToDayMoney") public R getToDayMoney(){ Date date=new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String day=formatter.format(date); String day_start=day+" 00:00:00"; String day_end=day+" 23:59:59"; System.out.println(day_start); QueryWrapper<Checkin> wrapper=new QueryWrapper<>(); wrapper.select("sum(money) as sumAll"); try{ wrapper.ge("create_time",formatter1.parse(day_start)); wrapper.le("create_time",formatter1.parse(day_end)); Checkin checkin=checkinService.getOne(wrapper); System.out.println(checkin.getSumAll()); }catch (Exception e){ e.printStackTrace(); return R.error(); } return R.ok(); } }
package com.tsx.boot.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.tsx.boot.entity.*; import com.tsx.boot.entity.query.checkQuery; import com.tsx.boot.service.CheckinService; import com.tsx.boot.service.ChecksService; import com.tsx.boot.service.RoomService; import com.tsx.boot.service.RoomtypeService; import com.tsx.boot.utils.R; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.ws.rs.Path; import java.math.BigDecimal; import java.nio.file.Watchable; import java.util.Date; import java.util.List; /** * <p> * 前端控制器 * </p> * * @author testjava * @since 2022-07-27 */ @RestController @RequestMapping("/check") @CrossOrigin public class CheckinController { @Autowired private CheckinService checkService; @Autowired private ChecksService checksService; @Autowired private RoomService roomService; @Autowired private RoomtypeService roomtypeService; /* 退房功能,当点击退房按钮,根据主客的入住记录id,将主客、从客入住状态转为已退房 再将房间状态转换为空闲 */ @PostMapping("/checkOut/{id}") public R checkOut(@PathVariable String id){ //根据id,将主客、从客入住状态转换为退房 UpdateWrapper<Checkin> wrapper1=new UpdateWrapper<>(); wrapper1.eq("id",id); wrapper1.set("state",0); checkService.update(null,wrapper1); UpdateWrapper<Checks> wrapper2=new UpdateWrapper<>(); wrapper2.eq("zid",id); wrapper2.set("state",0); checksService.update(null,wrapper2); //将房间状态转换为空闲 先获取房间号,再将房间状态改为空闲 QueryWrapper<Checkin> queryWrapper=new QueryWrapper<>(); queryWrapper.eq("id",id); Checkin checkin=checkService.getOne(queryWrapper); UpdateWrapper<Room> wrapper=new UpdateWrapper<>(); wrapper.eq("id",checkin.getRoomId()); wrapper.set("state",1); roomService.update(null,wrapper); return R.ok().message("退房成功!"); } /* 通过房号查到房费 */ @GetMapping("/calculate/{roomId}") public R calculate(@PathVariable String roomId){ QueryWrapper<Room> wrapper1=new QueryWrapper<>(); wrapper1.eq("id",roomId); Room room = roomService.getOne(wrapper1); if(null==room){ return R.error().message("房间号不存在!"); } BigDecimal fee= roomService.getFeebyRoomId(roomId); return R.ok().data("money",fee); } /* 通过id查询入住信息 */ @GetMapping("/getCheckInfoById/{id}") public R getCheckInfoById(@PathVariable String id){ QueryWrapper<Checkin> wrapper=new QueryWrapper<>(); wrapper.eq("id",id); Checkin checkin = checkService.getOne(wrapper); return R.ok().data("check",checkin); } /* 逻辑删除入住信息(逻辑删除需要在实体类的逻辑删除的成员属性上加注解@TableLogic 在isDelete字段上加上这个注解再执行BaseMapper的删除方法时,删除方法就会变成修改) 通过传id值,逻辑删除入住信息 */ @DeleteMapping("/deleteCheck/{id}") public R removeCheck(@PathVariable String id){ // 房间状态改为1,再删除 roomService.updateRoomState(id); boolean flag = checkService.removeById(id); if(flag){ //修改该类型剩余房间数量 roomtypeService.updateAllSTypeNum(); return R.ok(); } else { return R.error(); } } /* 修改入住信息 */ @PostMapping("/updateCheck") public R updateCheck(@RequestBody Checkin checkin){ boolean flag = checkService.updateById(checkin); if(flag){ //修改该类型剩余房间数量 roomtypeService.updateAllSTypeNum(); return R.ok(); }else { return R.error(); } } //退房 需要修改房间状态、入住状态,剩余空闲房间数量 @PostMapping("/tRoom") public R TRoom(@PathVariable String id){ //修改入住状态为退房 UpdateWrapper<Checkin> wrapper=new UpdateWrapper<>(); wrapper.eq("id",id); wrapper.set("state",2); boolean flag = checkService.update(wrapper); if(flag){ // 删除后,房间状态改为1 roomService.updateRoomState(id); return R.ok(); }else { return R.error(); } } //添加方法 @PostMapping("/addCheck") public R addCheck(@RequestBody Checkin checkin){ //入住状态初始为:1 表示入住中 checkin.setState(1); //查出房间信息、判断该记录的房间状态是否为1(空闲状态) QueryWrapper<Room> wrapper1=new QueryWrapper<>(); wrapper1.eq("id",checkin.getRoomId()); Room room = roomService.getOne(wrapper1); if(null==room){ return R.error(); } int state=room.getState(); //初始化入住记录的类型名 checkin.setTypeName(room.getTypeName()); boolean flag; //如果该房间状态为1(空闲),才能够入住 if(state==1){ flag=checkService.save(checkin); }else { return R.error(); } //新增 if(flag){ //添加入住记录,将该房间的状态修改为已经入住 UpdateWrapper<Room> wrapper=new UpdateWrapper<>(); wrapper.eq("id",checkin.getRoomId()); wrapper.set("state",2); roomService.update(wrapper); //修改该类型剩余房间数量 roomtypeService.updateAllSTypeNum(); return R.ok(); }else { return R.error(); } } /* * 分页条件查询入住信息 */ @ApiOperation(value = "入住信息分页查询") @PostMapping("/pageCheck/{current}/{limit}")//当前页与每页数目 public R pageListTeacher(@PathVariable long current, @PathVariable long limit, @RequestBody(required = false) checkQuery checkQuery){ System.out.println(checkQuery); Page<Checkin> pageRoom = new Page<>(current, limit); //构建条件 QueryWrapper<Checkin> wrapper=new QueryWrapper<>(); //wrapper. String roomId = checkQuery.getRoomId(); List<Date> dates = checkQuery.getDates(); String name = checkQuery.getName(); String idNumber=checkQuery.getIdNumber(); Integer state=checkQuery.getState(); if(!StringUtils.isEmpty(roomId)){ wrapper.eq("room_id",roomId); } if(!StringUtils.isEmpty(name)){ wrapper.like("name",name); } if(!StringUtils.isEmpty(idNumber)){ wrapper.like("id_number",idNumber); } if(!StringUtils.isEmpty(state)){ wrapper.eq("state",state); } if(!StringUtils.isEmpty(dates)){ //前端日期传到后端出了问题,在此进行处理。将日期左右边界都加1 long time1 = dates.get(0).getTime(); long time2 = dates.get(1).getTime(); Date DateStart = new Date(); Date DateEnd = new Date(); DateStart.setTime(time1 + 1000*60*60*10); DateEnd.setTime(time2 + 1000*60*60*34-1000); wrapper.between("create_time",new Date(DateStart.toString()),new Date(DateEnd.toString())); } //排序 wrapper.orderByDesc("create_time"); //实现条件分页查询 checkService.page(pageRoom,wrapper); long total=pageRoom.getTotal(); List<Checkin> records=pageRoom.getRecords(); return R.ok().data("total",total).data("rows",records); } }
五,项目总结
本系统分为前台与后台。前台采用Springboot+js、Jquery等技术实现了酒店信息展示、客房信息展示、客房预定、提交建议、用户中心等功能。后台采用Springboot+Vue实现了用户管理、房间管理、入住信息管理、订单管理、图表展示等诸多功能。数据存储使用的是MySQL。
因为使用的Springboot作为后端开发框架,简化了原本SSM框架的繁琐配置,让开发周期大大缩短。持久层框架采用MyBatisPlus这一Mybatis的增强工具,使SQL编写量大大减少。甚至可以在完成数据库表的创建后,使用代码生成器,将Controller、Mapper、Service等文件创建好。
在设计与实现的过程中,遇到了许多因为对业务没有接触过而产生的问题。例如订单数据库设计、预定流程等问题。还有因为技术选型的纠结,因为对Vue技术的不熟练,最终决定前台的实现不采用前后端分离。最后都通过百度查询资料,或与同学讨论勉强解决。系统至此实现了中小型酒店管理所需功能。
但此系统仍不可避免的有许多不足之处可以改进,例如比起电脑端的预定,大家更常用的是手机端小程序、app端上定房。还有就是,后台系统应当有一个能够看到后台用户的操作记录的日志模块。比起市面上已经完善的酒店系统,本系统还是有许多可以改进的地方,接下来若是有机会,也会不断参照,改进。