文章目录
- 一、session注册时
- 二、用户增加时
- 三、RequestBody
- 3.1 Long问题
- 3.2 RequestBody
- 3.3 UpdataById
- 四、公共填充字段
- 五、文件上传与下载
- 5.1 拦截器与过滤器
一、session注册时
刚开始使用的是该代码
httpServletRequest.setAttribute("employee",emp.getId());
导致我点击登录后,还是一直在登录页面,进不去主页面。
原因是:
我使用的那行代码并没有保存到session里,只是保存到当前请求中。当请求别的页面时,就会失效。
正确代码:
httpServletRequest.getSession().setAttribute("employee", emp.getId());
该代码是保存到当前会话域。
移除的代码:
httpServletRequest.getSession().removeAttribute("employee");
获取代码:
httpServletRequest.getSession().getAttribute("employee")
当时还比较迷惑一个地方,就是 已经已经排除掉以下页面,就是以下页面直接放行。但是当我请求/backend/index.html 时并不会被直接放行,还是需要先登录,经过检查,
String[] urls=new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
原因是:
index.html页面 会直接默认请求一个页面,
请求该页面就会被过滤器拦截。
二、用户增加时
ControllerAdvice
注解给忘记了,通过该注解可以配置一个全局的信息,其中有一个属性是annotations,: 允许你指定一个或多个类(类型数组),只有继承或实现了这些类的控制器才会受到此 advice 影响。
用途:专门用于处理控制器层的全局异常处理、数据绑定和数据预处理。
应用范围:仅限于 Spring MVC 的控制器层。
原始代码: 使用的是ControllerAdvice
package com.cky.exceptions;
import com.cky.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* @ClassName MyExceptions
* @Description TODO
* @Author lukcy
* @Date 2024/6/21 15:51
* @Version 1.0
*/
@Slf4j
@ResponseBody
@ControllerAdvice(annotations = {RestController.class, Controller.class})
public class MyExceptions {
/**
* 异常处理方法
* @param ex
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> EmpExceptions(SQLIntegrityConstraintViolationException ex) {
if (ex.getMessage().contains("")) {
String[] split = ex.getMessage().split("");
String msg = split[2] + "已存在";
return R.error(msg);
}return R.error("未知错误");
}}
修改后
我没有使用该全局异常,而是在保存时先判断是否存在
public interface EmployeeService extends IService<Employee> {
boolean isUsernameExist(String username);
}
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
/**
* 检查数据库中是否存在指定的用户名
*
* @param username 要检查的用户名
* @return 如果存在返回true,否则返回false
*/
public boolean isUsernameExist(String username) {
QueryWrapper<Employee> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
int count = employeeMapper.selectCount(queryWrapper);
return count > 0;
}
}
@PostMapping
public R<String> saveemp(HttpServletRequest httpServletRequest,@RequestBody Employee employee){
boolean usernameExist = employeeService.isUsernameExist(employee.getUsername());
if (usernameExist){
return R.error(String.format("%s 用户名已经存在", employee.getUsername()));
}
//设置默认初始密码
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//其他字段
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
Long id = (Long) httpServletRequest.getSession().getAttribute("employee");
employee.setCreateUser(id);
employee.setUpdateUser(id);
//直接调用接口的save方法
employeeService.save(employee);
return R.success("员工添加成功");
}
能够实现同样的功能
更正
到后边,比如新增菜品或者套餐时,发现还是需要判断名字是否唯一,所以还是需要全局异常类更好些。
package com.cky.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* @ClassName GlobalExceptiobHandler
* @Description TODO
* @Author lukcy
* @Date 2024/6/25 9:56
* @Version 1.0
*/
@Slf4j
@ResponseBody
@ControllerAdvice(annotations = {RestController.class, Controller.class})
public class GlobalExceptiobHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> 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("未知错误");
}
}
三、RequestBody
在修改用户状态时:
3.1 Long问题
js 在对Long数据传送时,只会保证前16位有效,做法:增加一个自己的消息转换器+对象转换器,把Long数据转换为String。
package com.cky.common;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
/**
* 对象映射器:基于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_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(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.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);
}
}
package com.cky.config;
import com.cky.common.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.util.List;
/**
* @ClassName WebmvcConfig
* @Description TODO
* @Author lukcy
* @Date 2024/5/29 9:40
* @Version 1.0
*/
//默认能访问类路径下的static和template文件夹 这里我们没有放在这些文件夹下 所以访问不到,需要自己配置静态资源处理器
@Configuration
@Slf4j
public class WebmvcConfig extends WebMvcConfigurationSupport {
/**
* 静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
/**
* 增加消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("配置的消息转换器....");
MappingJackson2HttpMessageConverter mappingJackson2CborHttpMessageConverter=new MappingJackson2HttpMessageConverter();
mappingJackson2CborHttpMessageConverter.setObjectMapper(new JacksonObjectMapper());
converters.add(0,mappingJackson2CborHttpMessageConverter);
}
}
3.2 RequestBody
刚开始在控制器上没有加上@RequestBody注解,导致传入的参数并不能赋给Employee类。
RequestBody和RequestParam区别
:
@RequestBody会将请求体中的数据,转换成对象
@RequestParam会从http请求查询参数中提取数据
@RequestParam和@RequestBody是Spring Framework中用于处理HTTP请求的注解,它们有以下区别:
1.数据来源:
@RequestParam: 从HTTP请求的查询参数中提取数据,即从URL中的?key=value形式的参数中获取数据。
@RequestBody: 从HTTP请求的请求体(body)中提取数据,通常用于接收JSON、XML等格式的数据。
2.用法:
@RequestParam: 通常用于处理GET请求或POST请求中的表单数据,例如?name=John&age=30这样的查询参数。
@RequestBody: 通常用于处理POST请求中的非表单数据,例如JSON格式的数据,或者XML格式的数据。
3.数据格式:
@RequestParam: 提取的数据一般是简单类型,如字符串、整数等。
@RequestBody: 提取的数据可以是复杂类型,如自定义的Java对象、Map、List等,通常是用于反序列化JSON或XML数据为Java对象。
3.3 UpdataById
由于Mybatis plus默认的更新策略是NOT_NULL:非 NULL;即通过接口更新数据时数据为NULL值时将不更新进数据库。所以Mybatis plus通过updateById(XXX)更新数据,当用户有更新字段为 空字符串 或者 null 的需求时,需要对 FieldStrategy 策略进行调整。
FieldStrategy 有三种策略:
IGNORED:0 忽略
NOT_NULL:1 非 NULL,默认策略
NOT_EMPTY:2 非空
四、公共填充字段
比如employee中 创建人,修改人,创建时间,修改时间都属于公共字段,我们没必要写到controller中,可以通过mybatispuls的公共填充机制。
①实体类中
还有一个问题就是我们自己编写的配置类没有办法获取http的请求,所以id如何获得呢?
解决:
使用ThreadLocal
每次http请求都对应一个线程。
package com.cky.common;
/**
* @ClassName MythreadLocal
* @Description TODO
* @Author lukcy
* @Date 2024/6/25 9:21
* @Version 1.0
*/
/**
* 线程工具类
*/
public class MythreadLocal {
//因为存放的是id 所以是lONG
private static ThreadLocal<Long> threadLocal=new ThreadLocal<>();
public static void setCurrendId(Long id){
threadLocal.set(id);
}
public static Long getCurrendId(){
return threadLocal.get();
}
}
在filter中已经登录后在threadLocal中保存id,在我们编写的元数据处理配置类中获取id。
package com.cky.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* @ClassName MyMetaObjectHandler
* @Description TODO
* @Author lukcy
* @Date 2024/6/25 9:12
* @Version 1.0
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser", MythreadLocal.getCurrendId());
metaObject.setValue("updateUser",MythreadLocal.getCurrendId() );
}
@Override
public void updateFill(MetaObject metaObject) {
long id = Thread.currentThread().getId();
log.info("当前线程id{}",id);
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser",MythreadLocal.getCurrendId());
}
}
控制器中这些就可以省略了。
五、文件上传与下载
5.1 拦截器与过滤器
请求处理顺序总结
前端请求拦截器:在请求发出之前处理请求。
后端过滤器:在请求到达控制器之前进行预处理。
后端拦截器:在请求到达控制器之前进行进一步的处理。
控制器:处理请求并生成响应。
后端拦截器(响应阶段):在响应返回之前处理响应。
后端过滤器(响应阶段):在响应返回之前进行进一步的处理。
前端响应拦截器:在响应返回到客户端之前处理响应。
AJAX请求:前端拦截器(如axios拦截器)会处理请求和响应,然后请求会经过后端过滤器和拦截器,最后到达控制器。
非AJAX请求:不会经过前端拦截器,而是直接发送到服务器。请求会先经过后端过滤器和拦截器,最后到达控制器。
因此,对于非AJAX请求,前端的axios拦截器(request.use 和 response.use)将不会被触发。这些请求只会经过后端的过滤器和拦截器进行处理。
写上述是因为把upload.html 加入之后,返回的是200,相应内容
我在想,为什么这里也没有登录并且common/page 并没有在过滤器里配置,怎么没有返回到login页面。
原因:
因为emlment-upload 组件 是一个普通的http请求,该请求会经过我们的后端过滤器,所以返回了NOTLOGIN(我们在过滤器里面配置的),由于他不是一个ajax请求,所以并不会经过我们的前端拦截器的请求和相应。所以不会跳转到登录页面,我们可以看到登录页面是在前端拦截器的响应中写的。