使用springboot+vue+mybatisplus+redis+elementUI

一、 实现功能

除员工登录外功能外:

所有功能都要通过登录实现(安全方面 前端通过vue-router 和vuex实现页面访问拦截,后端通过mysql和redis缓存中校验用户合法性,判断其身份状态)

员工分为 管理员 和 前台职工

共有功能:

登录功能:

登录前检查状态 防止重复登录,能够判断身份 vue据此进入不同主页,同时将信息保存在redis中,减少sql与服务器压力且查询 校验方面更加便捷

普通员工功能
查看所有房间,查看剩余房间(可点击房间直接进行开房),查看营业中的房间,查看暂停营业的房间(维修,清理等等)
添加顾客信息(给客人进行登记 ),按条件查询顾客信息(可个根据 电话号码查询 、身份证号查询)、查询所有在住的人员信息,查询历史顾客信息、对顾客信息进行修改
开房,退房,办理会员,查询所有未完成的订单和已完成的订单,查询某个顾客的订单,退出登录

管理员功能
添加员工,修改员工信息,查看所有在线的员工,强制员工下线
添加房间,删除房间,修改房间,进入普通员工页面实现所有普通员工功能

二、库表设计

员工表
在这里插入图片描述

房间表
在这里插入图片描述
在这里插入图片描述
房间类型映射表
在这里插入图片描述
数据部分
在这里插入图片描述

顾客等级表
在这里插入图片描述
数据部分
在这里插入图片描述
订单表
在这里插入图片描述
在这里插入图片描述
数据
在这里插入图片描述

三。最终效果

springboot后端
在这里插入图片描述
在这里插入图片描述
vue前端:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
管理员界面
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

源码地址

https://gitee.com/wang-yongyan188/HotelManger.git

后端具体流程

总共分为4个模块:
员工模块(负责进行一些安全方面 员工的登录认证,身份校验,员工的增改查)
顾客模块(负责将顾客信息存储,对顾客信息进行增删改查),
房间模块(对房间的增删改查)
订单模块(负责开房(增),退房信息记录(改),外键房间号,员工号,顾客编号 可根据订单获取办理业务的员工所有信息,该顾客以及同住人员的所有信息,房间的所有信息以及自身订单信息(查))
接口为restful形式
事先准备
建立一个maven项目,引入springboot父项目,web依赖,测试依赖,druid连接池,lombok,mybatis-plus,redis,hutool工具包
pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>hotelsys</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/>
    </parent>
    <properties>
        <maven.compiler.source>9</maven.compiler.source>
        <maven.compiler.target>9</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.19</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.20</version>
        </dependency>
    </dependencies>
</project>

在recources目录下 创建application springboot的配置文件 分为开发环境和生产环境,两种配置方案,随时切换
application.yml

spring:
  profiles:
    active: dev
  application:
    name: HotelManger
