请求返回类封装
package com.liu.reggie.common;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class R <T>{
private Integer code;
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;
}
}
使用
@GetMapping("/{id}")
public R<Employee> 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;
@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 class LoginCheckFilter implements Filter {
public static final 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();
log.info("拦截到请求:{}",requestURI);
String[] urls=new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
boolean check = check(urls, requestURI);
if (check){
log.info("本次请求{}不需要处理",requestURI);
filterChain.doFilter(request,response);
return;
}
if (request.getSession().getAttribute("employee") !=null){
log.info("用户已经登录,用户id为:{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
return;
}
log.info("用户未登录");
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
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;
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> 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值改变处理方法
mybatis Plus公共字段自动填充
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常用方法:
我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
实现步骤:
-
编写BaseContext工具类,基于ThreadLocal封装的工具类 package com.liu.reggie.common;
public class BaseContext {
private static ThreadLocal<Long> threadLocal=new ThreadLocal<>();
public static void setCurrentId(Long id){
threadLocal.set(id);
}
public static Long getCurrenId(){
return threadLocal.get();
}
}
-
在LogincheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
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);
}
}
-
在全局异常捕获中捕获返回前端
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> 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("未知错误!");
}
@ExceptionHandler(CustomException.class)
public R<String> 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<String> upload(MultipartFile file){
log.info(file.toString());
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
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));
System.out.println("自减1,再获取值: "+int1.decrementAndGet());
System.out.println("自增1,再获取值: "+int1.incrementAndGet());
System.out.println("先获取值,再自减1: "+int1.getAndDecrement());
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);
List<OrderDetail> orderDetails=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());
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对象中的值以整数返回。
|