Part02 ☀️

YangeIT大约 23 分钟瑞吉外卖软件开发流程登录退出过滤器

Part02 ☀️

课程内容

  • 软件开发整体 🍐
  • 瑞吉外卖项目介绍
  • 开发环境搭建 ✏️
  • 后台登录功能开发 ✏️
  • 后台退出功能开发 ✏️
  • 完善登录功能 ✏️

1. 软件开发整体介绍 🍐

软件开发整体介绍

作为一名软件开发工程师,我们需要了解在软件开发过程中的开发流程, 以及软件开发过程中涉及到的岗位角色,角色的分工、职责, 并了解软件开发中涉及到的三种软件环境。那么这一小节,我们将从 三个方面,来整体上介绍一下软件开发。

软件开发流程 🍐

image
image
岗位/角色职责/分工
项目经理对整个项目负责,任务分配、把控进度
产品经理进行需求调研,输出需求调研文档、产品原型等
UI 设计师根据产品原型输出界面效果图
架构师项目整体架构设计、技术选型等
开发工程师功能代码实现
测试工程师编写测试用例,输出测试报告
运维工程师软件环境搭建、项目上线

角色分工是在一个项目组中比较标准的角色分工, 但是在实际的项目中, 有一些项目组由于人员配置紧张, 可能并没有专门的架构师或测试人员, 这个时候可能需要有项目经理或者程序员兼任

2. 瑞吉外卖项目介绍 🍐

瑞吉外卖项目介绍

在开发瑞吉外卖这个项目之前,我们需要全方位的来介绍一下当前我们学习的这个项目。接下来,我们将从以下的五个方面 👇, 来介绍瑞吉外卖这个项目。

2.1 项目介绍

image-20210726000655646
image-20210726000655646

本项目(瑞吉外卖)是专门为餐饮企业(餐厅、饭店) 定制的一款软件产品,包括 系统管理后台 和 移动端应用 两部分。其中系统管理后台 主要提供给餐饮企业内部员工使用,可以对餐厅的分类、菜品、套餐、订单、员工等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。

本项目共分为 2 期进行开发:

阶段功能实现
第一期主要实现基本需求,其中移动端应用通过 H5 实现,用户可以通过手机浏览器访问
第二期主要针对系统进行优化升级,提高系统的访问性能
                       |

3. 开发环境搭建 ✏️

3.1 数据库环境搭建

数据库环境搭建

创建数据库和数据库表导入

可以通过以下两种方式中的任意一种, 来创建项目的数据库和数据库表导入:

image
image

1).图形界面 使用Idea导入数据库资料

image
image

项目的数据库reggie必须先创建, 才可以直接将 资料/数据模型/db_reggie.sql 直接导入到数据库中

数据库表介绍

数据库表导入之后, 接下来介绍一下本项目中所涉及到的表结构:

序号表名说明
1employee员工表
2category菜品和套餐分类表
3dish菜品表
4setmeal套餐表
5setmeal_dish套餐菜品关系表
6dish_flavor菜品口味关系表
7user用户表(C 端)
8address_book地址簿表
9shopping_cart购物车表
10orders订单表
11order_detail订单明细表

总结

课堂作业

  1. 根据上述提示,使用Idea创建reggie数据库后,完成数据库脚本的导入🎤
  2. 根据上述提示,熟悉每一张表的业务意义

3.2 Maven 项目导入(12项目) ✏️

Maven 项目导入

image
image

导入步骤:

image
image

按照上述步骤,导入已有的Maven工作

注意 :导入成功后,需要检查Maven的配置,并且初次导入时间较长,可能是因为需要下载jar包,后续不会重复下载!

4. 后台系统登录退出功能 🍐 ✏️

4.1 后台项目介绍和准备工作 🍐

后台项目介绍和准备工作

1). 页面原型展示

image-20210726233540703
image-20210726233540703

2). 登录页面成品展示

登录页面存放目录 /resources/backend/page/login/login.html

image-20210726233631409
image-20210726233631409

代码操作

