package com.liu.reggie.common; import lombok.Data; import java.util.HashMap; import java.util.Map; @Data public class R{ private Integer code; //编码:1成功,0和其它数字为失败 private String msg; //错误信息 private T data; //数据 private Map map = new HashMap(); //动态数据 public static R success(T object) { R r = new R (); r.data = object; r.code = 1; return r; } public static R error(String msg) { R r = new R(); r.msg = msg; r.code = 0; return r; } public R add(String key, Object value) { this.map.put(key, value); return this; } }
使用
@GetMapping("/{id}")
public R getById(@PathVariable Long id){
log.info("根据id查员工信息....");
Employee employee = employeeService.getById(id);
if (employee!=null){
return R.success(employee);
}
return R.error("没有查询到相对应的信息!");
}
Spring Boot 中使用 Servlet
在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。
-
使用 Filter 拦截器
-
在启动类里加上 @ServletComponentScan
package com.liu.reggie; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.transaction.annotation.EnableTransactionManagement; // 扫描 servlet 注解 @ServletComponentScan // 开启事务 @EnableTransactionManagement @SpringBootApplication public class ReggieTakeOutApplication { public static void main(String[] args) { SpringApplication.run(ReggieTakeOutApplication.class, args); } } -
启用@WebFilter进行路径拦截
package com.liu.reggie.filter; import com.alibaba.fastjson.JSON; import com.liu.reggie.common.R; import lombok.extern.slf4j.Slf4j; import org.springframework.util.AntPathMatcher; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Slf4j @WebFilter(filterName = "loginCheckFilter",urlPatterns = " public boolean check(String[] urls,String requestURL){ for (String url : urls) { boolean match = PATH_MATCHER.match(url, requestURL); if (match){ return true; } } return false; } }
-
每次都在方法里使用 try/catch 太麻烦了,使用全局异常拦截,通过报错的类型就行分类处理能大大的减少代码的,和提高编码效率
package com.liu.reggie.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;
// 拦截调用 RestController注解和Controller注解 抛出异常的方法
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.info(ex.getMessage());
if (ex.getMessage().contains("Duplicate entry")){
String[] s = ex.getMessage().split(" ");
String msg = s[2] + "已存在";
return R.error(msg);
}
return R.error("未知错误!");
}
}
Long类型id传输前端,id值改变处理方法
-
原因:由于后端long长度为19位,而前端接受整型的长度为16位,多余的3位会被js自动处理,导致id的改变,导致与数据库id对应不上
-
解决方法:
使用 fastjson 中间件 和 spring MVC 的 消息转换器 (以后有这个问题直接复制两个类就行了)
-
创建JacksonObjectMapper转换类
package com.liu.reggie.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; 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) // 将 BigInteger类型 向前端传输时转换成 String类型 .addSerializer(Long.class, ToStringSerializer.instance) // 将 long类型 向前端传输时转换成 String类型 .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); } } -
在spring MVC配置文件里加入自己的消息转换器
package com.liu.reggie.config; import com.liu.reggie.common.JacksonObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; 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; @Slf4j @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { log.info("开始进行静态资源映射"); registry.addResourceHandler("/backend @Override protected void extendMessageConverters(List> converters) { log.info("扩展消息转换器!"); // 创建消息转换器对象 MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); // 设置对象转换器,底层使用Jackson将Java对象转为json messageConverter.setObjectMapper(new JacksonObjectMapper()); // 将上面的消息转换器对象追加到mvc框架的转换器集合中 converters.add(0,messageConverter); } }
-
1、在实体类的属性上加入@TableField注解,指定自动填充的策略
package com.liu.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@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;
@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;
}
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口
因为MetaObjectHandler类里面拿不到 session 所以要想办法拿到 userId 就可以用 ThreadLocal 来获取
在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1、LoginCheckFilter的doFilter方法
2、EmployeeController的update方法
3、MyMetaObjectHandler的updateFill方法
可以在上面的三个方法中分别加入下面代码(获取当前线程id ) :
long id = Thread.currentThread().getId() ;
log.info("线程id:{}",id);
什么是ThreadLocal?
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
-
public void set(T value) 设置当前线程的线程局部变量的值
-
public T get() 返回当前线程所对应的线程局部变量的值
我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
实现步骤:
-
编写BaseContext工具类,基于ThreadLocal封装的工具类
package com.liu.reggie.common; public class BaseContext { private static ThreadLocalthreadLocal=new ThreadLocal<>(); public static void setCurrentId(Long id){ threadLocal.set(id); } public static Long getCurrenId(){ return threadLocal.get(); } } -
在LogincheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
//4-1、判断登录状态,如果已登录,则直接放行 if (request.getSession().getAttribute("employee") !=null){ log.info("用户已经登录,用户id为:{}",request.getSession().getAttribute("employee")); Long empId = (Long)request.getSession().getAttribute("employee"); BaseContext.setCurrentId(empId); long id = Thread.currentThread().getId() ; log.info("线程id:{}",id); filterChain.doFilter(request,response); return; } -
在 MyMetaObjectHandler 的方法中调用 BaseContext 获取登录用户的id
package com.liu.reggie.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.LocalDateTime; @Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("公共字段自动填充 [insert] ...."); log.info(metaObject.toString()); metaObject.setValue("createTime",LocalDateTime.now()); metaObject.setValue("updateTime",LocalDateTime.now()); metaObject.setValue("createUser",BaseContext.getCurrenId()); metaObject.setValue("updateUser",BaseContext.getCurrenId()); } @Override public void updateFill(MetaObject metaObject) { log.info("公共字段自动填充 [update] ...."); log.info(metaObject.toString()); long id = Thread.currentThread().getId() ; log.info("线程id:{}",id); metaObject.setValue("updateTime",LocalDateTime.now()); metaObject.setValue("updateUser",BaseContext.getCurrenId()); } }
-
创建 CustomException 自定义异常类 继承 RuntimeException 运行异常类,用于自定义异常消息
package com.liu.reggie.common; public class CustomException extends RuntimeException{ public CustomException(String message){ super(message); } } -
在全局异常捕获中捕获返回前端
// 拦截调用 RestController注解和Controller注解 抛出异常的方法 @ControllerAdvice(annotations = {RestController.class, Controller.class}) @ResponseBody @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(SQLIntegrityConstraintViolationException.class) public RexceptionHandler(SQLIntegrityConstraintViolationException ex){ log.info(ex.getMessage()); if (ex.getMessage().contains("Duplicate entry")){ String[] s = ex.getMessage().split(" "); String msg = s[2] + "已存在"; return R.error(msg); } return R.error("未知错误!"); } @ExceptionHandler(CustomException.class) public R exceptionHandler(CustomException ex){ log.info(ex.getMessage()); return R.error(ex.getMessage()); } }
spring-web 已经封装了,底层也是使用Apache的两个组件:
- commons-fileupload
- commons-io
就不用引入其他的包,只需要用MultipartFile接受文件流就行
application.yml配置文件里设置基础路径,方便文件转移
# 文件上传路径 web: upload-path: F:\images
CommonController控制器里
package com.liu.reggie.controller;
import com.liu.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.UUID;
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
@Value("${web.upload-path}")
private String filePath;
@PostMapping("/upload")
public R upload(MultipartFile file){
log.info(file.toString());
// 原始文件名
String originalFilename = file.getOriginalFilename();
// 截取文件后缀
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
// 使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
// 创建一个目录对象
File dir = new File(filePath);
if (!dir.exists()){
// 目录不存在需要创建
dir.mkdirs();
}
try {
file.transferTo(new File(filePath+fileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName);
}
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
// 输入流,通过输入流读取文件内容
try {
FileInputStream fileInputStream=new FileInputStream(new File(filePath+name));
// 输出流,通过输出流将文件写回浏览器,在浏览器展示图片
ServletOutputStream outputStream = response.getOutputStream();
response.setContentType("image/jpeg");
int len=0;
byte[] bytes=new byte[1024];
while ((len=fileInputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
outputStream.flush();
}
// 关闭资源
outputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
金额累加AtomicInteger类
i++和++i不是线程安全的,因此在高并发的情况下,需要使用synchronized等关键字来保证线程安全,但是AtomicInteger这个类则是线程安全的
常用方法public static void main(String[] args) {
AtomicInteger int1=new AtomicInteger();
System.out.println("AtomicInteger的默认值为:"+int1);
//对数据赋值
int1.set(123);
//获取数据值
System.out.println("获取数据的值为: "+int1.get());
//先相加,再获取值
System.out.println("先与12相加,再获取值: "+int1.addAndGet(12));
//先获取值,再相加
System.out.println("先获取值,再与12相加: "+int1.getAndAdd(12));
//先获取值,再赋新值
System.out.println("先获取值,再赋新值100: "+int1.getAndSet(100));
//自减1,再获取值
System.out.println("自减1,再获取值: "+int1.decrementAndGet());
//自增1,再获取值
System.out.println("自增1,再获取值: "+int1.incrementAndGet());
//先获取值,再自减1
System.out.println("先获取值,再自减1: "+int1.getAndDecrement());
//先获取值,再自增1
System.out.println("先获取值,再自增1: "+int1.getAndIncrement());
}
运行结果
AtomicInteger的默认值为:0 获取数据的值为: 123 先与12相加,再获取值: 135 先获取值,再与12相加: 135 先获取值,再赋新值100: 147 自减1,再获取值: 99 自增1,再获取值: 100 先获取值,再自减1: 100 先获取值,再自增1: 99使用AtomicInteger和int在高并发下的线程安全
public class Counter {
public static AtomicInteger count=new AtomicInteger();
public volatile static int countInt=0;
public static void increase(){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
countInt++;
count.getAndIncrement(); //自增
}
public static void main(String[] args) throws InterruptedException {
final CountDownLatch latch=new CountDownLatch(100);
for(int i=0;i<100;i++){
new Thread(new Runnable() {
@Override
public void run() {
Counter.increase();
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("运行结果:count: "+Counter.count+",countInt: "+countInt);
}
}
运行结果:
运行结果:count: 100,countInt: 99
使用AtomicInteger,即使不用同步锁synchronized,最后的结果也是100,可用看出AtomicInteger的作用,用原子方式更新的int值。主要用于在高并发环境下的高效程序处理。使用非阻塞算法来实现并发控制。
总结:
使用AtomicInteger是线程安全的,即使不使用synchronized关键字也能保证其是线程安全的。而且由于AtomicInteger由硬件提供原子操作指令实现,在非激烈竞争的情况下,开销更小,速度更快
项目中的简单使用// 线程安全的数量累加器 AtomicInteger amount = new AtomicInteger(0); // 计算总金额,封装订单明细 ListorderDetails=shoppingCarts.stream().map((item)->{ OrderDetail orderDetail = new OrderDetail(); orderDetail.setOrderId(orderId); orderDetail.setNumber(item.getNumber()); orderDetail.setDishFlavor(item.getDishFlavor()); orderDetail.setDishId(item.getDishId()); orderDetail.setSetmealId(item.getSetmealId()); orderDetail.setName(item.getName()); orderDetail.setImage(item.getImage()); orderDetail.setAmount(item.getAmount()); // 每一个订单详情中的 价格(item.getAmount()) 乘于(.multiply) 数量(item.getNumber()) amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue()); return orderDetail; }).collect(Collectors.toList());
BigDecimal说明:
-
简介:
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、"、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。
-
构造器描述:
BigDecimal(int) 创建一个具有参数所指定整数值的对象。
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 //不推荐使用
BigDecimal(long) 创建一个具有参数所指定长整数值的对象。
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。 //推荐使用
-
方法说明
add(BigDecimal) BigDecimal对象中的值相加,然后返回这个对象。
subtract(BigDecimal) BigDecimal对象中的值相减,然后返回这个对象。
multiply(BigDecimal) BigDecimal对象中的值相乘,然后返回这个对象。
divide(BigDecimal) BigDecimal对象中的值相除,然后返回这个对象。
toString() 将BigDecimal将对象的数值转换成字符串。
doubleValue() 将BigDecimal对象中的值以双精度数返回。
floatValue() 将BigDecimal对象中的值以单精度数返回。
longValue() 将BigDecimal对象中的值以长整数返回。
intValue() 将BigDecimal对象中的值以整数返回。



