Lombok提供了一个注解@Slf4j可以直接使用log变量log.info输出日志,方便代码调试
静态资源不一定要放在static,template文件夹里面,可以通过配置类的方式来设置静态资源的映射,告诉框架,指定的目录就是静态资源。
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
//设置静态资源映射
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:front/");
}
}
如果前端表单传送过来的是json格式的数据,后端对应的Controller接收参数需要加上注解@RequestBody,通过对象封装,实体类的属性要和前端参数名一致。
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee::getUsername的意思就相当于:
1.实例化一个Employee对象
Employee employee = new Employee()
2.调用对象employee的get方法,这里调用的是getUsername
employee.getUsername();
@Controller与@RestController的区别
@Controller注解Controller,Controller中的方法能够返回jsp页面,视图解析器可以解析return的jsp,html页面,并且跳转到相应页面若要返回数据到页面,需要用model,或者加上@ResponseBody注解。
@ResponseBody能够把方法返回的结果封装成json数据返回
@RestController注解Controller,相当于@ResponseBody+@Controller合在一起的作用,Controller中的方法无法返回jsp页面,视图解析器无法解析,返回的内容就是返回到页面的数据。
在SpringBoot项目中如果用过滤器的方式拦截页面
首先定义好过滤器,加上注解@WebFilter
/**
* 检查用户是否已经完成登录
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
log.info("拦截到请求:{}",request.getRequestURI());
filterChain.doFilter(request,response);
}
}
要让这个过滤器生效,需要在启动类上加一个注解@ServletComponentScan,才能扫描到@WebFilter
@Slf4j
@ServletComponentScan
@SpringBootApplication
public class ReggieTakeOutApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieTakeOutApplication.class, args);
log.info("项目启动成功...");
}
}
路径匹配器,支持通配符
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
String requestURI = request.getRequestURI();//backend/index.html
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
boolean match = PATH_MATCHER.match(url,requestURI);
通过输出流的方式向页面响应JSON数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
字段索引类型unique,表示这个字段不允许重复
如果插入了相同的字段就会抛异常
处理这个异常有两种处理方式:
1.在Controller方法中加入try、catch进行异常捕获
try{
employeeService.save(employee);
}catch(Exception ex){
return R.error("新增员工失败");
}
2.使用异常处理器进行全局异常捕获(建议使用)
/**
* 全局异常处理
*/
//@ControllerAdvice用来指定拦截哪些Controller
@ControllerAdvice(annotations = {RestController.class, Controller.class})
//@ResponseBody能够把方法返回的结果封装成json数据返回
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @param ex
* @return
*/
//@ExceptionHandler用来指定方法用来处理哪些异常
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
return R.error("用户名已存在");
}
}
注意:
@ControllerAdvice用来指定拦截哪些Controller
@ResponseBody能够把方法返回的结果封装成json数据返回
@ExceptionHandler用来指定方法处理哪些异常(这里处理的是数据库抛出来的异常)
再优化一下这个异常处理器
/**
* 全局异常处理
*/
//@ControllerAdvice用来指定拦截哪些Controller
@ControllerAdvice(annotations = {RestController.class, Controller.class})
//最终的方法数据需要返回json数据,@ResponseBody能够把结果封装成json数据返回
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @param ex
* @return
*/
//@ExceptionHandler用来指定方法用来处理哪些异常
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R 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("未知错误");
}
}
MybatisPlus分页插件
/**
* 配置MP的分页插件
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
/**
* 员工信息分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R page(int page, int pageSize,String name){
log.info("page = {},pageSize = {},name = {}",page,pageSize,name);
//构造分页构造器
Page pageInfo = new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
//添加过滤条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
注意:前端将json格式的数据传给后端,后端Controller可以直接接收对应的参数。
@RequestBody的作用是将前端json格式的数据转为java对象。前端的json数据会自动匹配到后端接收对象的属性中了,当然属性名称要一样。
前端JS对Long型id处理的时候会丢失精度,只能保证前16位数字是精确的,后面的数字做四舍五入。
如何解决这个问题?
可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串
具体实现步骤:
1.提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换
/**
* 对象映射器:基于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);
}
}
注意:
把Long型数据转换成了字符串
把日期格式也进行了更改
2.在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换
/**
* 扩展mvc框架的消息转换器
* 项目启动的时候就会调用
* @param converters
*/
@Override
protected void extendMessageConverters(List> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//converters包含了Springmvc默认的几个消息转换器
//追加自己的消息转换器,将上面的消息转换器对象追加到mvc框架的转换器集合中,设置成优先使用
converters.add(0,messageConverter);
}
注意:
项目启动的时候就会调用这个消息转换器
追加自己的消息转换器,将上面的消息转换器对象追加到mvc框架的转换器集合中,设置成优先使用
公共字段自动填充自定义的元数据对象处理器MetaObjectHandler,现在自动填充的createUser和updateUser设置的用户id是固定值,需要改造成动态获取当前登录用户的id。
/**
* 自定义元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入操作,自动填充
* @param metaObject
*/
@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",new Long(1));//这里无法直接获取到当前用户request Session对象
metaObject.setValue("updateUser",new Long(1));
}
/**
* 更新操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充update...");
log.info(metaObject.toString());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",new Long(1));
}
}
注意:在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以需要通过其他方式来获取登录用户id,可以使用ThreadLocal来解决此问题,它是JDK中提供的一个类。
ThreadLocal
在学习ThreadLocal之前,需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1.LoginCheckFilter的doFilter方法
2.EmployeeController的update方法
3.MyMetaObjectHandler的updateFill方法
可以在上面的三个方法中分别加入下面代码(获取当前线程id):
Long id = Thread.currentThread().getId();
log.info("线程id:{}",id);
执行编辑员工功能进行验证,通过观察控制台输出可以发现,一次请求对应的线程id是相同的:
什么是ThreadLocal?
ThreadLocal并不是Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
实现步骤:
1.编写BaseContext工具类,基于ThreadLocal封装的工具类
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
2.在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
//4.判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee")!=null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
log.info("线程id为:{}",Thread.currentThread().getId());
filterChain.doFilter(request,response);
return;
}
3.在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
/**
* 自定义元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入操作,自动填充
* @param metaObject
*/
@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.getCurrentId());//这里无法直接获取到当前用户request Session对象
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
/**
* 更新操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充update...");
log.info(metaObject.toString());
log.info("线程id为:{}",Thread.currentThread().getId());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
}
自定义业务异常类
/**
* 自定义业务异常类
*/
public class CustomException extends RuntimeException{
public CustomException(String message){
super(message);
}
}
需要的时候可以直接抛出这个异常
throw new CustomException("菜品正在售卖中,不能删除");
全局异常处理GlobalExceptionHandler处理这个异常
@ExceptionHandler(CustomException.class)//处理数据库抛出的异常
public R exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
}
文件上传
文件上传时,对页面的form表单有如下要求:
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件
/**
* 文件上传和下载
*/
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R upload(MultipartFile file){//参数名一定要与FormData的name的值保持一致
log.info(file.toString());
return null;
}
}
注意:
MultipartFile file参数名一定要与FormData的name的值保持一致,不然上传不了为null
file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
文件下载
通过浏览器进行文件下载,通常由两种表现形式:
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程
文件上传
注意:图片上传成功后会自动发送下载请求,下载成功后图片才会显示成功
/**
* 文件上传和下载
*/
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
@Value("${reggie.path}")
private String basePath;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R upload(MultipartFile file){//参数名一定要与FormData的name的值保持一致
//file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
log.info(file.toString());
//原始文件名
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));//.jpg
//使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
//创建一个目录对象
File dir = new File(basePath);
//判断当前目录是否存在
if(!dir.exists()){
//目录不存在,需要创建
dir.mkdirs();
}
try {
//将临时文件转存到指定位置
file.transferTo(new File(basePath + fileName));
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName);
}
/**
* 文件下载
* @param name
* @param response
* @return
*/
@GetMapping("/download")
public void download(String name, HttpServletResponse response){
try {
//输入流,通过输入流读取文件内容
FileInputStream fileInputStream = new FileInputStream(new File(basePath + 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();
}
}
}
@Value的用法
1.@Value("${}")从配置文件读取值,也就是从application.yml文件中获取值
reggie:
path: C:\Users\83825\Desktop\test\
@Value("${reggie.path}")
private String basePath;
2.@Value("")常量注入
3.@Value("#{}")获取bean属性,系统属性,表达式
前端传过来的Json数据有flavors属性,但实体类Dish没有该属性字段
前端Json数据属性与后端实体类属性不对应的情况,可以通过DTO解决
DTO,全程Data Transfer Object,即数据传输对象,一般用于展示层与服务层之间的数据传输
前端数据,封装页面提交的数据
@Data
public class DishDto extends Dish {
private List flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
涉及多张表的操作需要加入事务控制@Transactional,保证数据的一致性
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品,同时保存对应的口味数据
* @param dishDto
*/
@Override
@Transactional
public void saveWithFlavor(DishDto dishDto) {
//保存菜品的基本信息到菜品表dish
this.save(dishDto);
Long dishId = dishDto.getId();//菜品id
//菜品口味
List flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item)->{
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
//保存菜品口味数据到菜品口味表dish_flavor
dishFlavorService.saveBatch(flavors);
}
}
要让@Transactional需要在启动类上开启事务的支持@EnableTransactionManagement
@Slf4j
@ServletComponentScan
@SpringBootApplication
@EnableTransactionManagement
public class ReggieTakeOutApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieTakeOutApplication.class, args);
log.info("项目启动成功...");
}
}
对象拷贝BeanUtils.copyProperties(),把一个对象的属性值拷贝到另一个对象对应的属性里面去,可以设置忽略的属性。
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
遇到问题:因循环引用导致启动时报错,项目启动失败。
报错原因:因循环引用导致启动时报错:SetmealServiceImpl引入了自己的SetmealService导致了循环调用。依赖循环引用是不鼓励的,默认情况下是禁止的。
public class SetmealServiceImpl extends ServiceImpl implements SetmealService {
@Autowired
SetmealDishService setmealDishService;
@Autowired
SetmealService setmealService;
}
解决方法1:
不通过这种方式调用SetmealService,可以通过this的方式调用。
解决方法2:
更新应用程序以删除bean之间的依赖循环。作为最后的手段,可以通过设置spring.main来自动打破循环。
spring:
main:
allow-circular-references: true
RequestParam和PathVariable的区别
短信发送
短信服务介绍
阿里云短信服务
代码开发
使用阿里云短信服务发送短信,可以参照官方提供的文档即可。
官方文档: https://help.aliyun.com/document_detail/112148.html
1.导入maven坐标
com.aliyun
aliyun-java-sdk-core
4.5.16
com.aliyun
aliyun-java-sdk-dysmsapi
1.1.0
2.调用API
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.google.gson.Gson;
import java.util.*;
import com.aliyuncs.dysmsapi.model.v20170525.*;
public class SendSms {
public static void main(String[] args) {
DefaultProfile profile = DefaultProfile.getProfile("cn-beijing", "", "");
/** use STS Token
DefaultProfile profile = DefaultProfile.getProfile(
"", // The region ID
"", // The AccessKey ID of the RAM account
"", // The AccessKey Secret of the RAM account
""); // STS Token
**/
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers("1368846****");//接收短信的手机号码
request.setSignName("阿里云");//短信签名名称
request.setTemplateCode("SMS_20933****");//短信模板CODE
request.setTemplateParam("张三");//短信模板变量对应的实际值
try {
SendSmsResponse response = client.getAcsResponse(request);
System.out.println(new Gson().toJson(response));
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
System.out.println("ErrCode:" + e.getErrCode());
System.out.println("ErrMsg:" + e.getErrMsg());
System.out.println("RequestId:" + e.getRequestId());
}
}
}
封装成SMSUtils
package com.itheima.reggie.utils;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
/**
* 短信发送工具类
*/
public class SMSUtils {
/**
* 发送短信
* @param signName 签名
* @param templateCode 模板
* @param phoneNumbers 手机号
* @param param 参数
*/
public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setSysRegionId("cn-hangzhou");
request.setPhoneNumbers(phoneNumbers);
request.setSignName(signName);
request.setTemplateCode(templateCode);
request.setTemplateParam("{\"code\":\""+param+"\"}");
try {
SendSmsResponse response = client.getAcsResponse(request);
System.out.println("短信发送成功");
}catch (ClientException e) {
e.printStackTrace();
}
}
}
使用
@PostMapping("/sendMsg")
public R sendMsg(@RequestBody User user, HttpSession session){
//获取手机号
String phone = user.getPhone();
if(StringUtils.isNotEmpty(phone)){
//生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
//调用阿里云提供的短信服务API完成发送短信
SMSUtils.sendMessage("瑞吉外卖","",phone,code);
//需要将生成的验证码保存到Session
session.setAttribute(phone,code);
return R.success("手机验证码短信发送成功");
}
return R.success("短信发送失败");
}
接收前端json数据,除了利用对象,Dto之外还有一种简单的方式就是利用键值对Map
@PostMapping("/login")
public R login(@RequestBody Map map){
log.info(map.toString());
return null;
}
Vue生命周期中mounted和created的区别
created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
原子数, 保证在多线程的情况下计算也没有问题,能够保证线程安全。
Mybatis-plus时间范围内的查询方式
@GetMapping("/page")
public R page(int page, int pageSize, String number,String beginTime,String endTime){
log.info("page={},pageSize={},number={},beginTime={},endTime={}",page,pageSize,number,beginTime,endTime);
Page pageInfo = new Page<>(page,pageSize);
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(number!=null,Orders::getNumber,number);
queryWrapper.ge(beginTime!=null,Orders::getOrderTime,beginTime);
queryWrapper.le(endTime!=null,Orders::getOrderTime,endTime);
queryWrapper.orderByDesc(Orders::getOrderTime);
ordersService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}