草庐IT

瑞吉外卖项目实战

Chen Mon 2023-07-09 原文

文章目录


一、软件开发整体介绍

二、 瑞吉外卖项目整体介绍

三、开发环境搭建

1、数据库环境的搭建

(一)启动Navicat

  • 启动Navicat ,创建mysql连接

(二)数据库设计

  • 数据库设计:概念设计(E-R图)、逻辑设计、物理设计

1、概念设计

  • 概念设计是数据库设计的核心环节。通过对用户需求进行综合、归纳与抽象,形成一个独立于具体DBMS的概念模型。
    (1)明确建模目标(模型覆盖范围)
    (2)定义实体集(自底向上标识和定义实体集)
    (3) 定义联系(实体间关联关系)
    (4) 建立信息模型(构造ER模型)
    (5)确定实体集属性(属性描述一个实体集的特征或性质)
    (6)对信息模型进行集成与优化(检查和消除命名不一致、结构不一致等)
  • 概念设计目前采用最广泛的是ER建模方法。将现实世界抽象为具有属性的实体及联系。1976年,Peter.Chen提出E-R模型(Entity- Relationship Model),即实体联系模型,用E-R图来描述数据库的概念模型。
  • 观点:世界是由一组称作实体的基本对象和这些对象之间的联系构成的。
  • 实体间的联系有三类:一对一联系(1:1)、一对多联系(1:n )、多对多联系(m:n)
  • E-R图实例
  • 思维导图只管呈现

2、逻辑设计

  • 将概念模型(如ER图)转化为DBMS支持的数据模型(如关系模型),并对其进行优化
