瑞吉项目总结--针对后端技术点
ThreadLocal设置值
每次请求调用一个线程,我们可以在该线程中设置值,其底层相当于一个map,可以调用set和get方法,但是此处是静态对象,它只能存一个值,如下是其set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我们可以发现它是将自己作为建存入map,再将传过来的value作为值
具体操作:
1.创建
创建一个BaseContext类,内部创建一个静态对象ThreadLocal,并提供相对应的静态方法
public class BaseContext {
//创造一个静态的ThreadLocal对象,并声明泛型类
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
//存入key值
public static void setSessionId(Long id){
threadLocal.set(id);
}
//获取key值
public static Long getSessionId(){
return threadLocal.get();
}
}
2.1存入key值
因为每次请求都调用一个线程,那么我们可以在请求经过拦截器或过滤器的时候设置它的值
如下是一个登录过滤器,用于判断用户是否登录
public class LoginFilter implements Filter {
//路径匹配,也会匹配通配符
private static 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;
String requestURI = request.getRequestURI();
//不需要拦截的路径
String strs[] = new String[]{
"/backend/**",
"/front/**",
"/employee/login",
"/employee/logout",
"/user/login",
"/user/getcode"
};
boolean check = check(strs, requestURI);
if (check){
filterChain.doFilter(request,response);
return;
}
Object employeeId = request.getSession().getAttribute("employee");
Long userId = (Long) request.getSession().getAttribute("userID");
//登录成功
if (employeeId!=null){
//BaseContext存入key值
BaseContext.setSessionId((Long)employeeId);
filterChain.doFilter(request,response);
return;
}
if (userId!=null){
BaseContext.setSessionId(userId);
filterChain.doFilter(request,response);
return;
}
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}
/**
* 路径匹配,检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
2.2获取key值
在存入key值后,且在这次请求中,就可以获取key值了
如下是要将seesionid存入数据库中的公共字段处理器
public class CommonField implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("进入自动装入公共字段");
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
//存入key值
metaObject.setValue("createUser", BaseContext.getSessionId());
metaObject.setValue("updateUser", BaseContext.getSessionId());
}
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getSessionId());
}
}
登录过滤器LoginFilter
用于判断用户是否登录
//urlPatterns : 设置要拦截的请求,此处是拦截所有请求
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginFilter implements Filter {
//路径匹配,也会匹配通配符
private static 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;
String requestURI = request.getRequestURI();
//不需要拦截的路径
String strs[] = new String[]{
"/backend/**",
"/front/**",
"/employee/login",
"/employee/logout",
"/user/login",
"/user/getcode"
};
boolean check = check(strs, requestURI);
if (check){
//为true就放行
filterChain.doFilter(request,response);
return;
}
Object employeeId = request.getSession().getAttribute("employee");
Long userId = (Long) request.getSession().getAttribute("userID");
//登录成功
if (employeeId!=null){
//BaseContext存入key值
BaseContext.setSessionId((Long)employeeId);
filterChain.doFilter(request,response);
return;
}
if (userId!=null){
BaseContext.setSessionId(userId);
filterChain.doFilter(request,response);
return;
}
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}
/**
* 路径匹配,检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
公共字段自动填充
问题分析
在很多的对象中都有一些相同的字段,每次插入数据的时候都是必填,例如 创建日期,修改日期,创建人,修改人等等.
而手动填写很麻烦,我们就可以利用公共字段的技术实现每次插入或更新时自动填入
实现步骤
1、编写对象处理器,要实现MetaObjectHandler类,并且要使用@Component注解
@Component
@Slf4j
public class CommonField implements MetaObjectHandler {
/**
* 在插入时自动执行该方法
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("进入自动装入公共字段");
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
//存入key值
metaObject.setValue("createUser", BaseContext.getSessionId());
metaObject.setValue("updateUser", BaseContext.getSessionId());
}
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getSessionId());
}
}
2、在实体类的属性上加入@TableField注解,指定自动填充的策略。
例如员工类:
@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;
//在插入时执行FieldFill.INSERT方法
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
}
对象映射器
问题分析
当服务器将Long型数据传给前端页面时会造成精度缺失,此时就可以利用对象映射器处理该问题,(此处提供一种解决方法,核心就是将long型转为string类型)
步骤
1.编写JacksonObjectMapper类
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);
}
}
2.添加到WebMvcConfig 类中的extendMessageConverters方法中
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 添加我们自己的消息转换器,将我们自己的转换器放在最前面
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将我们自己的转换器放在最前面
converters.add(0,messageConverter);
}
}
全局异常处理器
针对某些特定的且常会产生的异常进行统一处理
//拦截annotations中,有标注这些注解的类或方法
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> handleException(SQLIntegrityConstraintViolationException ex){
//从报错信息中获得是否是我们想要的异常错误
if (ex.getMessage().contains("Duplicate entry")){
String s = ex.getMessage().split(" ")[2];
return R.error(s+"已经存在了");
}
return R.error("未知错误");
}
@ExceptionHandler(CustomerException.class)
public R<String> handleException2(CustomerException ex){
//从报错信息中获得是否是我们想要的异常错误
return R.error(ex.getMessage());
}
}
通用结果类R
将返回数据包装成类R返回给前端
@Data
public class R<T> implements Serializable {
//前端会根据code这一字段的值进行后续的操作
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
//前端所需要的数据
private T data; //数据
private Map map = new HashMap(); //动态数据
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
DTO(数据传输对象)
当前端所需要的数据不仅仅是对象中的字段,还需要一些对象之外的字段,这个时候就可以用dto扩展对象的字段
如下DishDto继承了Dish,还增加了一些其他的字段
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
分页插件
使用分页功能就要注册这个插件,是一个config类
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
配置静态资源路径映射
此操作就是让请求页面路径与我们自己的静态资源路径匹配
@Configuration
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/");
}
}
将前端传过来的xx对象转为xxDto对象
例如将dish对象转为dishdto,期间要新添一些字段
利用BeanUtils.copyProperties(source,dest)方法,先将dish中有的字段拷贝给dishdto,并且利用stream流对每个字段进行操作
dishDtos = list.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId, item.getId());
List<DishFlavor> list1 = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(list1);
return dishDto;
}).collect(Collectors.toList());