【瑞吉外卖】瑞吉外卖项目笔记

Day1 瑞吉外卖项目概述

软件开发整体介绍

软件开发流程

角色分工

软件环境

软件开发流程

【瑞吉外卖】瑞吉外卖项目笔记_第1张图片

角色分工

【瑞吉外卖】瑞吉外卖项目笔记_第2张图片

软件环境

瑞吉外卖项目介绍

项目介绍

产品原型展示

技术选型

功能架构

角色

项目介绍

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

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

第一期主要实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问

第二期主要针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便

第三期主要针对系统进行优化升级,提高系统的访问性能

产品原型展示

产品原型,就是一款产品成型之前的一个简单的框架,就是将页面的排版布局展现出来,使产品的初步构思有一个可视化的展示。通过原型展示,可以更加直观的了解项目的需求和提供的功能。

注意:产品原型主要用于展示项目的功能,并不是最终的页面效果

技术选型

【瑞吉外卖】瑞吉外卖项目笔记_第3张图片

功能架构

【瑞吉外卖】瑞吉外卖项目笔记_第4张图片

角色

【瑞吉外卖】瑞吉外卖项目笔记_第5张图片

开发环境搭建

数据库环境搭建

maven项目搭建

数据库环境搭建

【瑞吉外卖】瑞吉外卖项目笔记_第6张图片【瑞吉外卖】瑞吉外卖项目笔记_第7张图片

 maven项目搭建

【瑞吉外卖】瑞吉外卖项目笔记_第8张图片

1) 配置依赖



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.7.10
         
    
    com.itheima
    regie_take_out
    1.0-SNAPSHOT

    
        17
        17
    
    

        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-starter-web
            compile
        

        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.2
        

        
            org.projectlombok
            lombok
            1.18.20
        

        
            com.alibaba
            fastjson
            1.2.76
        

        
            commons-lang
            commons-lang
            2.6
        

        
            mysql
            mysql-connector-java
            8.0.31
            runtime
        

        
            com.alibaba
            druid-spring-boot-starter
            1.1.23
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                2.4.5
            
        
    

2)application.yml配置

        spring.application.name是应用的名称,可选

server:
  port: 8080
spring:
  application:
    name: reggie_take_out
  datasource:
    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: root
mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

3)编写启动类

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

4)导入前端页面

因为backend和front不在静态资源目录(static和template目录 )下,所以会访问404,可以加以配置

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport{
    /*
    * 设置静态资源映射
    * */
    @Override
    protected void addResourceHandlers(ResourceHandleRegistry registry){
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }
}

备注:

@Configuration
public class MyWebMVCConfig implements WebMvcConfigurer {
    @Value("${file.location}") //         D:/test/ 
    String filelocation;  // 这两个是路径 
    @Value("${file.path}") //         /file/**
    String filepath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //匹配到resourceHandler,将URL映射至location,也就是本地文件夹
        registry.addResourceHandler(filepath).addResourceLocations("file:///" + filelocation);//这里最后一个/不能不写
    }
}

这段代码的意思就是配置一个拦截器,如果访问的路径是addResourceHandler中的filepath,就把它映射到本地的addResourceLocations的参数的这个路径上,这样就可以让别人访问服务器的本地文件了,比如本地图片、本地音乐视频等等。

访问路径http://localhost:8080/backend/index.html测试配置是否成功:

【瑞吉外卖】瑞吉外卖项目笔记_第9张图片

后台登录功能开发

需求分析

代码开发

功能测试

需求分析

只需要输入用户名和密码就可以登录成功

【瑞吉外卖】瑞吉外卖项目笔记_第10张图片

代码开发

1)创建实体类Employee,和employee表进行映射

/*
* 员工实体类
* */
@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; // 身份证号
    private Integer status;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;

}

备注:①其中@Data是lombok的注解,集成了以下注解:

  • @Getter
  • @Setter
  • @RequiredArgsConstructor
  • @ToString
  • @EqualsAndHashCode

②实现Serializable接口的作用

在程序中为了能直接以 Java 对象的形式进行保存,然后再重新得到该 Java 对象,这就需要序列化能力。

  • 提供一种简单又可扩展的对象保存恢复机制。
  • 对于远程调用,能方便对对象进行编码和解码,就像实现对象直接传输。
  • 可以将对象持久化到介质中,就像实现对象直接存储。
  • 允许对象自定义外部存储的格式。

实现序列化操作用于存储,一般针对于NoSql数据库

③@TableField字段填充策略