(1)用户信息表(user)
字段名类型宽度小数位数是否主键备注
idbigint200主键
namevarchar500姓名
phonevarchar1000主键
sexvarchar20主键
id_numbervarchar180主键
avatarvarchar5000主键
statusint110状态 0:停用 1:启用
(2)购物车(shopping_cart)
字段名类型宽度小数位数是否主键备注
idbigint00主键
namevarchar500名称
imagevarchar1000图片
user_idbigint00主键
dish_idbigint00菜品id
setmeal_idbigint00套餐id
dish_flavorvarchar500口味
numberint00数量
amountdecimal102金额
create_timedatetime00创建时间
(3)套餐菜品关系(setmeal_dish)
字段名类型宽度小数位数是否主键备注
idbigint00主键
setmeal_idvarchar320套餐id
dish_idvarchar320菜品id
namevarchar320菜品名称
pricedecimal102菜品原价
copiesint00份数
sortint00排序
create_timedatetime00创建时间
update_timedatetime00更新时间
create_userbigint00创建人
update_userbigint00修改人
is_deletedint00是否删除
(4)套餐(setmeal)
字段名类型宽度小数位数是否主键备注
idbigint00主键
category_idbigint00菜品分类
namevarchar640套餐名称
pricedecimal102套餐价格
statusint180状态 0:停用 1:启用
codevarchar320编码
descriptionvarchar5120描述信息
imagevarchar2550图片
create_timedatetime00创建时间
update_timedatetime00更新时间
create_userbigint00创建人
update_userbigint00修改人
is_deletedint00是否删除
(5)订单明细表(orders_detail)
字段名类型宽度小数位数是否主键备注
idbigint00主键
namevarchar500名字
imagevarchar1000图片
order_idbigint00订单id
dish_idbigint00菜品id
setmeal_idbigint00套餐id
dish_flavorvarchar500口味
numberint00数量
amountdecimal102金额
(6)订单表(orders)
字段名类型宽度小数位数是否主键备注
idbigint00主键
namevarchar500是否订单号
statusint00订单状态 1待付款,2待派送,3已派送,4已完成,5已取消
user_idbigint00下单用户
address_book_idbigint00地址id
order_timedatetime00下单时间
checkout_timedatetime00结账时间
pay_methodint00支付方式 1微信,2支付宝
amountdecimal102实收金额
remarkvarchar1000备注
phonevarchar2550手机号
addressvarchar2550地址
user_namevarchar2550用户名
consigneevarchar2550接收人
(7)员工信息(employee)
字段名类型宽度小数位数是否主键备注
idbigint00主键
namevarchar320姓名
usernamevarchar320用户名
passwordvarchar640密码
phonevarchar110手机号
sexvarchar20性别
id_numbervarchar180身份证号
statusint00状态 0:禁用,1:正常
create_timedatetime00创建时间
update_timedatetime00更新时间
create_userbigint00创建人
update_userbigint00修改人
(8)菜品口味关系表(dish_flavor)
字段名类型宽度小数位数是否主键备注
idbigint00主键
dish_idvarchar00菜品
namevarchar640口味名称
valuevarchar5000口味数据
create_timedatetime00创建时间
update_timedatetime00更新时间
create_userbigint00创建人
update_userbigint00修改人
is_deletedint00是否删除
(9)菜品管理(dish)
字段名类型宽度小数位数是否主键备注
idbigint00主键
namevarchar640菜品名称
category_idbigint00菜品分类id
pricedecimal102菜品价格
codevarchar640商品码
imagevarchar2000图片
descriptionvarchar4000描述信息
statusint000 停售 1 起售
sortint00顺序
create_timedatetime00创建时间
update_timedatetime00更新时间
create_userbigint00创建人
update_userbigint00修改人
is_deletedint00是否删除
(10)菜品及套餐分类(category)
字段名类型宽度小数位数是否主键备注
idbigint00主键
typevarchar00类型 1 菜品分类 2 套餐分类
namevarchar0分类名称
sortint00顺序
create_timedatetime00创建时间
update_timedatetime00更新时间
create_userbigint00创建人
update_userbigint00修改人
(11)address_book(地址管理)
字段名类型宽度小数位数是否主键备注
idbigint00主键
user_idbigint00是否用户id
consigneevarchar500收货人
sextinyint00性别 0 女 1 男
phonevarchar110手机号
province_codevarchar120省级区划编号
province_namevarchar320省级名称
city_codevarchar120市级区划编号
city_namevarchar320市级名称
district_codevarchar120区级区划编号
district_namevarchar320区级名称
detailvarchar2000详细地址
labelvarchar1000标签
labeltinyint10默认 0 否 1是
create_timedatetime00创建时间
update_timedatetime00更新时间
create_userbigint00创建人
update_userbigint00修改人
is_deletedint00是否删除

(三)创建数据库

  • 创建项目需要的数据库 - reggie,字符集采用utf8mb4
  • 单击[确定]按钮
  • 打开reggie数据库

(四)导入数据库脚本

  • 导入数据库脚本:db_reggie.sql

  • 单击开始按钮

(五)查看数据库中的表

  • 数据库reggie包含11张表

2、Maven项目搭建

  • 两种常用项目构建工具

(一)创建Maven项目

  • 创建Maven项目,配置信息
  • 单击【Finish】按钮

(二)检查项目编码、maven仓库配置以及jdk配置

  • 对项目编码、maven仓库配置以及jdk配置进行更改
  • 安装maven软件
  • 配置maven的环境变量

    -
  • 检查maven环境是否配置成功
  • 在maven配置文件添加阿里镜像源
  • 检查IntelliJ IDEA 2021.3里maven仓库的配置
  • 检查jdk配置情况

  • 查看java版本

(三)在pom.xml文件添加项目依赖

  • 在pom.xml文件里添加相关依赖和构建插件
<?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>net.cch</groupId>
    <artifactId>ReggieTakeOut</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

<!--    <properties>-->
<!--        <maven.compiler.source>11</maven.compiler.source>-->
<!--        <maven.compiler.target>11</maven.compiler.target>-->
<!--    </properties>-->
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.14</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.4</version>
            </plugin>
        </plugins>
    </build>


