苍穹外卖项目开发学习中的问题

1.MD5加密数据库密码

对前端传过来的明文密码加密,调用spring提供的工具类,DigestUtil

//密码比对
// TODO 后期需要进行md5加密,然后再进行比对
//调用spring提供的工具类
password = DigestUtils.md5DigestAsHex(password.getBytes());
if (!password.equals(employee.getPassword())) {
    //密码错误
    throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}

新增功能时,就要将加密后的密码存进数据库!!

2.前后端分离流程

 3.swagger的Knief4j(MVC集成swagger)

介绍:

使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。官网: https://swagger.io/
Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案

1.导入Knief4j依赖

<dependency>
    <groupld>com.github.xiaoymin</groupld>
    <artifactld>knife4j-spring-boot-starter</artifactld>
    <version>3.0.2</version>
</dependency>

2.在配置类中添加配置Bean

     /**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("项目接口文档")
                .version("1.0")
                .description("项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

配置类:继承WebMvcConfigurationSupport类

3.设置静态资源映射,否则接口文档页面无法访问

     /**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

 4.访问端口加上 registry.addResourceHandler("/doc.html")中的url

http://localhost:8080/doc.html

看到页面,成功 

4.DTO的使用

如果前端提交的数据和实体类对应的属性差距较大时,建议使用DTO来精确封装

4.1当前端提交数据的属性比定义的实体类多(未多出的属性属于实体类),那么使用extends的方法来定义DTO;

作用:继承实体类的全部属性在另外加上多的属性,简化代码

4.2如果前端提交数据的属性少于实体类,那么就直接创建新的DTO类,实现Serializable接口

但若是提交的属性完全属于实体类那么也可以直接直接不创DTO,直接使用原来的实体类;

5.Nginx使用

使用nginx代理的,在访问之前一定要先打开nginx

6.LocalThread存储信息和Session存储信息

LocalThread是Java多线程编程中的一个概念。它是Java中的一个线程本地变量(Thread Local Variable),也被称为线程局部变量。每个线程都拥有自己独立的LocalThread变量副本,线程之间互不影响。通过LocalThread,开发人员可以在多线程环境下将数据与线程关联起来,以便在同一线程的不同方法之间进行共享。LocalThread通常用于在多线程环境下存储线程特定的上下文信息,如用户身份认证信息、请求跟踪ID等。

HttpSession是Java Servlet规范中的一部分,它用于在Web应用程序中跟踪用户会话状态。每个用户在与Web服务器建立会话时都会被分配一个唯一的HttpSession对象。通过HttpSession,开发人员可以在不同的HTTP请求之间存储和检索用户特定的数据,以便在整个会话期间进行共享。HttpSession通常用于存储用户的登录状态、购物车内容、用户首选项

存储ID信息

首先在拦截器解析出当前用户的ID信息,然后存入LocalThread

Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
//把ID信息存入Localhost
ThreadLocal<Long> threadLocal=new ThreadLocal<>();
threadLocal.set(empId);

此时就已经将ID存入了thread中,要用的时候在使用get方法拿出即可。

7.扩展springMVC的消息转换器 (转换日期格式)

作用:

扩展mvc的消息转换器,将后端传给前端的数据进行一个统一的处理

对日期对象进行处理列子,将这种时间转成时间格式

 在MVC的配置类中重写extendMessageConverters()方法,自定义一个消息转换器,并设置为0号索引位置

    /**
     *扩展mvc的消息转换器,将后端传给前端的数据进行一个统一的处理
     * @param converters
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //自定义消息转换器,转换日期格式,将日期对象转换成JSON对象
        //1、创建一个自定义消息转换器
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //2.在消息转换器中设置对象转换器,对象转换器可以将对象转换成json形式(对象转换器也是自定义的)
        converter.setObjectMapper(new JacksonObjectMapper());
        //3.将自定义消息转换器放入springMVC的消息转换器容器中,并设置索引
        converters.add(0,converter);
    }

 并写一个对象转换器,将日期类转成JSON格式

public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    //public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

重新运行代码,成功修改格式

 8.扩展springMVC的消息转换器(js对long型进行处理的精度丢失)

当我们对地址栏(Query参数)的Long型id进行取出时候,js对long型进行了"四舍五入"的操作,导致了精度的丢失。导致我们后端获取的数据和数据库中数据不一致。也就无法进行需要先查询的操作。

首先前端页面报错500

后端报错空指针表示我们查询不到,这就是代表我们后端得到的Long被精度丢失了

 java.lang.NullPointerException: null
    at com.sky.controller.admin.EmployeeController.updateStatus(EmployeeController.java:111) ~[classes/:na]

此时我们对咱们的对象转换器添加对long型转string的序列化。

完善对象转换器代码

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    //public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                
                //js精度丢失问题解决
                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance);
        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

9.使用MP改进使用mybatis的分类分页

MybatisPlus可以让我们不去写xml的sql语句,简化我们的sql开发。在分页上更是有着很大的简便。

1.在pom文件中导包


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

2.写MP配置类

/**
 * 利用MP来处理页面的分页查询
 */
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