1. 工程说明:

  1. common --放一些通用的类
  2. config --配置文件
  3. controller --放一些控制类,用来处理前端请求
  4. entity --对象实体类
  5. mapper --操作数据库的接口类
  6. service --业务层代码

调用顺序: 控制层(controller) 调用业务层(service)--->调用持久层(mapper) image-20210726234548093


2). 创建实体类 Employee

该实体类主要用于和员工表 employee 进行映射。 该实体类, 也可以直接从资料( 资料/实体类 )中拷贝工程中。

所属包: com.itheima.reggie.entity

@Data
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)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
}

3). 定义 Mapper 接口和Service接口

在 MybatisPlus 中, 自定义的 Mapper 接口, 需要继承自 BaseMapper。

所属包: com.itheima.reggie.mapper

@Mapper
public interface EmployeeMapper extends BaseMapper<Employee>{
}

本项目的 Service 接口, 在定义时需要继承自 MybatisPlus 提供的 Service 层接口 IService, 这样就可以直接调用 父接口的方法直接执行业务操作, 简化业务层代码实现。

所属包: com.itheima.reggie.service


public interface EmployeeService extends IService<Employee> {
}

image
image

4). Service 实现类

所属包: com.itheima.reggie.service.impl


@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployeeService{
}
image
image

5). 导入通用结果类 R

此类是一个通用结果类,服务端响应的所有结果最终都会包装成此种类型返回给前端页面。

image
image

所属包: com.itheima.reggie.common


/**
 * 通用返回结果,服务端响应的数据最终都会封装成此对象
 * @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;
    }
}



A. 如果业务执行结果为成功, 构建 R 对象时, 只需要调用 success 方法; 如果需要返回数据传递 object 参数, 如果无需返回, 可以直接传递 null。

B. 如果业务执行结果为失败, 构建 R 对象时, 只需要调用 error 方法, 传递错误提示信息即可。

总结

课堂作业

  1. 实体类和数据库中的表有什么关系?🎤
  2. 使用MybatisPlus有什么优势!!
  3. 为什么要完成12项目?

4.2 后台项目登录接口开发 ✏️

后台项目登录接口开发

处理逻辑

①. 将页面提交的密码 password 进行 md5 加密(DigestUtils)处理 , 得到加密后的字符串

②. 根据页面提交的用户名 username 查询数据库中员工数据信息

③. 如果没有查询到, 则返回登录失败结果

④. 密码比对,如果不一致, 则返回登录失败结果

⑤. 查看员工状态,如果为已禁用状态,则返回员工已禁用结果

⑥. 登录成功,将员工 id 存入 Session, 并返回登录成功结果

代码操作

技术点说明:

  1. 由于需求分析时, 我们看到前端发起的请求为 post 请求, 所以服务端需要使用注解 @PostMapping
  2. 由于前端传递的请求参数为 json 格式的数据, 这里使用 Employee 对象接收, 但是将 json 格式数据封装到实体类中, 在形参前需要加注解 @RequestBody
浏览器按住F12点击网络可以查看
浏览器按住F12点击网络可以查看
/**
 * 员工登录
 * @param request
 * @param employee
 * @return
 */
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee){

    //1、将页面提交的密码password进行md5加密处理
    String password = employee.getPassword();
    password = DigestUtils.md5DigestAsHex(password.getBytes());

    //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("账号已禁用");
    }

    //6、登录成功,将员工id存入Session并返回登录成功结果
    request.getSession().setAttribute("employee",emp.getId());
    return R.success(emp);
}

总结

课堂作业

  1. 根据截图,完成登录代码的书写🎤

4.3 后台系统退出功能 ✏️

后台系统退出功能

在后台管理系统中,管理员或者员工,登录进入系统之后,页面跳转到后台系统首页面(backend/index.html),此时会在系统的右上角显示当前登录用户的姓名。

如果员工需要退出系统,直接点击右侧的退出按钮即可退出系统,退出系统后页面应跳转回登录页面。

1). 退出页面展示

image-20210727005437531

2).前端请求分析

image
image

退出发出了一个POST请求,没有参数

代码操作