</project>

(四)创建应用属性文件

  • 在resources目录下创建应用属性文件 - application.yml
  • 配置application.yml
#配置服务器
server:
  port: 8080

#配置spring框架
spring:
  application:
    name: ReggieTakeOut #应用名称
  datasource: #数据源
    druid: #druid数据源
      driver-class-name: com.mysql.cj.jdbc.Driver #驱动程序
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root  #用户名
      password: 123  #密码
mybatis-plus:
  configuration:
#    address_book---->AddressBook
#    user_name---->userName
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志实现类
  global-config: #全局配置
    db-config: #数据库配置
      id-type: ASSIGN_ID #id-type: auto #数据ID自增

(五)创建启动的主类

  • 在java创建net.cch包,然后创建ReggieApplication类

  • 启动项目ReggieApplication
package net.cch;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 功能: 项目启动类
 * 作者: 陈春宏
 * 时间: 2022/10/20 11:16
 */
@Slf4j
@SpringBootApplication
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功!");
    }
}

  • 查看项目启动页面

  • log对象的五个方法

方法名作用
info()输出普通信息
debug()输出调试信息
error()输出错误信息
warn()输出调试信息

(六)拷贝静态资源和模板资源

  • 将backend、frontend拷贝到ReggieTakeOut项目下的resource下面

  • 启动项目
  • 启动应用在浏览器输入localhost:8080/backend/index.html

(七)配置静态资源映射

  • 在ReggieTakeOut.src.main.java.net.cch.reggie创建config子包,在config里创建WebMvcConfig配置类
package net.cch.reggie.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * 功能: 静态资源映射
 * 作者: 陈春宏
 * 时间: 2022/10/26 21:23
 */
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Override

    protected void addResourceHandlers(ResourceHandlerRegistry registry){
        log.info("开始启动静态资源!");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }
}

  • WebMvcConfig继承WebMvcConfigurationSupport类重写addResourceHandlers方法
  • 打印静态资源映射
  • 测试项目查看静态资源映射
  • 启动应用在浏览器输入localhost:8080/backend/index.html,访问后端首页
  • 访问前端图片资源

四、后台登录功能开发

1、需求分析

(一)页面原型展示

  • 找到项目资源 - 产品原型 > 瑞吉外卖后台(管理端)- 登录.html
  • 单击 登录.html 页面
  • 登录页面前端页面在输入用户名和密码后点登录时调用ajax函数并将用户名和密码传至后台,后台控制器要编写相应的处理函数,对提交的数据进行业务处理,然后将处理结果返回给前端。
  • 查看login.html 页面代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>瑞吉外卖管理端</title>
  <link rel="shortcut icon" href="../../favicon.ico">
  <!-- 引入样式 -->
  <link rel="stylesheet" href="../../plugins/element-ui/index.css" />
  <link rel="stylesheet" href="../../styles/common.css">
  <link rel="stylesheet" href="../../styles/login.css">
  <link rel="stylesheet" href="../../styles/icon/iconfont.css" />
  <style>
    .body{
      min-width: 1366px;
    }
  </style>
</head> 

