登录界面

1.目前没有在线接口文档,所以需要通过前端网页F12 查询image.png
2.在前端login.html中image.pngdata为登录成功的员工信息,code==1表示登录成功,msg为登录错误的返回信息,在实体类中R类表示了这三个参数image.png,R类中封装了success和error两个静态方法。
3.0.登录:登陆流程.png
4.编写dao层:写mapper接口和mysql语句。
5.编写service层:service方法中写接口方法,impl中写业务逻辑;
校验密码是否正确用到了md5加密方法:DigestUtils.md5DigestAsHex(需要加密的明文)
image.png
6.image.pngimpl最终返回的是数据库中的员工信息,是因为返回一个完整的员工对象才表示你登录成功了。
7.编写控制层:image.png,通过image.png可以看到,url都是带employee的,所以@RequestMapping(“/employee”);看到是post请求,所以是@PostMapping(“/login”);
8.登录成功为什么要写入session问题image.png
session是一个会话内有效,距离下一次访问30分钟才会销毁,每次登录访问数据库和进行用户名、密码
、状态校验,每次这么做很花时间;那么把第一次校验的结果存下来,存到session里面,那么整个会话内 都有效,会话内就不用再校验,目的是减少校验(校验走的是数据库会影响性能),所以直接在方法参数中注 入一个session(spring支持bean的作用域session在DisparcherServlet已经将session存储在IoC容器中的);
同时前端传入的是image.pngjson对象,所以要加 @RequestBody将json数据转成java对象。
9.调用业务进行登录,获取登录结果对象;判断登录是否成功,登录成功后,要将登录数据员工ID写入session中image.png因为在页面右上角会显示员工ID——管理员;返回结果给前端。
10.要对mapper进行包扫描,不然mybatis没法对sql语句进行处理。在启动类中@MapperScan
11.断点调试

注销退出

image.png
根据前端代码,有一个logout点击事件,给前端数据返回code=1即表示退出成功,则会跳回login.html登录页面。关键在于注销session数据session.invalidate();
根据请求知道post请求,url为logout。

登录权限校验

拦截器与过滤器的区别:
1.拦截器是属于SpringMVC体系的,只能拦截controller的请求。
2.过滤器是属于servlet体系的,可以拦截所有的请求。
本次使用的是过滤器。
过滤器的思路:
首先我们要知道过滤器拦截的所有资源,获取本次请求的uri,判断本次请求是否需要处理和判断登录状态(是否登录)来决定要不要放行,若本次请求(如前端页面和登录页面)不需要处理就放行;已登录就放行;没登录就跳到登录页面。

用于路径匹配的AntPathMatcher工具类:
image.png

1.查看前端返回结果是怎么样的。image.png
前端的拦截器:判断后端是否登录成功,如果没有登录前端会跳转页面到登录页面。所有我们要返回code=0 和msg=NOTLOGIN到前端。
2.写一个类继承Filtier,注意要写注解拦截所有路径image.png
3.强转request,获取URI,用一个数组定义放行资源列表,遍历数组每个资源与URI进行校验,通过Spring框架提供的AntPathMatcher工具类,进行通配符匹配。
antPathMatcher.match(带有通配符的字符串,用户访问的路径字符串),匹配返回true,不匹配返回false。
匹配之后放行,记得要return,不要往下走。
4.登录检验,获取session及登录数据。
5.没有登录则根据前端要的数据image.png,自己构造有个R对象返回结果,记得要把R对象转成json数据才能给到前端。
6.最后在SpringBoot启动类要扫描到这个过滤器。
@ServletComponentScan//扫描所有Web原生技术注解 @WebServlet,@WebFilter,@WebListener等原生注解

新增员工

image.png
1.找到页面请求的数据,image.png
image.png,找到页面的代码在哪里add.html。
2.image.png,可以看到只需要返回数据code和msg以及添加的数据data就可以了。
3.编写dao层,写sql语句。
4.编写Service层,返回值为通用String类型,因为给到前端的数据只需要cod和msg。
5.编写impl,首先检验添加的这个员工对象是否已经存在,存在就直接输入数据给到前端;不存在就补全数据,设置初始密码(DigestUtils.md5DigestAsHex(“123456”.getBytes()));获取当前时间信息(LocalDateTime.now());添加完成后把整个employee对象add到容器;并返回通用R类传送添加成功的数据到前端。
6.编写控制层:因为要获取当前操作人的信息,使用参数要注入session;一般session放在控制层来处理;从session中获取操作人的信息,补全到employee对象。

员工分页查询

分页技术原理sql语句:
前端传递给后端2个数据:当前页码page和每页大小pageSize;
后端需要查询的数据:总条数和当前页数据列表。