描述
DEFAULT 默认不处理
INSERT 插入填充字段
UPDATE 更新填充字段
INSERT_UPDATE 插入和更新填充字段

比如在进行插入操作时,会对添加了@TableField(fill = FieldFill.INSERT)的字段进行自动填充;再进行插入和更新操作时,会对添加了@TableField(fill = FieldFill.INSERT_UPDATE)的字段进行自动填充

2)实现后台登录的三层架构框架

第一步:创建包结构

【瑞吉外卖】瑞吉外卖项目笔记_第11张图片

第二步:创建Mapper

@Mapper
public interface EmployeeMapper extends BaseMapper {
}

第三步:创建Service及其实现类

public interface EmployeeService extends IService {
}

EmployeeServiceImpl需要实现ServiceImpl,泛型需要指定Mapper以及实体类

@Service
public class EmployeeServiceImpl extends ServiceImpl implements EmployeeService {
}

第四步:创建Controller

@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;
}

3)具体实现三层框架代码

第一步:开发返回结果类R

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


/*通用返回结果,服务端响应的数据最终都会封装成此对象
* */
@Data
public class R {
    private Integer code; // 编码:1表示成功,0和其他数字都表示失败
    private String msg; //错误信息
    private T data; //数据
    private Map map=new HashMap(); //动态数据

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

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

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


}

第二步:在Controller中创建登录方法

1.将页面提交的密码password进行md5加密处理

2.根据页面提交的用户名username查询数据库

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

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

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

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

【瑞吉外卖】瑞吉外卖项目笔记_第12张图片

@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;

    @PostMapping("/login")
    public R login(@RequestBody Employee employee, HttpServletRequest request){
        //1.将页面提交的密码password进行md5加密处理
        String password=employee.getPassword();
        password=DigestUtil.md5Hex(password);


        //2.根据页面提交的用户名username查询数据库
        LambdaQueryWrapper 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);
    }

}

登录成功以后,前端会使用localStorage存储用户信息

【瑞吉外卖】瑞吉外卖项目笔记_第13张图片

备注: ①QueryWrapper的用法

【瑞吉外卖】瑞吉外卖项目笔记_第14张图片

 此外,需要说明的是使用LambdaQueryWrapper,简化lambda使用,避免QueryWrapper的硬编码问题

也可以使用链式查询:

List bannerItems = new LambdaQueryChainWrapper<>(bannerItemMapper)
                        .eq(BannerItem::getBannerId, id)
                        .list();


BannerItem bannerItem = new LambdaQueryChainWrapper<>(bannerItemMapper)
                        .eq(BannerItem::getId, id)
                        .one();

②session.setAttribute方法解析

B/S架构中,客户端与服务器连接,在服务端就会自动创建一个session对象. session.setAttribute(“username”,username); 是将username保存在session中!session的key值为“username”value值就是username真实的值,或者引用值. 这样以后你可以通过session.getAttribute(“username”)的方法获得这个对象. 比如说,当用户已登录系统后你就在session中存储了一个用户信息对象,此后你可以随时从session中将这个对象取出来进行一些操作,比如进行身 份验证等等.

request.getSession()可以帮你得到HttpSession类型的对象,通常称之为session对象,session对 象的作用域为一次会话,通常浏览器不关闭,保存的值就不会消失,当然也会出现session超时。服务器里面可以设置session的超时时 间,web.xml中有一个session time out的地方,tomcat默认为30分钟

功能测试

分别测试用户名不存在、密码错误、账号已禁用以及登录成功的四个情况即可

后台退出功能开发

需求分析

 点击退出按钮,触发方法logout

【瑞吉外卖】瑞吉外卖项目笔记_第15张图片

此时会调用logoutApi,向/employee/logout发起一个post请求。然后处理返回结果,删除localStorage中存储的userInfo,跳转到login页面

【瑞吉外卖】瑞吉外卖项目笔记_第16张图片

 代码开发

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

我们只需要在Controller中创建对应的处理方法即可,具体的处理逻辑:

1.清理session中的用户id

2.返回结果

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

Day2 员工业务管理开发

完善登录功能

新增员工

员工信息分页查询

启用/禁用员工账号

编辑员工信息

完善登录功能

问题分析

前面我们已经完成了后台系统的员工登录功能开发,但是还存在一个问题:如果用户不登录,直接访问系统首页,照样可以正常访问。

这种设计并不合理,我们希望看到的效果是,只有登陆成功以后才可以访问系统中的页面,如果没有登陆则跳转到登录页面。

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

答案就是使用过滤器或者拦截器,在过滤器或者拦截器中判断用户是否已经完成登录,如果没有登陆则跳转到登录页面