<body>
  <div class="login" id="login-app">
    <div class="login-box">
      <img src="../../images/login/login-l.png" alt="">
      <div class="login-form">
        <el-form ref="loginForm" :model="loginForm" :rules="loginRules" >
          <div class="login-form-title">
            <img src="../../images/login/logo.png" style="width:139px;height:42px;" alt="" />
          </div>
          <el-form-item prop="username">
            <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号" maxlength="20"
              prefix-icon="iconfont icon-user" />
          </el-form-item>
          <el-form-item prop="password">
            <el-input v-model="loginForm.password" type="password" placeholder="密码" prefix-icon="iconfont icon-lock" maxlength="20"
              @keyup.enter.native="handleLogin" />
          </el-form-item>
          <el-form-item style="width:100%;">
            <el-button :loading="loading" class="login-btn" size="medium" type="primary" style="width:100%;"
              @click.native.prevent="handleLogin">
              <span v-if="!loading">登录</span>
              <span v-else>登录中...</span>
            </el-button>
          </el-form-item>
        </el-form>
      </div>
    </div>
  </div>

  <!-- 开发环境版本,包含了有帮助的命令行警告 -->
  <script src="../../plugins/vue/vue.js"></script>
  <!-- 引入组件库 -->
  <script src="../../plugins/element-ui/index.js"></script>
  <!-- 引入axios -->
  <script src="../../plugins/axios/axios.min.js"></script>
  <script src="../../js/request.js"></script>
  <script src="../../js/validate.js"></script>
  <script src="../../api/login.js"></script>

  <script>
    new Vue({
      el: '#login-app',
      data() {
        return {
          loginForm:{
            username: 'admin',
            password: '123456'
          },
          loading: false
        }
      },
      computed: {
        loginRules() {
          const validateUsername = (rule, value, callback) => {
            if (value.length < 1 ) {
              callback(new Error('请输入用户名'))
            } else {
              callback()
            }
          }
          const validatePassword = (rule, value, callback) => {
            if (value.length < 6) {
              callback(new Error('密码必须在6位以上'))
            } else {
              callback()
            }
          }
          return {
            'username': [{ 'validator': validateUsername, 'trigger': 'blur' }],
            'password': [{ 'validator': validatePassword, 'trigger': 'blur' }]
          }
        }
      },
      created() {
      },
      methods: {
        async handleLogin() {
          this.$refs.loginForm.validate(async (valid) => {
            if (valid) {
              this.loading = true
              let res = await loginApi(this.loginForm)
              if (String(res.code) === '1') {
                localStorage.setItem('userInfo',JSON.stringify(res.data))
                window.location.href= '/backend/index.html'
              } else {
                this.$message.error(res.msg)
                this.loading = false
              }
            }
          })
        }
      }
    })
  </script>
</body>

</html>

  • Vue对象通过el属性绑定了id属性为login-app的div元素
  • Vue对象通过data() 方法绑定JSON数据loginForm,通过computed绑定校验规则 loginRules
  • Vue对象通过methods绑定对登录表单数据进行处理的一步方法handleLogin
  • 在前端处理函数里,有后端处理函数返回的结果,保存在res变量里,里面有三个数据:res.coderes.datares.msg,这就要求后端处理函数返回JSON数据必须要包含这三项内容

(二)登录页面展示

  • 页面位置:在resource/backend/page/login/login.html
  • 为什么Vue对象里要绑定这个用户登录数据呢?
  • 因为员工表employee里有一条数据:admin123456(MD5加密之后就成了e10adc3949ba59abbe56e057f20f883e)
  • 单击【登录】按钮,首先进行校验,如果校验通过,按钮标题就会变成登录中……,如果校验失败,按钮标题就依然是登录

(三)查看登录请求信息

  • F12键进入浏览器的调试模式
  • 说明单击登录按钮通过客户端校验之后,请求的URL:
    http://localhost:8080/employee/login
  • 后面我们会在雇员控制器里编写相应的处理函数login()
@RestController // 交给Spring容器管理
@RequestMapping("/employee")
public class EmployeeController {
   
    @PostMapping("/login")
    public R<Employee> login(HttpRequest request, @RequestBody Employee employee) {
        return null;
    }
}

(四)数据模型 - 雇员表

  • 查看雇员表结构

2、代码开发

  • 开发流程图

(一)创建雇员实体类

  • ORM(Object Relation Mapping)对象关系映射
  • 雇员实体类(Employee)— 雇员表(employee)
序号实体属性名关系字段名
1idid
2namename
3usernameusername
4passwordpassword
5phonephone
6sexsex
7idNumberid_number
8statussatus
9createTimecreate_time
10statusstatus
11createUsercreate_user
12updateUserupdate_user
  • 创建entity子包在里面创建雇员实体类 - Employee