需要在 Controller 中创建对应的处理方法, 接收页面发送的 POST 请求 /employee/logout ,具体的处理逻辑:

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

注意

用户如果不登录,直接访问系统首页面,照样可以正常访问。 如:不登录,直接访问:http://localhost:8080/backend/index.htmlopen in new window

总结

课堂作业

  1. 根据上述提示,完成退出的书写,并且思考注意的问题? 不登录下直接访问首页?🎤

4.4. 完善登录功能 ✏️ 🍐

完善登录功能

前面我们已经完成了后台系统的员工登录功能开发,但是目前还存在一个问题,接下来我们来说明一个这个问题, 以及如何处理。

1). 目前现状

用户如果不登录,直接访问系统首页面,照样可以正常访问。

image-20210727232226862
image-20210727232226862

2). 理想效果

上述这种设计并不合理,我们希望看到的效果应该 是,只有登录成功后才可以访问系统中的页面,如果没有登录, 访问系统中的任何界面都直接跳转到登录页面。

image-20210727232747276

那么,具体应该怎么实现呢?

可以使用我们之前讲解过的 过滤器、拦截器来实现,在过滤器、拦截器中拦截前端发起的请求,判断用户是否已经完成登录,如果没有登录则返回提示信息,跳转到登录页面。

代码操作

1). 定义登录校验过滤器

自定义一个过滤器 LoginCheckFilter 并实现 Filter 接口, 在 doFilter 方法中完成校验的逻辑。 那么接下来, 我们就根据上述分析的步骤, 来完成具体的功能代码实现:

image
image

所属包: com.itheima.reggie.filter

/**
 * 检查用户是否已经完成登录
 * filterName:过滤器在对象容器中的名字
 * urlPatterns:匹配路径 /* 匹配所有的路径
 */
@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 {
        HttpServletRequest request= (HttpServletRequest) servletRequest;  //获取session中值
        HttpServletResponse response= (HttpServletResponse) servletResponse; //重定向

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

//        {}占位符
        log.info("接受到的请求路径:{}",requestURI);

        //定义不需要处理的请求路径--白名单
        String[] urls = new String[]{
                "/employee/login",  // 登陆页 直接放行
                "/employee/logout",//注销页 直接放行
                "/backend/**", //所有的pc端的 前端资源 放行
                "/front/**" //所有的移动端 前端资源 放行
        };


        //2、判断本次请求是否需要处理
        boolean check = check(urls, requestURI);

        //3、如果不需要处理,则直接放行
        if(check){
            //filterChain.doFilter 放行
            filterChain.doFilter(servletRequest,servletResponse);
            //直接返回该方法,下面的代码不要执行了
            return;
        }


        //4、判断登录状态,如果已登录,则直接放行
        HttpSession session = request.getSession();
        Object employee = session.getAttribute("employee");

        //如果用户id不等于空,说明已经登陆过,直接放行
        if (employee!=null){

            log.info("用户已经登录,直接放行");

            filterChain.doFilter(servletRequest,servletResponse);
            //直接返回该方法,下面的代码不要执行了
            return;
        }


        log.info("用户未登录");
        //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据

        //JSON.toJSONString 将对象 转成 JSON字符串 给前端使用  返回的数据一定是NOTLOGIN  且code 为0
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
    }


        /**
        * 路径匹配,检查本次请求是否需要放行
        * @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;
        }
}





 












































































AntPathMatcher 拓展:介绍: Spring 中提供的路径匹配器 ; 通配符规则:

符号含义
?匹配一个字符
*匹配 0 个或多个字符
**匹配 0 个或多个目录/字符

2). 开启组件扫描

需要在引导类上, 加上 Servlet 组件扫描的注解, 来扫描过滤器配置的@WebFilter 注解, 扫描上之后, 过滤器在运行时就生效了。

@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功...");
    }
}


 






@ServletComponentScan 的作用:

​ 在 SpringBoot 项目中, 在引导类/配置类上加了该注解后, 会自动扫描项目中(当前包及其子包下)的@WebServlet , @WebFilter , @WebListener 注解, 自动注册 Servlet 的相关组件 ;