过滤器实现步骤:

1.创建自定义过滤器LoginCheckFilter

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

2.在启动类上加上注解@ServletComponentScan

        加上注解@ServletComponentScan,才能扫描到@WebFilter注解

3.完善过滤器的处理逻辑

代码实现

1.获取本次请求的URI

2.判断本次请求是否需要处理

3.如果不需要处理,则直接放行

4.判断登录状态,如果已登录,则直接放行

5.如果未登录则返回登录结果

【瑞吉外卖】瑞吉外卖项目笔记_第17张图片

@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;
        HttpServletResponse response=(HttpServletResponse) servletResponse;
        //1.获取本次请求的UTI
        String requestURI = request.getRequestURI();
        //定义不需要处理的请求路径
        String[] urls=new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };
        //2.判断本次请求是否需要处理
        boolean check = checkURI(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;
    }

    //路径匹配,检查本次请求是否需要放行
    private boolean checkURI(String[] urls,String requestURI){
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if(match){
                return true;
            }
        }
        return false;
    }
}

新增员工

需求分析

数据模型

代码实现

功能测试

需求分析

点击添加员工就可以跳转到添加员工的页面

【瑞吉外卖】瑞吉外卖项目笔记_第18张图片

数据模型

新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。需要注意的是,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的

employee表中的status字段已经设置了默认值1,表示状态正常

代码开发

程序执行的过程:

1.页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端

2.服务端Controller接受页面提交的数据并调用Service将数据进行保存

3.Service调用Mapper操作数据库,保存数据

【瑞吉外卖】瑞吉外卖项目笔记_第19张图片

【瑞吉外卖】瑞吉外卖项目笔记_第20张图片

    /*
    * 新增员工
    * */
    @PostMapping
    public R save(HttpServletRequest request,@RequestBody Employee employee){
        employee.setPassword(DigestUtil.md5Hex("123456"));
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        //获得当前登录用户id
        Long empId = (Long)request.getSession().getAttribute("employee");
        employee.setCreateUser(empId);
        employee.setUpdateUser(empId);

        employeeService.save(employee);

        return R.success("新增员工成功!");
    }

编写全局异常

当新增员工时输入的账号已经存在,由于employee表中对该字段加入了唯一约束,因此程序会抛出异常“SQLIntegrityConstraintViolationException”

框架搭建:

/*
* 全局异常处理
* */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage()); //打印错误信息
        return R.error("失败");
    }
}

【瑞吉外卖】瑞吉外卖项目笔记_第21张图片

逻辑完善:

/*
* 全局异常处理
* */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage()); //打印错误信息
        if(ex.getMessage().contains("Duplicate entry")){
            String[] split = ex.getMessage().split(" ");
            String msg=split[2]+"已存在";
            return R.error(msg);
        }
        return R.error("未知错误");
    }
}

【瑞吉外卖】瑞吉外卖项目笔记_第22张图片

总结

【瑞吉外卖】瑞吉外卖项目笔记_第23张图片

员工信息分页查询

系统中的员工很多时,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统都会采用分页的方式来展示列表数据

流程梳理

1.页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端

2.服务端Controller接受页面提交的数据并调用Service查询数据

3.Service调用Mapper操作数据库,查询分页数据

4.Controller将查询到的分页数据响应给页面

5.页面接收到分页数据并通过ElementUI的Table组件展示到页面上

来到index页面,会自动发起员工分页查询

【瑞吉外卖】瑞吉外卖项目笔记_第24张图片

代码开发

第一步:配置分页拦截器

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

第二步:编写Controller

搭建框架:

    @GetMapping("/page")
    public R page(int page,int pageSize,String name){
        log.info("page={},pageSize={},name={}",page,pageSize,name);
        return null;
    }

【瑞吉外卖】瑞吉外卖项目笔记_第25张图片

逻辑编写:

    @GetMapping("/page")
    public R page(int page,int pageSize,String name){
        log.info("page={},pageSize={},name={}",page,pageSize,name);
        //构造分页构造器
        Page pageInfo = new Page(page, pageSize);
        //构造条件构造器
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        //添加过滤条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        //执行查询
        employeeService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

注意,如果StringUtils.isNotEmpty()找不到该方法,注意检查导包是否为:

import org.apache.commons.lang.StringUtils;

功能测试:

【瑞吉外卖】瑞吉外卖项目笔记_第26张图片

补充说明

页面返回的status是0或1,但是显示在页面上的却是已禁用或正常

这是因为使用了