package net.cch.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 功能: 员工实体类
 * 作者: 陈春宏
 * 时间: 2022/10/27 11:16
 */

@Data //Lombok注解,注在类上,提供类的get、set、equals、hashCode、CanEqual、toString方法
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    private String username;

    private String name;

    private String password;

    private String phone;

    private String sex;

    private String idNumber; //对应id_number

    private Integer status;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT) //mybatis-plus注解,填充策略
    private Long createUser;//对应字段 -create_user

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;//对应字段 -update_user


}

(二)创建雇员映射器接口

  • 创建mapper子包 ,在mapper子包里创建雇员映射器接口 - EmployeeMapper
  • 采用了mybatis-plus插件,就不用再去创建对应的映射器配置文件(EmployeeMapper.xml)

package net.cch.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import net.cch.reggie.entity.Employee;
import org.apache.ibatis.annotations.Mapper;

/**
 * 功能:EmployeeMapper接口
 * 作者: 陈春宏
 * 时间: 2022/10/27 21:20
 */
@Mapper //交给spring容器来管理
public interface EmployeeMapper extends BaseMapper<Employee> {

}

(三)创建雇员服务

1、创建雇员服务接口

  • 创建service子包 ,在service包里创建雇员服务接口 - EmployeeService
  • 采用mybatis-plus插件,代码及其简单,只需要继承IService<Employee>接口
package net.cch.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import net.cch.reggie.entity.Employee;


/**
 * 功能: EmployeeService接口
 * 作者: 陈春宏
 * 时间: 2022/10/27 21:21
 */

public interface EmployeeService extends IService<Employee> {
}

2、创建雇员服务接口实体类

net.cch.service包里创建impl子包,在子包里创建雇员服务接口实体类 - EmployeeServiceImpl

package net.cch.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.cch.reggie.entity.Employee;
import net.cch.reggie.mapper.EmployeeMapper;
import net.cch.reggie.service.EmployeeService;
import org.springframework.stereotype.Service;

/**
 * 功能: EmployeeService实现类
 * 作者: 陈春宏
 * 时间: 2022/10/27 21:26
 */
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {

}

(四)创建返回结果类

  • 服务器端所有处理方法返回结果都封装到这个通用类里
  • 创建common子包 ,里创建返回结果类 - R
package net.cch.reggie.common;

import lombok.Data;
import java.util.HashMap;
import java.util.Map;

/**
 * 通用返回结果,服务器响应的数据最终会封装成此对象
 * @param <T>
 */

@Data
public class R<T> {

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}

(五)创建雇员控制器

  • 创建controller子包 ,在子包下创建 - EmployeeController
package net.cch.reggie.controller;

import 
import lombok.extern.slf4j.Slf4j;
import net.cch.reggie.common.R;
import net.cch.reggie.entity.Employee;
import net.cch.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;


/**
 * 功能: 员工管理控制层
 * 作者: 陈春宏
 * 时间: 2022/10/27 21:28
 */
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    /**
     * 员工登录
     * @param request
     * @param employee
     * @return
     */
    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {
       return null;
    }
}
  • 登录方法处理逻辑

1、将页面提交的密码password进行md5加密处理
2、根据页面提交的用户名username查询数据库
3、如果没有查询到则返回登录失败结果
4、密码比对,如果不一致则返回登录失败结果
5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
6、登录成功,将员工id存入Session并返回登录成功结果

  • 登录方法流程图
  • 将页面提交的密码password进行md5处理
 //1、将页面提交的密码进行md5加密处理
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());
  • 根据页面提交的用户名username查询数据库
  • 一般情况下,按用户名查询,返回的是一个记录集,但是雇员表对用户名字短做了唯一约束。所以按用户名查询雇员表,只有两种情况:要么没找到,要么找到一条。

