苍穹外卖项目开发学习中的问题
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的名称
客户端和管理端类名相同:
管理端
用户端