这个用到了PageHelper,mybatis的组件,用于简化分页开发的,只需要开发人员写一条sql语句,这个组件会拦截这个sql语句生成2条sql语句,开发人员只需要写查询所有数据的一条sql语句即可。
PageHelper固定的三个步骤:
1.告诉PageHelper插件当前页码和每页大小
PageHelper.startPage(page,pageSize);
2.调用mapper查询所有数据
3.将所有数据给到PageInfo对象(PageHelper提供的类),里面有总条数,当前页数据列表
PageInfo pageInfo = new PageInfo<>(mapper查询的所有数据);
①需要导入分页插件的依赖:


com.github.pagehelper
pagehelper-spring-boot-starter
1.4.1

②在application.yml文件配置信息:

  1. pagehelper:
  2. #使用的数据库
  3. helper-dialect: mysql
  4. #合理化分页
  5. reasonable: true

总体思路
image.png
image.png
1.编写dao层:返回值是多个员工的数据列表,所以用list集合;传入的参数是String类型的name,根据前端页面传给后端的数据确定。image.png写动态sql语句参数要写别名,pojo类对象就不用起别名。
2.因为要写动态sql语句,所以要在resources文件下创建目录,注意这个创建目录要有/分开image.png它的底层是四个目录;然后新建映射文件XXXmapper.xml的模板,
image.png这个要写dao层接口的全名。
3.这里image.png起别名才能拿到name;
concat(字符串1,字符串2,…)用于拼接多个字符串。
4.编写service层:image.png
image.png
定义一个实体类里面包含了页数信息,员工数据信息。
返回的是通用结果类型R>,其中R中包含了前端要的数据code,Page包含了前端要的数据total,Employee包含了前端要的当前页数据列表属性。
5.编写impl实现类:使用PageHelper分页,三个步骤;根据实体类中的Page封装Page对象-Pageobj,根据pageInfo中存储的数据给到Page对象-Pageobj,然后返回Page对象-Pageobj数据。

6.编写控制层:根据传入的地址写参数image.png
image.png

启用/禁用员工账号

后端需要的数据:点击启用/禁用按钮,浏览器给到的数据是:image.png
后端返回的数据:前端代码只需要image.png返回code,所以只需要返回通用结果R

精度丢失问题

普通数据类型过长时会出现精度丢失的问题。

对象映射器:基于jackson将java对象转为json,或者json转为java对象
将JSON解析为Java对象的过程称为【从JSON反序列化Javaduix】
从Java对象生成JSON的过程称为【序列化Java对象到JSON】

反序列化时,属性不存在兼容处理,所以只需要在后端传到前端时处理精度丢失的问题。

处理步骤:
1.在config包下 引入JacksonObjectMapper

  1. package com.itheima.reggie.config;
  2. import com.fasterxml.jackson.databind.DeserializationFeature;
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import com.fasterxml.jackson.databind.module.SimpleModule;
  5. import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
  6. import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
  7. import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
  8. import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
  9. import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
  10. import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
  11. import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
  12. import java.math.BigInteger;
  13. import java.time.LocalDate;
  14. import java.time.LocalDateTime;
  15. import java.time.LocalTime;
  16. import java.time.format.DateTimeFormatter;
  17. import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
  18. /**
  19. * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
  20. * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
  21. * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
  22. */
  23. public class JacksonObjectMapper extends ObjectMapper {
  24. public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
  25. public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
  26. public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
  27. public JacksonObjectMapper() {
  28. super();
  29. //收到未知属性时不报异常
  30. this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
  31. //反序列化时,属性不存在的兼容处理
  32. this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
  33. SimpleModule simpleModule = new SimpleModule()
  34. .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
  35. .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
  36. .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
  37. .addSerializer(BigInteger.class, ToStringSerializer.instance)
  38. .addSerializer(Long.class, ToStringSerializer.instance)
  39. .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
  40. .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
  41. .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
  42. //注册功能模块 例如,可以添加自定义序列化器和反序列化器
  43. this.registerModule(simpleModule);
  44. }
  45. }

2.在WebMvcConfig中重写方法extendMessageConverters

/**
 * 扩展mvc框架的消息转换器
 * @param converters
 */
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    log.info("扩展消息转换器...");
    //创建消息转换器对象
    MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
    //设置对象转换器,底层使用Jackson将Java对象转为json
    messageConverter.setObjectMapper(new JacksonObjectMapper());
    //将上面的消息转换器对象追加到mvc框架的转换器集合中
    converters.add(0,messageConverter);
}

编辑员工数据回显

image.png
image.png
根据浏览器的数据得知编辑员工信息和启动状态请求的是同一个页码,用的是前端add.html同一个页面。
所以编辑员工信息的代码跟修改员工状态的代码共用,这里只需要通过浏览器传来的id数据进行查询回显给到前端就可以了。

新增分类

写两个mapper,一个做添加,一个通过name进行查询,查询判断添加的类型是否已经存在。
注意加飘号image.png