//2、根据页面提交的用户名username查询数据库
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);
  • 如果没有查询到则返回登录失败的结果
 //3、如果没有查询到则返回登录失败的结果
        if(emp == null){
            return R.error("登录失败[用户名错误]");
        }
  • 密码比对,如果不一致则返回密码错误的结果
//4、密码比对,如果不一致则返回密码错误的结果
        if(!emp.getPassword().equals(password)){
            return R.error("登录失败[密码错误]");
        }

  • 查看员工状态,如果为禁用状态,则返回员工已禁用结果
  //5、查看员工状态,如果为禁用状态,则返回员工已禁用结果

        if(emp.getStatus() == 0){
            return  R.error("账号已禁用!");
        }
  • 登录成功,将员工id存入到Session并返回登录结果
//6、登录成功,将员工id存入到Session并返回登录结果

        request.getSession().setAttribute("Employee",emp.getId());
        return R.success(emp);

3、功能测试

(一)修改超时配置

  • resources/backend/js/request.js文件里设置超时为1000000毫秒,便于后面做断点调试

(二)设置断点

  • EmployeeController里设置断点
  • 查看控制台信息

(四)测试登录 - [成功]

  • 浏览器访问 http://localhost:8080/backend/page/login/login.html

  • F12键,打开开发者工具

  • 使用用户名和密码登录,admin : 123456 ,单击登录按钮

  • 查看断点调试信息

  • 击[Step Over] 按钮3次,判断用户是否错误

  • 单击【Step Over】按钮,判断密码是否错误

  • 单击【Step Over】按钮,判断雇员状态是否已禁用

  • 单击【Step Over】按钮3次,返回登录成功结果

  • 此时,查看登录页面,登录成功,会本地存储用户信息

(五)测试登录 - [失败]

  • 浏览器访问 http://localhost:8080/backend/page/login/login.html
  • 测试用户名登录失败

  • 测试密码比对失败
  • 测试员工状态禁用,修该employee表的status字段改为0

五、后台退出功能开发

1、需求分析

  • 员工登录成功后,页面跳转到后台系统首页(backend/index.html),此时会显示当前登录用户的姓名,如果员工需要退出系系统,直接点击右侧的退出按钮即可退出系统,退出系统后页面应跳转回登录页面。

  • 员工登录成功后跳转到系统首页
  • 显示当前用户登录
  • 通过localStorage方法得到用户登录信息
  • 查看存储好的userinfo信息

2、代码开发

  • 用户点击页面中退出按钮,发送请求,请求地址为/employee/logout,请求方式为POST.

(一)清理Session中的用户id

    /**
     * 员工退出
     * @param request
     * @return
     */
    @PostMapping("/logout")
    public R<String> logout(HttpServletRequest request){
        //清理Session中保存的当前员工的id
        request.getSession().removeAttribute("employee");

        return null;

    }

(二)返回结果

   /**
     * 员工退出
     * @param request
     * @return
     */
    @PostMapping("/logout")
    public R<String> logout(HttpServletRequest request){
        //清理Session中保存的当前员工的id
        request.getSession().removeAttribute("employee");
        return R.success("退出成功");
    }

3、功能测试

(一)重启服务

(二)测试退出

  • 按住F12进入调试页面
  • 点击退出跳回了登录页面,LocalStorage的userinfo没有了

六、完善登录功能

1、问题分析

  • 前面我们已经完成了后台系统员工登录功能的开发,但是还是存在一个问题:如果用户不登录,可以直接访问系统首页面。
  • 这种设计不合理,我们系统看到的效果应该是,只有在登录成功后才能访问系统的首页。没有登录则跳转到登录页面

  • 使用过滤器判断用户是否已经完成登录,如果没有登录跳转到登录页面。

2、代码实现

(一)创建自定义过滤器

  • 创建filter子包,在filter子包里创建LoginCheckFilter过滤器
package net.cch.reggie.filter;