mybatis-plus:
  mapper-locations: classpath:/com/yan/*.xml
  type-aliases-package: com.yan.entity

application-dev.yml

server:
  port: 8888
spring:
  datasource:
    username: root
    password: 1234
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/hotelsys?serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&autoReconnect=true&useSSL=false&failOverReadOnly=false
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    host: 192.168.83.100
    port: 6380
    database: 1
logging:
  level:
    com.yan.mapper: debug
#    root: warn
isStartRedis: 0

application-prod.yml 放入部署时的配置

创建启动类

@SpringBootApplication
public class HotelMangerApplication {
    public static void main(String[] args) {
        SpringApplication.run(HotelMangerApplication.class,args);
    }
}

按照数据库表,建立对应实体类,以及统一返回给前端格式的类

package com.yan.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Accessors(chain = true)
public class Common{
    private Integer status;
    private String msg;
    private Object data;
   public static Common error(int status,String msg){
        return new Common(status,msg,null);
    }
    public static Common success(String msg,Object data){
       return new Common(200,msg,data);
    }
    public static Common success(String msg){
        return new Common(200,msg,null);
    }
}

创建mapper层,service层,control层,根据需要,如果是多表查询,mybatisplus需要使用mapper.xml文件

例如 customerMapper.xml 在查询顾客时,还要展现对应的等级,等级在另一张表 涉及多表查询

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.yan.dao.CustomerMapper">
    <resultMap id="custype" type="Customer">
       <result  property="cid" column="cid"></result>
        <result property="name" column="name"></result>
        <result property="telnum" column="telnum"></result>
        <result property="grade" column="grade"></result>
        <result property="isdel" column="isdel"></result>
        <association property="grade1" javaType="com.yan.entity.Grade">
           <result property="gradeid" column="gradeid"></result>
            <result property="gradename" column="gradename"></result>
            <result property="discount" column="discount"></result>
        </association>
    </resultMap>

<select id="getAllCus" resultMap="custype">
    SELECT * FROM `customer` c,grade g WHERE c.grade=g.gradeid
</select>
    <select id="getIsDel" resultType="Customer">
        select * from customer where isdel=1
    </select>

</mapper>

员工模块 EmployeeController

员工登录

@PostMapping("/login")
public Common login(@RequestBody Employee employee, HttpSession session)

前端传递实体类对象,根据对象的eid和name通过mapper去数据库中获取两者同时相符合的数据库对象,先判断对象是否为空 否则返回失败 再将数据库对象的密码与传递过来的实体类对象密码进行equals比较 不同返回失败,相同则进一步判断数据库对象的状态(确保一个账号,只能同时在线一人)。到此就能够判断用户的合法性,将自己的状态设置为已经登录,根据前端传递 设置自己的柜台号,判断是否使用redis 是则以json方式 用户id作为key存入redis 否则存入Servletcontext中,最后在将对象传入mapper使数据库中此用户数据修改,为了保障安全 ,将密码置空通过map传递给前端

判断是否为管理者

@PostMapping("isManger/{token}")
public Boolean isManger(@PathVariable("token") String token,HttpSession session)

这个接口权限认证,可能被频繁调用 改为从缓存中获取数据 因为需要校验时用户一定已登录,缓存中有数据
前端从localstore中获取的key进行传递,接收到后,如果使用redis缓存,则从缓存中由key寻找到对象,进行转化后 获取ismanger 的值,如果没使用redis从Servletcontext中获取,最后根据ismnger 的值返回true或false

退出登录

  @GetMapping("/logout/{token}")
    public Common logout(@PathVariable String token,HttpSession session)

根据前端传递过来的token 从缓存中按key寻找 ,没有找到,则代表没有登录,返回对应信息,若找到该用户,将对象转为员工对象,并对登录状态进行修改,移除缓存中放入key ,最后调用mapper,将修改后的对象传递修改数据库(问题:暴露接口 不安全

查询所有员工

@GetMapping("/getAll")
    public Common getAllEmployee()

底层通过 return mapper.selectList(new QueryWrapper());无条件查询所有 一句搞定

修改某个员工信息

@PostMapping("updateEms")
   public Common updateEms(@RequestBody Employee employee)

前端传递对象,底层 mapper.update( employee, new QueryWrapper<>().eq("eid",eid)) 并校验是否成功,返回对应信息

查询所有在线员工 和不在线员工

  @GetMapping("/getWorking")
    public Common getWorkingEmS()
@GetMapping("/getNoWork")
    public Common getNOWorkEmS()

底层通过 return mapper.selectList(new QueryWrapper<Employee>().eq("status",status)); 实现 不同接口 ,调用相同service ,传递不同status值

添加员工

  @PostMapping("/addEms")
    public Common addEms(@RequestBody Employee employee)

前端传递对象,后端 mapper.insert(employee);

强制退出登录(适用于管理员使员工下线)

 @GetMapping("/forceLeave/{eid}")
    public Common forceLeave(@PathVariable int eid, 
    HttpSession session)

底层调用logout方法(接口地址暴露,不安全

强制退出登录(适用于员工本地token不存在时)

   @PostMapping("/forceLogout")
    public Common forceLogout(@RequestBody Employee 
    employee,HttpSession session)

前端员工点击退出,若本地localstore中key被删除,将无法通过传递key退出
这是检测本地key不存在,会弹出一个表格进行退出,员工要匹配 id name passwd 才能确认身份,安全退出

顾客模块CustomerController

查询所有顾客

 @GetMapping("getAll")
    public Common getAllCus()

底层进行多表查询 ,通过grade字段去匹配grade表

<resultMap id="custype" type="Customer">
       <result  property="cid" column="cid"></result>
        <result property="name" column="name"></result>
        <result property="telnum" column="telnum"></result>
        <result property="grade" column="grade"></result>
        <result property="isdel" column="isdel"></result>
        <association property="grade1" javaType="com.yan.entity.Grade">
           <result property="gradeid" column="gradeid"></result>
            <result property="gradename" column="gradename"></result>
            <result property="discount" column="discount"></result>
        </association>
    </resultMap>
<select id="getAllCus" resultMap="custype">
    SELECT * FROM `customer` c,grade g WHERE c.grade=g.gradeid
</select>

查询在住顾客

    @GetMapping("getInAll")
    public Common getNoAllCus()

通过用户表iddel字段实现,该字段开始为了实现假删除设计,后来觉得用来判断是否在住更加合适 即用户是否有效 在开房时,通过用户cid将此字段修改为0 退房时,通过用户cid将此字段修改为1
底层

mapper.selectList(new QueryWrapper<Customer>().eq("isdel",0));

根据身份证号 或 顾客电话号 查询

 @GetMapping("getByTel/{tel}")
    public Common getCusByTel(@PathVariable Long tel)

@GetMapping("getById/{id}")
    public Common getCusById(@PathVariable Long id)

两者只有字段不同 用相同底层实现,通过service层 封装 getCusByParam(String param,Long val)
调用dao层 mapper.selectOne(new QueryWrapper<Customer>().eq(param,val));

注册员工

 @PostMapping("regCus")
    public Common regCus(@RequestBody Customer customer)

前端传递对象 dao层使用mapper.insert(customer);

更新顾客信息

 @PostMapping("update")
    public Common updateCus(@RequestBody Customer customer)

当前端实现注册会员时候 也是修改顾客信息 dao层使用mapper.update(customer,new QueryWrapper<Customer>().eq("cid",customer.getCid())

房间模块 RoomController

查询房间所有信息

 @GetMapping("/getAll")
    public Common getAllRoom()

使用多表查询 连接roomtype表

package com.yan.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("room")
public class Room {
    @TableId
    private Integer rid;
    private Integer rtype;
    private Double extraprice;
    private Integer status;
    private String comment;
    @TableField(exist = false)
    private RoomType roomtype;
}

mapper映射

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yan.dao.RoomMapper">
    <resultMap id="roomtype" type="Room">
        <result property="rid" column="rid"></result>
        <result property="rtype" column="rtype"></result>
        <result property="extraprice" column="extraprice"></result>
        <result property="status" column="status"></result>
        <result property="comment" column="comment"></result>
        <association property="roomtype" javaType="com.yan.entity.RoomType">
            <result property="describe" column="describe"></result>
            <result property="standardprice" column="standardprice"></result>
            <result property="typename" column="typename"></result>
            <result property="tid" column="tid"></result>
        </association>
    </resultMap>

  <select id="getAllRoom" resultMap="roomtype">
        SELECT r.rid ,r.rtype,r.extraprice,r.`status`,r.`comment`,t.typename ,t.`describe` ,t.standardprice,t.tid
        from room r , roomtype t
        WHERE r.rtype=t.tid
    </select>

查询剩余房间 ,满的房间,暂停营业房间

房间状态status依次为0 ,1,2

底层同一个方法

 public List<Room>getRoomByParam(@Param("status")int status);
 <select id="getRoomByParam" resultMap="roomtype" parameterType="int">
        SELECT r.rid ,r.rtype,r.extraprice,r.`status`,r.`comment`,t.typename ,t.`describe` ,t.standardprice,t.tid
        from room r , roomtype t
        WHERE r.rtype=t.tid
          and r.`status`=#{status}
    </select>
 public List<Room> getAllFullRoom() {
        return mapper.getRoomByParam(1);
    }

    public List<Room> getAllRemainRoom() {

        return mapper.getRoomByParam(0);
    }

    public List<Room> getAllStopRoom() {
        return mapper.getRoomByParam(2);
    }

**添加房间 **

 @PostMapping("/addRoom")
    public Common addRoom(@RequestBody Room room)

前传递对象 底层一句mapper.insert(room);

更新房间

@PostMapping("/updateRoom")
    public Common updateRoom(@RequestBody Room room)
  mapper.update(room,new QueryWrapper<>().
  eq("rid",room.getRid);

删除房间

@DeleteMapping("/{rid}")
    public Common delRoom(@PathVariable("rid") int rid)

前端以restful形式传递房间号 底层调用

mapper.delete(new QueryWrapper<Room>().eq("rid",rid)

订单模块 OrderController

创建订单(开房操作)

@PostMapping("/create")
    public Common createOrder(@RequestBody Map map)

前端传递对象,对象的属性 后端用map作为key接收(也可以用order接收)

创建一个order对象 order
利用hutool工具包 获取一个唯一标识id 并设置为order的id,将订单的状态设置为未完成(因为开房,订单为进行时 退房时 为完成时 ) 从前台获取业务员的eid,房间号rid,顾客身份号cid 并设置 。获取房间使用时长,根据rid从数据中查询对象,判断房间状态 判断类型,由此确定时长为天数还是小时数 和对应价钱(房间类型标准价钱±每个房间的浮动) ,根据顾客身份号获取顾客对象 将开房人的状态设置为0 获取顾客等级由此知道顾客享受的折扣 ,最终价钱=(房间类型标准价钱±每个房间的浮动)*折扣 。
获取系统日期为startTime 如果房间类型为钟点房 利用hutool 在现在时间加上对应小时数的时间 转为string 进行设置 ;如果为普通间 再次基础上加上天数,将日期去掉时间,加上当天12:00 确保不管几点入住 截止时间为对应天数第二天12点退房 。其余获取备注,同住人cid 最后对订单对象进行插入保存。加上事务 ,使得过程出错,数据得以回滚。

 public Common createOrder(Map map) {
        try {
            System.out.println(map.toString());
            MyOrder order = new MyOrder();
            // oid
            order.setOid( IdUtil.fastSimpleUUID());
            // status
            order.setStatus(1);
            // 必要的key

            String session1 = map.get("key").toString().substring(4);
            System.out.println(session1);

            // 停掉redis 测试


            Employee employee = (Employee) eMapper.getEmsById(Integer.parseInt(session1)).getData();
//            Employee employee = (Employee) redisTemplate.opsForValue().get(session);
            // eid
            order.setEid(employee.getEid());
            // 原计划 直接前端传入间隔
            Integer daysOrHours = Integer.parseInt(String.valueOf(map.get("daysOrHours"))) ;
            // 现在前端传入截止日期



            Integer rid =Integer.parseInt(String.valueOf(map.get("rid"))) ;
            // rid
            order.setRid(rid);
            Room room = rService.getRoomByRid(rid);
            if(room.getStatus()!=0){
                return  Common.error(500,"该房间不可预订");
            }

            // realprice
            Double extraprice = room.getExtraprice();
            RoomType roomtype = room.getRoomtype();
            Double standardprice = roomtype.getStandardprice();

            
           String cid =String.valueOf(map.get("cid"));
            long l = Long.parseLong(cid);
            //cid
            order.setCid(l);

          Common cus = cService.getCusByParam("cid", l);

            Customer customer = (Customer) cus.getData();
            customer.setIsdel(1);
            double dis= customer.getGrade1().getDiscount();

            Double realprice=(standardprice+extraprice)*dis;

            DecimalFormat df = new DecimalFormat("0.00");
            String format = df.format(realprice);
            double v = Double.parseDouble(format);
            order.setRealprice(v);
            String now = DateUtil.now();
            order.setStarttime(now);

            // date
            if(room.getRtype()==6){
                // 假设 钟点房 时长 最低1h  最高 6h 任意时间入住
                Date dateNow= DateUtil.parse(now);
                DateTime dateTime = DateUtil.offsetHour(dateNow, daysOrHours);
                order.setEndtime(dateTime.toString());
            }else {
                // 假设一次最多14天 一律中午12点前退房
                Date dateNow= DateUtil.parse(now);
                DateTime dateTime = DateUtil.offsetDay(dateNow, daysOrHours);
                Date parse = DateUtil.parse(dateTime.toString("yyyy-MM-dd"));
                DateTime endtime = DateUtil.offsetHour(parse, 12);
                order.setEndtime(endtime.toString());
            }
            //可选操作
            // remark
            Object remark = map.get("remark");
            if(!ObjectUtil.isNull(remark)){
                order.setRemark(remark.toString());
            }
            // cidrest
            Object cidrest = map.get("cidrest");
            if (!ObjectUtil.isNull(cidrest)) {
                order.setCrest((List<Customer>) cidrest);
            }
            System.out.println(order);
            int insert = oMapper.createOrder(order);

            if(insert==1){
                room.setStatus(1);
                rMapper.updateRoom(room);
                cService.updateCus(customer);

                return Common.success("订单创建成功",order);
            }
            return Common.error(500,"出错");
        }
        catch (Exception e){
            return Common.error(500,"出错");
        }
    }

退房操作(获取订单,修改状态后保存)

@GetMapping("/finish/{rid}/{cid}")
    public Common finishOrder(@PathVariable("rid") int rid,@PathVariable("cid") Long cid){
        return service.finishOrder(rid,cid);
    }

前端传递房间号和开房人身份证号查找唯一订单(因为在开房时设置 每个用户只能开一个房间,也可以根据订单号作为凭证,开房时返回给用户,用户由此退房),检测订单状态 防止重复退房 在对订单状态设置为已完成(1) 将房间状态进行修改 ,对用户是否在住进行修改 获取当前系统时间作为最终离开时间 并与截止时间都转为时间对象再咋转为 毫秒数进行比较,如果离开时间大于截止时间,判定为超时,并将大于的毫秒数转为小时数 对订单备注进行设置 最后update 进行更新 同样如果出错,进行数据回滚

 public Common finishOrder(int rid, Long cid) {
        try {
            Common orderByCR = getOrderByCR(rid, cid);
            MyOrder data = (MyOrder) orderByCR.getData();
            if(data.getStatus().equals(1)){
                return Common.error(500,"订单已完成");
            }
            // 订单失效
            data.setStatus(0);
            String now = DateUtil.now();
            data.setRealtime(now);

            // 如果退房超时 给出提示 打在订单上
            Date dateNow= DateUtil.parse(now);
            String endtime = data.getEndtime();
            Date dateend= DateUtil.parse(endtime);
            if(dateNow.getTime()>dateend.getTime()){
              Long  overtime=dateNow.getTime()-dateend.getTime();

                long overHours = TimeUnit.MILLISECONDS.toHours(overtime);
                data.setRemark("超时 "+overHours+"小时");
            }
            // 房间
            Room roomByRid = rService.getRoomByRid(data.getRid());
            if(roomByRid.getStatus().equals(0)){
                return Common.error(500,"房间号错误");
            }
            roomByRid.setStatus(0);
            // 顾客
            Common cus = cService.getCusByParam("cid", cid);
            Customer data1 = (Customer) cus.getData();
            data1.setIsdel(0);
            cService.updateCus(data1);
            rService.updateRoom(roomByRid);
            updateOrder(data);
            return Common.success("退房成功",data);
        }
        catch (Exception e){
            return Common.error(500,"系统错误");
        }
    }

按条件查询订单

  @GetMapping("/getAllByCid/{cid}")
      public Common getAllOrderByCid(Long cid)
 oMapper.selectList(new QueryWrapper<MyOrder>().eq("cid", cid));

查询正在进行订单和已完成订单

 @GetMapping("/getRunOrder")
    public Common getRunOrder(){
        return service.getRunOrder(1);
    }
  @GetMapping("/getFinOrder")
    public Common getFinOrder()

oMapper.selectList(new QueryWrapper<MyOrder>().eq("status",i)));

后端部分完成

后端有待改进

缺少安全保护,数据库密码用明文存储 ,后续将使用shiro技术
对接口的保护全靠前端,接口暴露,后续在后端设置拦截器 利用jwt判断身份拦截

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