分类信息分页查询

用pageHelper进行分页查询,写sql语句的时候只需要传入参数为当前页和当前页大小。
image.png这里是两层地址,记得在控制层@GETMapper的时候要写上地址。

删除分类

写两个mapper:image.png
image.png
用于查询需要删除的分类模块是否存在关联数据,当存在关联数据则不删除。这里注意sql语句中的count()返回的是int类型,而是返回List集合类型。
当检测到没有关联的数据的时候才通过id来删除。

ApachePOI——excel导入导出

Apache POI功能:
数据报表生成,数据批量上传,数据备份等工作

ApachePOI 入门案例

首先需要导入excel POI依赖

<!--excel  POI依赖-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.0.1</version>
        </dependency>

XXX.xls——2003版本excel XXX.xlsx——2007版本及以上

SpringBoot测试类的注意事项:
bbddc9e6db6910d3a35980424d1f6c3.png如果不是相同包下需要指明入口类,相同包下及其子包下不用指定。

读取03版本的excel一行数据

image.png

到excel表格的所有行数

int rows = sheet.getPhysicalNumberOfRows();——获取有数据的行数
承接上面代码

 //得到excel表格的所有行数
        int rows = sheet.getPhysicalNumberOfRows();
        //遍历读取其他行的数据
        for (int i = 1; i < rows; i++) {
            //得到行
            row = sheet.getRow(i);
            //输出单元格的数据
            System.out.print(row.getCell(1).getStringCellValue() + "\t");
            System.out.print(row.getCell(2).getStringCellValue() + "\t");
            System.out.print(row.getCell(3).getNumericCellValue() + "\t");
            System.out.print(row.getCell(4).getStringCellValue() + "\t");
            System.out.print(row.getCell(5).getNumericCellValue() + "\t");
            System.out.print(row.getCell(6).getNumericCellValue() + "\t");
            System.out.print(row.getCell(7).getNumericCellValue() + "\t");
            System.out.print(row.getCell(8).getStringCellValue() + "\t");
            System.out.println(row.getCell(9).getStringCellValue());
        }

03和07版本的区别只在一个地方:
image.png
注意:03版本的excel对应的实现类HSSFWorkbook,07版本的excel对应的实现类XSSFWorkbook

导出excel文件

生成excel文件

 //生成excel文件
    @Test
    public void  createExcel() throws Exception {
        //1. 创建工作薄
        Workbook workbook = new XSSFWorkbook();
        //2. 创建工作单
        Sheet sheet = workbook.createSheet("最帅男生表");
        //3. 创建行
        Row row = sheet.createRow(0);
        //4. 创建单元格并设置内容
        row.createCell(0).setCellValue("姓名");
        row.createCell(1).setCellValue("年龄");
        row.createCell(2).setCellValue("身高");
        row.createCell(3).setCellValue("收入");
        row.createCell(4).setCellValue("人品");
        //5. 把工作薄写出
        workbook.write(new FileOutputStream("d:/contact.xlsx"));
    }

使用excel模板去生成excel的数据

//使用excel模板去生成excel的数据
    @Test
    public void  createBaseExcel() throws Exception {
        //1. 读取模板的输入流
        InputStream inputStream = POITest.class.getResourceAsStream("/excel/users.xlsx");
        //2. 基于模板的输入流创建工作薄
        Workbook workbook = new XSSFWorkbook(inputStream);
        //3. 得到模板的工作单
        Sheet sheet = workbook.getSheetAt(0);
        //4. 得到索引值为1这个行的数据
        Row row = sheet.getRow(1);
        //5. 模板导出最为关键的一步:提取模板的样式存储集合中
        List<CellStyle> styleList =new ArrayList<>();
        //六个单元格,所有我们需要提取6个样式出来
        for (int i = 1; i <=6 ; i++) {
            Cell cell = row.getCell(i);//得到单元格
            styleList.add(cell.getCellStyle());
        }
        //6. 创建行,创建单元格去设置样式.写入内容
        row = sheet.createRow(1);
        //设置单元格的内容与样式
        //姓名
        Cell cell = row.createCell(1);
        cell.setCellValue("itheima");
        cell.setCellStyle(styleList.get(0));
        //密码
        cell = row.createCell(2);
        cell.setCellValue("123456");
        cell.setCellStyle(styleList.get(1));
        //姓名
        cell = row.createCell(3);
        cell.setCellValue("播仔");
        cell.setCellStyle(styleList.get(2));
        //年龄
        cell = row.createCell(4);
        cell.setCellValue(18);
        cell.setCellStyle(styleList.get(3));
        //邮箱
        cell = row.createCell(5);
        cell.setCellValue("bozai@itcast.cn");
        cell.setCellStyle(styleList.get(4));
        //性别
        cell = row.createCell(6);
        cell.setCellValue("男");
        cell.setCellStyle(styleList.get(5));
        //把表格写出
        workbook.write(new FileOutputStream("D:/person.xlsx"));
    }