3.使用MP

3.1mapper类继承BaseMapper

 3.2service继承IService接口

3.3实现类实现对应接口,继承 ServiceImpl类

 4.功能代码

    @GetMapping("/page")
    @ApiOperation("分类分页接口")
    public Result<Page> page(String name, int page, int pageSize, Integer type){
        Page pageInfo = new Page<>(page,pageSize);
        LambdaQueryWrapper<Category> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotEmpty(name),Category::getName,name);
        wrapper.eq(type != null,Category::getType,type);
        wrapper.orderByDesc(Category::getUpdateTime);
        categoryService.page(pageInfo,wrapper);
        return Result.success(pageInfo);
    }

10.自定义注解

注解的类型为@interface

@Target(ElementType.METHOD)//该注解作用于方法上
@Retention(RetentionPolicy.RUNTIME)

 类中的value是用来在注解上指定operationType

注解的使用:

   /**
     * 新增员工
     * @return
     */
    @PostMapping
    @ApiOperation("新增员工")
    @AutoFill(value = OperationType.INSERT)
    public Result<String> save(@RequestBody EmployeeDTO employeeDTO){
        employeeService.save(employeeDTO);
        return Result.success("");
    }

11.AOP注解开发公共功能 

自定义切面类

切面类中需要有切点通知

切点:

 前置通知及其处理逻辑(AOP通过反射来赋值)

    /**
     * 通知
     */
    @Before("autoFillpointcut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始公共字段自动填充");
        //获取被拦截方法上的数据库类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的注解对象
        OperationType operationType = autoFill.value();//获取数据库操作类型
        //获取到当前被拦截方法的参数————实体对象(约定:在这个注解下的方法,实体参数放在第一个)
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            return;
        }
        Object entity = args[0];

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作数据类型,为对应属性通过反射来赋值
        if (operationType == OperationType.INSERT) {
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射为对象赋值
                setCreateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else if(operationType == OperationType.UPDATE){
            //为两个字段赋值
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射为对象赋值
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

看到此类标志就说明该方法已经被切入

 11.文件(图片)上传

开发接口接收文件

 先创建接口方法,用springMVC封装的MultipartFile类来接收前端传来的file类文件,参数名必须是和前端传来的参数名字保持一致。 

 前端传来的文件名是file,我们的接口方法参数就是file

下一步进行文件上传(使用aliyun的oss服务,可以实现文件的上传及其回显)

详细步骤看博客

https://blog.csdn.net/qq_42807952/article/details/132105447

12.在Java中操作Redis

spring data redis的使用方式

导入maven坐标和配置数据源(操作的数据来自哪个Redis就要连接哪个数据库)

 创造模板对象(最重要的一步)

 maven导入

  <!--    spring data redis  的maven坐标    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

 配置数据源 

13.在@RestController中自定义bean的名称

在开发过程中遇到客户端和管理端类名相同注入到spring容器时,会造成bean的注入冲突

我们只需要自定义bean的名称即可,在@RestController注解中写上我们想要的bean的名称

客户端和管理端类名相同:

管理端

 用户端