import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 陈春宏
 * @function 检查用户是否已经完成登录
 * @create 2022/11/22 23:09
 */

@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        log.info("拦截到请求:{}",request.getRequestURI());
        filterChain.doFilter(request,response);
    }
}

(二)在启动类上加入注解@ServletComponentScan

  • 查看过滤器是否生效,在启动类上加上注解@ServletComponentScan,这样才会扫描WebFilter注解从而过滤器创建出来
package net.cch.reggie;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

/**
 * 功能: 项目启动类
 * 作者: 陈春宏
 * 时间: 2022/10/20 11:16
 */
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功!");
    }
}

  • 测试过滤器是否生效,如果能生效在具体编写处理的细节问题
  • 启动项目
  • 在首页上刷新页面
  • 查看控制台,请求的是/backend/index.html

(三)完善过滤器的处理逻辑

  • 过滤器具体处理逻辑:
1.获取本次请求的URL
2.判断本次请求是否需要处理
3.如果不需要处理,则直接放行
4.判断登录状态,如果已经登录,则直接放行
5.如果未登录则返回未登录的结果

  • 获取本次请求的URI
package net.cch.reggie.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 陈春宏
 * @function 检查用户是否已经完成登录
 * @create 2022/11/22 23:09
 */

@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器,支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        //1.获取本次请求的URL
        String requestURI = request.getRequestURI();

        //定义不需要请求的URL
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };
     
        log.info("拦截到请求:{}", request.getRequestURI());
        filterChain.doFilter(request, response);
    }
}

  • 判断本次请求是否需要处理
package net.cch.reggie.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 陈春宏
 * @function 检查用户是否已经完成登录
 * @create 2022/11/22 23:09
 */

@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器,支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        //1.获取本次请求的URL
        String requestURI = request.getRequestURI();

        //定义不需要请求的URL
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };
        //2.判断本次请求是否需要处理
        boolean check = check(urls, requestURI);

        //3.如果不需要处理,则直接放行
        //4.判断登录状态,如果已经登录,则直接放行
        //5.如果未登录则返回未登录的结果


        log.info("拦截到请求:{}", request.getRequestURI());
        filterChain.doFilter(request, response);
    }

    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * @param requestURI
     * @return
     */
    //封装一个check方法来判断
    public boolean check(String[] urls, String requestURI) {
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if (match) {
                return true;
            }
        }
        return false;
    }
}

  • 如果不需要处理,则直接放行
 //3.如果不需要处理,则直接放行
        if (check) {
            filterChain.doFilter(request, response);//放行
            return;
        }
  • 判断登录状态,如果已经登录,则直接放行
    //4.判断登录状态,如果已经登录,则直接放行

        if(request.getSession().getAttribute("employee") != null){
            filterChain.doFilter(request,response);
            return;
        }

  • 如果未登录则返回未登录的结果,需要结合页面上的js代码来看
package net.cch.reggie.filter;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import net.cch.reggie.common.R;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 陈春宏
 * @function 检查用户是否已经完成登录
 * @create 2022/11/22 23:09
 */

@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器,支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        //1.获取本次请求的URL
        String requestURI = request.getRequestURI();

        //定义不需要请求的URL
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };
        //2.判断本次请求是否需要处理
        boolean check = check(urls, requestURI);

        //3.如果不需要处理,则直接放行
        if (check) {
            filterChain.doFilter(request, response);//放行
            return;
        }
        //4.判断登录状态,如果已经登录,则直接放行

        if(request.getSession().getAttribute("employee") != null){
            filterChain.doFilter(request,response);
            return;
        }
        //5.如果未登录则返回未登录的结果,通过输出䄦的方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;


//        log.info("拦截到请求:{}", request.getRequestURI());
//        filterChain.doFilter(request, response);
    }

    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * @param requestURI
     * @return
     */

    public boolean check(String[] urls, String requestURI) {
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if (match) {
                return true;
            }
        }
        return false;
    }
}