效果:
image.png

导出类别信息

在瑞吉外卖平台导出分类信息,导出数据生成excel文件,通过响应流导出给客户端浏览器,response.getOutputStream()。
点击image.pngimage.png

同样是三层架构:
mapper:image.png
Service:image.png
ServiceImpl:image.png
控制层:

 @GetMapping("/exportExcel")
    public void exxportExcel(HttpServletResponse response) throws IOException {
        //1.设置响应头,输出文件,让浏览器下载
        //响应头:content-disposition  设置让浏览器下载附件
        //attachment 附件下载
        //filename=category.xlsx 设置浏览器下载文件的名字
        response.setHeader("content-disposition","attachment;filename=category.xlsx");
        //1. 读取模板的输入流
        InputStream inputStream = CategoryController.class.getResourceAsStream("/excel/category.xlsx");
        //2.使用模板的输入流构建一个工作薄
        Workbook workbook = new XSSFWorkbook(inputStream);//这里需要抛出异常的
        //3. 得到工作单
        Sheet sheet = workbook.getSheetAt(0);
        //4. 读取第一行,提取第一行的样式,并且存储到集合中
        Row row = sheet.getRow(1);
        List<CellStyle> cellStylesList= new ArrayList<>();
        //5个单元格,所以我们需要提取5个样式出来
        for (int i = 0; i < 5; i++) {
            Cell cell=row.getCell(i); //得到单元格
            cellStylesList.add(cell.getCellStyle());
        }
        //5. 得到数据,遍历数据,把数据写入到excel
        List<Category> categoryList = categoryService.findAll();
        //遍历每一个分类对象,输出到excel中
        for (int i = 0; i < categoryList.size(); i++) {
            //一个类别就对应一行
            row =  sheet.createRow(i+1);
            //获取每一个分类对象数据
            Category category = categoryList.get(i);
            //把类别信息设置到单元格上
            //类别类型
            Cell cell = row.createCell(0);
            //设置单元格内容
            cell.setCellValue(category.getType());
            cell.setCellStyle(cellStylesList.get(0));
            //类别名字
            cell = row.createCell(1);
            //设置单元格内容
            cell.setCellValue(category.getName());
            cell.setCellStyle(cellStylesList.get(1));
            //类别排序
            cell = row.createCell(2);
            //设置单元格内容
            cell.setCellValue(category.getSort());
            cell.setCellStyle(cellStylesList.get(2));
            //创建时间
            cell = row.createCell(3);
            //设置单元格内容
            cell.setCellValue(category.getCreateTime().toLocalDate().toString());
            cell.setCellStyle(cellStylesList.get(3));
            //创建时间
            cell = row.createCell(4);
            //设置单元格内容
            cell.setCellValue(category.getUpdateTime().toLocalDate().toString());
            cell.setCellStyle(cellStylesList.get(4));
        }
        //6. 把工作薄写出,把表格输出客户端浏览器,使用response输出流
        workbook.write(response.getOutputStream());
    }

修改菜品信息

文件上传和下载

01.文件上传与下载实现分析.png
1.文件上传与下载.png

@RestController
@RequestMapping("/common")
public class CommonController {
    //读取配置文件中上传文件的目录
    @Value("${reggie.path}")
    private String baseDir;

    //SpringMVC文件上传要求,使用MultipartFile类型接收上传文件对象,
    //MultipartFile类型参数名字必须与前端文件域表单元素name属性值一样,
    //如果不一样需要使用@RequestParm("别名")
    @RequestMapping("/upload")
    public R<String> upload(MultipartFile file) throws IOException {
        //1.获取上传文件的文件名
        String filename = file.getOriginalFilename();
            //1.1获取文件扩展名
        String fileExtName = filename.substring(filename.lastIndexOf("."));
            //2.1创建唯一随机的新的文件名
        String newFileName = UUIDUtils.getUUID() + fileExtName;
        //2.将文件保存到d:\img\目录下
        File dir = new File(baseDir);
        if (!dir.exists()){
            dir.mkdir();//这里使用前提必须有d盘,这里在判断如果img目录没有则创建
        }
        //进行保存
        file.transferTo(new File(baseDir+newFileName));
        return R.success(newFileName);
    }

    @RequestMapping("/download")
    public void download(@RequestParam("name") String fileName, HttpServletResponse response) throws IOException {
        //输入流
        InputStream inputStream=new FileInputStream(baseDir+fileName);
        //输出流
        ServletOutputStream outputStream = response.getOutputStream();
        //将输入流输出给输出流
        IOUtils.copy(inputStream,outputStream);
    }
}

登录验证

案例6 登录校验-代码实现.png