3、功能测试

  • 在测试之前在代码里加入一些日志方便我们调试代码,因为加了@Slf4j
    注解,可以通过log来记录日志
package net.cch.reggie.filter;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import net.cch.reggie.common.R;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 陈春宏
 * @function 检查用户是否已经完成登录
 * @create 2022/11/22 23:09
 */

@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器,支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        //1.获取本次请求的URL
        String requestURI = request.getRequestURI();

        log.info("拦截到请求:{}",requestURI);

        //定义不需要请求的URL
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };
        //2.判断本次请求是否需要处理
        boolean check = check(urls, requestURI);

        //3.如果不需要处理,则直接放行
        if (check) {
            log.info("本次请求{}不需要处理",requestURI);
            filterChain.doFilter(request, response);//放行
            return;
        }
        //4.判断登录状态,如果已经登录,则直接放行

        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
            filterChain.doFilter(request,response);
            return;
        }

        log.info("用户未登录");
        //5.如果未登录则返回未登录的结果,通过输出䄦的方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;



    }

    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * @param requestURI
     * @return
     */

    public boolean check(String[] urls, String requestURI) {
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if (match) {
                return true;
            }
        }
        return false;
    }
}

  • 启动服务
  • 直接进入首页会自动跳转到登录页面
  • 单击登录

七、新增员工

1、需求分析

  • 后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击[添加员工]按钮跳转到新增页面,如下图所示:

2、数据模型

  • 新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是惟一的。
  • employee表中的status字段已经设置了默认值1,表示状态正常。

3、代码开发

  • 在开发代码之前,需要先梳理一下整个程序的执行过程:
1、页面发送Ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
2、服务端Controller接收页面提交的数据并调研Service将数据进行保存
3、Service调用Mapper操做数据库,保存数据

有关瑞吉外卖项目实战的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  3. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  4. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  5. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  6. ruby - 如何在 Ruby 字符串中插入项目符号字符? - 2

    我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195

  7. ruby - 在 Rails 项目中测试本地版本的 gem - 2

    我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行​​bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正

  8. ruby - 合并 nanoc 中的项目 - 2

    我一直在尝试使用nanoc用于生成静态网站。我需要组织一个复杂的排列页面,我想让我的内容保持干燥。包含或合并的概念在nanoc系统中如何运作?我已阅读文档,但似乎找不到我想要的内容。例如:我如何获取两个部分内容项并将它们合并到一个新的内容项中。在staticmatic您可以在您的页面中执行以下操作。=partial('partials/shared/navigation')类似的约定在nanoc中如何运作? 最佳答案 这里是nanoc的作者。在nanoc中,部分是布局。因此,您可以拥有layouts/partials/shared/

  9. Ruby 和指南针路径与 yeoman 项目 - 2

    我安装了ruby​​、yeoman,当我运行我的项目时,出现了这个错误:Warning:Running"compass:dist"(compass)taskWarning:YouneedtohaveRubyandCompassinstalledthistasktowork.Moreinfo:https://github.com/gruUse--forcetocontinue.Use--forcetocontinue.我有进入可变session目标的路径,但它不起作用。谁能帮帮我? 最佳答案 我必须运行这个:geminstallcom

  10. node.js - 如何在 Travis CI 上的一个项目中运行 Node.js 和 Ruby 测试 - 2

    我有一个包含多个组件的存储库,其中大部分是用JavaScript(Node.js)编写的,一个是用Ruby(RubyonRails)编写的。我想要一个.travis.yml文件来触发一个运行每个组件的所有测试的构建。根据thisTravisCIGoogleGroupthread,目前还没有官方支持。我的目录结构是这样的:.├──构建服务器├──核心├──扩展├──网络应用├──流浪文件├──package.json├──.travis.yml└──生成文件我希望能够运行特定版本的Ruby(2.2.2)和Node.js(0.12.2)。我已经有了一个make目标,所以maketest在每

随机推荐