流程:
编写web配置类,继承WebMvcConfigurationSupport 类
/*** web配置类*/
@Slf4j // 输出日志
@Configuration // 声明配置类
public class WebConfig extends WebMvcConfigurationSupport {/*** 设置静态资源映射* @param registry*/@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("静态资源映射开始");
// 前端资源方式的请求 /backend/**registry.addResourceHandler("/backend/**")
// 前端资源所在的位置.addResourceLocations("classpath:/backend/");registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");}
}
/*** 通用返回结果类* @param */
@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;}}
//Mapper
@Mapper
public interface EmployeeMapper extends BaseMapper {}
//service
public interface EmployeeService extends IService {}
//ServiceImpl
@Service
public class EmployeeServiceImpl extends ServiceImpl implements EmployeeService {}
//config
@Slf4j
@RestController // @Controller + @ResponseBody(响应数据)
@RequestMapping("/employee")
public class EmployeeCpntroller {@AutowiredEmployeeService employeeService;@PostMapping("/login")public R EmployeeLogin(@RequestBody Employee employee, HttpServletRequest request){
// 1.获取提交的密码,进行加密String password = employee.getPassword();
// 将密码转化为字体节数组继续加密后,赋值给passwordpassword = DigestUtils.md5DigestAsHex(password.getBytes());
// 2.根据用户进行查询,查看数据是否存在LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
// Employee::getUsername(创建Employee对象获取username属性) = 前端传递的usernamequeryWrapper.eq(Employee::getUsername,employee.getUsername());
// 根据查询结果封装为实体类对象Employee one = employeeService.getOne(queryWrapper);
// 3.判断当前username的返回结果if (one == null){return R.error("当前用户不存在");}
// 4.密码比对if (!one.getPassword().equals(password)){return R.error("密码错误,请重新输入");}
// 5.查看员工状态if (one.getStatus() == 0){return R.error("当前用户已被禁用");}
// 6.登录成功,将id保存到session中,返回登录成功结果HttpSession session = request.getSession();session.setAttribute("employee",one.getId());return R.success(one);}@RequestMapping("/logout")
// 退出不需要返回详细数据public R logout(HttpServletRequest request ){HttpSession session = request.getSession();
// 退出操作,将session的employee信息删除session.removeAttribute("employee");return R.success("操作成功");}
}
/*** 配置过滤器*/
// 过滤器名称 过滤器拦截路径
@WebFilter(filterName = "LoginCheckFilter" , urlPatterns = "/*")
@Component
@Slf4j
public class LoginCheckFilter implements Filter {// 路径匹配器,支持通配符public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 向下转型HttpServletRequest request = (HttpServletRequest)servletRequest;HttpServletResponse response = (HttpServletResponse)servletResponse;log.info("开始获取路径");
// 1.获取本次请求的URLString requestURI = request.getRequestURI();
// 2.判断请求是否处理 urls是放行路径 /employee/page是当前列表的查询请求String[] urls = new String[]{"/employee/login","/employee/page","/employee/logout","/backend/**","/front/**"};boolean check = check(urls, requestURI);
// check = true时 ,标识存在当前放行目录中,进行放行if (check){log.info("放行目录放行"); //放行路径filterChain.doFilter(request,response); //放行return;}log.info("登录放行前置");
// 判断用户是否登录HttpSession session = request.getSession();Object employee = session.getAttribute("employee");log.info("登录放行中置"+requestURI);if (employee != null){log.info("登录放行路径"); //放行路径filterChain.doFilter(request,response); //放行return;}
// 不放行,将R对象转为JSON,响应给前端处理log.info("放行跳转");response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));return;}/*** 路径匹配,判断本次请求是否放行*/public boolean check(String[] urls , String requestURL){for (String s:urls){
// 是否匹配boolean match = PATH_MATCHER.match(s, requestURL);if (match){return true;}}return false;}
}
/*** 指定异常处理 annotations(拦截哪个注解下的异常) = (RestController.class)*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {/*** 异常处理方法,处理sql异常*/@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R exceptionHandler(SQLIntegrityConstraintViolationException ex){if (ex.getMessage().contains("Duplicate entry")){String[] s = ex.getMessage().split(" ");String name = s[2] + "已存在";return R.error(name);}return R.error("未知异常");}}
分页插件
/*** 分页插件*/
@Configuration()
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//添加分页插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}}
使用mybatis-plus进行分页
/** 分页查询*/@GetMapping("/page") // /employee/page?page=1&pageSize=10&name=123public R page(int page,int pageSize,String name){log.info("page = {} , pageSize = {} , name = {}",page,pageSize,name);
// 分页条件 查询的页码 每页数据Page employeePage = new Page(page, pageSize);LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
// 添加过滤条件 StringUtils.isNotEmpty()->判断当前属性是否为空,为空不执行 getUsername的值 ?= namequeryWrapper.like(StringUtils.isNotEmpty(name),Employee::getUsername,name);
// 添加排序条件queryWrapper.orderByDesc(Employee::getUpdateTime);
// 将分页 和 查询条件传入进去employeeService.page(employeePage,queryWrapper);return R.success(employeePage);}
将json转为java 将java转为json
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 org.springframework.stereotype.Component;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);}
}
@Slf4j // 输出日志
@Configuration // 声明配置类
public class WebConfig extends WebMvcConfigurationSupport {/*** 扩展springmvc的消息转换器* @param converters*/@Overrideprotected void extendMessageConverters(List> converters) {//创建消息转换器MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();//设置对象转换器,底层使用jackson将对象转化为jsonmessageConverter.setObjectMapper(new JacksonObjectMapper());//将上面的转换器,追加到mvc的框架集合中,优先使用converters.add(0,messageConverter);}
}
/*** 修改*/@PutMappingpublic R BanEmployee(@RequestBody Employee employee ,HttpServletRequest request){//前端已修改禁用状态Integer status = employee.getStatus();System.out.println("禁用状态为:"+status);
// 拿出登录时存储的当前用户idLong employee1 = (Long)request.getSession().getAttribute("employee");employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(employee1);employeeService.updateById(employee);return R.success("操作成功");}
分页插件
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 分页插件*/
@Configuration()
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//添加分页插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}}
/** 分页查询*/@GetMapping("/page") // /employee/page?page=1&pageSize=10&name=123public R page(int page,int pageSize,String name){log.info("page = {} , pageSize = {} , name = {}",page,pageSize,name);
// 分页条件 查询的页码 每页数据Page employeePage = new Page(page, pageSize);LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
// 添加过滤条件 StringUtils.isNotEmpty()->判断当前属性是否为空,为空不执行 getUsername的值 ?= namequeryWrapper.like(StringUtils.isNotEmpty(name),Employee::getUsername,name);
// 添加排序条件queryWrapper.orderByDesc(Employee::getUpdateTime);
// 将分页 和 查询条件传入进去employeeService.page(employeePage,queryWrapper);return R.success(employeePage);}
/*** 页面回显*/@GetMapping("/{id}")public R ShowDate(@PathVariable Long id){log.info("当前id是:"+id);LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();queryWrapper.eq(Employee::getId,id);Employee one = employeeService.getOne(queryWrapper);return R.success(one);}
客户端每次发送的http请求,对应的服务器都会分配一个新的线程来处理,在处理请求时,多个类的多个方法均为同一个显示。->拦截器->执行update方法->自定义字段处理器 用的都是同一个线程
ThreadLocal:不是线程,而是线程的局部变量,使用ThreadLocal维护变量时,每一个ThreadLocal为当前变量的副本,每个线程都可以独立的改变副本,而不会影响其他线程对应的版本,ThreadLocal为每个线程提供单独的存储时间,具有线程隔离的效果,只有在线程内才可以取的到对应的值,线程外,不能访问该值.
1.加入注解
@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.自定义对象处理器通过 ThreadLocal 获取当前操作对象的id值
/*** 自定义元数据对象处理器,将公共字段的值key*/
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {@Override // 执行insert操作时,执行public void insertFill(MetaObject metaObject) {long id = Thread.currentThread().getId();log.info("自定义线程id为:"+id);metaObject.setValue("createTime", LocalDateTime.now());metaObject.setValue("updateTime",LocalDateTime.now());metaObject.setValue("createUser",BaseContext.getCurrentId());metaObject.setValue("updateUser",BaseContext.getCurrentId());}@Override // 执行更新操作时,执行public void updateFill(MetaObject metaObject) {long id = Thread.currentThread().getId();log.info("自定义线程id为:"+id);metaObject.setValue("updateTime",LocalDateTime.now());metaObject.setValue("updateUser",BaseContext.getCurrentId());}
}
3.过滤器中,判断当前员工是否登录时,加入了 ThreadLocal 的set方法存储当前用户id的值
4.自定义ThreadLocal
/*** 基于ThreadLocal的封装工具类,用户保存和获取当前登录Id*/
public class BaseContext {// Long表示当前存储线程的字段属性private static ThreadLocal threadLocal = new ThreadLocal<>();/*** 获取id,存储id* @param id*/public static void serCurrentId(Long id){threadLocal.set(id);}/*** 取出存储id* @return*/public static Long getCurrentId(){return threadLocal.get();}
}
@PostMappingpublic R Employee(@RequestBody Employee employee, HttpServletRequest request){String username = employee.getUsername();
// 2.根据用户进行查询,查看数据是否存在LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
// Employee::getUsername(创建Employee对象获取username属性) = 前端传递的usernamequeryWrapper.eq(Employee::getUsername,employee.getUsername());Employee user = employeeService.getOne(queryWrapper);
// System.out.println(user);if (user != null){return R.error("当前用户名称已经存在");}
// 设置默认密码加密密码employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
// 获取当前sessionHttpSession session = request.getSession();Long empId =(Long) session.getAttribute("employee");
// employee.setCreateUser(empId);
// employee.setUpdateUser(empId);employeeService.save(employee);return R.success("操作成功");}
查看当前分类id是否绑定了菜品和套餐,如果绑定了菜品和套餐抛出自定义异常,没有则删除当前分类
/*** 根据id进行删除分类* @param id*/@Overridepublic void remove(Long id) {// 查询当前分类是否关联了菜品LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();queryWrapper.eq(Dish::getCategoryId,id);int count = dishService.count(queryWrapper);if (count > 0){// 关联了菜品throw new CustomException("当前分类已存在菜品,不可被删除");}// 查询当前分类是否关联了套餐LambdaQueryWrapper qw = new LambdaQueryWrapper<>();qw.eq(Setmeal::getCategoryId,id);int count1 = setmealService.count(qw);if (count1 > 0){// 关联了菜品throw new CustomException("当前分类已存在套餐,不可被删除");}
// 删除分类super.removeById(id);}
自定义异常
/*** 业务自定义异常*/
public class CustomException extends RuntimeException {public CustomException(String message){super(message);}}
指定自定义异常处理
/*** 指定异常处理 annotations(拦截哪个注解下的异常) = (RestController.class)*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {/*** 异常处理方法,处理sql异常*/@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R exceptionHandler(SQLIntegrityConstraintViolationException ex){if (ex.getMessage().contains("Duplicate entry")){String[] s = ex.getMessage().split(" ");String name = s[2] + "已存在";return R.error(name);}return R.error("未知异常");}/*** 自定义异常* @param ex* @return*/@ExceptionHandler(CustomException.class)public R exceptionHandlerone(CustomException ex){
// 抛出异常提示return R.error(ex.getMessage());}}
1.获取当前文件名称,根据文件名称获取文件后缀,使用uuid生成新的文件名称
2.判断当前目录是否已存在
3.输出文件到指定位置
``
@PostMapping("/upload") // 文件上传public R upload(MultipartFile file){// 获取原始文件名称String fileName = file.getOriginalFilename();// 随机生成文件名称,uuid 获取文件后缀名String hzName = fileName.substring(fileName.lastIndexOf("."));// 使用uuid + 后缀名称fileName = UUID.randomUUID().toString() + hzName;// 判断当前目录是否存在,不存在则创建File directory = new File(basePath);if (!directory.exists()){directory.mkdirs();}try {
// 指定下载图片的位置file.transferTo(new File(basePath+fileName));} catch (IOException e) {e.printStackTrace();}return R.success(fileName);}
1.输入流 获取当前文件所在的路径
2.创建输出流,用于响应文件
3.输入流读取文件,输出流写文件
4.刷新并关闭流
@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();}}
一般用于前端传递的值与封装的对象不统一,业务需要多表处理,简化操作,把两个表的属性写在一起
1.创建关联表对象DTO,关联表中为响应的全部字段
2.创建响应类的原对象,进行数据的条件查询
3.dto拷贝源对象,排除原对象缺少的字段 (list)
4.源对象获取缺少的字段(list),遍历该对象,拿出对象的值赋值给dto对象,根据拿出的对象id进行分类表格的查询,查询出获取分类表的name值,赋值给dto对象,再将新的list集合,排除拷贝的对象赋值给Dto对象
@GetMapping("/page")public R pageDish(int page,int pageSize,String name){Page pg = new Page<>(page,pageSize);Page pgDto = new Page<>(); //获取DishDto对象,里面有dish类中缺少的页面显示字段LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
// 添加排序条件queryWrapper.like(name!=null,Dish::getName,name);queryWrapper.orderByDesc(Dish::getUpdateTime);dishService.page(pg,queryWrapper);//拷贝对象 records是页面查出的缺少数据集合 要排除掉BeanUtils.copyProperties(pg,pgDto,"records");//获取列表排除的字段 records 给其添加字段List records = pg.getRecords();// 得到records集合的原对象,map方法取出对象,List list = records.stream().map((item)->{DishDto dishDto = new DishDto();//创建dto对象BeanUtils.copyProperties(item,dishDto);//将取出的对象拷贝到dto对象Long categoryId = item.getCategoryId(); //获取分类idCategory byId = categoryService.getById(categoryId);//根据id查询出该条分类数据String name1 = byId.getName();//获取当前对象的name字段值dishDto.setCategoryName(name1);//查询出的name字段值通过dto的set方法进行赋值return dishDto;}).collect(Collectors.toList()); //创建新的list集合pgDto.setRecords(list); //将新的list集合,赋值给当前排除的集合的对象return R.success(pg);}
新增:
1.先新增菜品,获取菜品id
2.获取dto对象的getFlavors(为list)属性,属性封装了口味表
3.item遍历list,获取菜品id,储存到口味表中
4.将dto封装的口味表list新增通过saveBatch方法新增
/*** 新增* @param dishDto*/@Overridepublic void saveWithFlaow(DishDto dishDto) {//1.新增菜品表, 获取idthis.save(dishDto);Long Id = dishDto.getId();//2.获取dto对象的getFlavors()属性,属性封装了口味表List flavors = dishDto.getFlavors();//3.item遍历list,获取菜品id,储存到口味表中flavors = flavors.stream().map((item) -> {item.setDishId(Id);return item;}).collect(Collectors.toList());//4.再将dto封装的口味表list新增dishFlavorService.saveBatch(flavors);}
修改:
1.新增菜品
2.将口味表删除
3.获取当前Dta的请求参数list值
4.遍历list,得到的对象获取菜品表id
5.新增口味表
/**** @param dishDto 修改菜品*/@Overridepublic void UpdateDish(DishDto dishDto) {//1.新增菜品this.updateById(dishDto);//2.删除口味表LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper();lambdaQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());dishFlavorService.remove(lambdaQueryWrapper);//3.获取flavors属性 -> 为口味表List flavors = dishDto.getFlavors();//4.item遍历当前口味集合,获取菜品的id,口味表进行赋值存储flavors = flavors.stream().map((item) -> {item.setDishId(dishDto.getId());return item;}).collect(Collectors.toList());//5.完成口味表新增dishFlavorService.saveBatch(flavors);}
1.批量删除时,传递list值
2.queryWrapper使用in,查询出删除的全部数据
/*** 删除* @param ids*/@Overridepublic void DeleteDisable(List ids) {LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.in(Setmeal::getId,ids); // 查询所有ids的数据queryWrapper.eq(Setmeal::getStatus,1); //查询ids数据中是否有在售的int count = this.count(queryWrapper);if (count > 0){ // 大于0代表有在售套餐throw new CustomException("套餐正在售卖,不可删除");}this.removeByIds(ids); //否则批量删除所有ids的数据LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);setmealDishesService.remove(lambdaQueryWrapper);}
1.从ThreadLocal获取当前用户的id
2.根据用户id,查询当前用户添加到购物车中的数据
3.根据当前用户id查询,地址数据
4.遍历当前购物车的数据,订单详情表通过set的方式进行复制
5.通过set的方式赋值订单表
6.像订单表插入输入,向订单明细表插入数据,删除购物车数据
/** 用户下单* @param orders*/@Override@Transactionalpublic void Submit(Orders orders) {// 1.获取用户idLong currentId = BaseContext.getCurrentId();// 2.根据用户,获取当前购物车数据LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(ShoppingCart::getUserId,currentId);List list = shoppingCartService.list(lambdaQueryWrapper);if (list == null || list.size() == 0){throw new CustomException("购物车不能为空");}// 查询用户数据 、 地址数据User byId = userService.getById(currentId);Long addressBookId = orders.getAddressBookId();AddressBook addressBook = addressBookService.getById(addressBookId);if (addressBook == null){throw new CustomException("地址不能为空");}// 3.订单表1条// 生成订单号long orderId = IdWorker.getId();// 计算总金额,遍历购物车数据AtomicInteger amount = new AtomicInteger(); //保证多线程,保证数据正确List orderDetails = list.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());orders.setNumber(String.valueOf(orderId));orders.setId(orderId);orders.setOrderTime(LocalDateTime.now());orders.setCheckoutTime(LocalDateTime.now());orders.setStatus(2);orders.setAmount(new BigDecimal(amount.get()));//总金额orders.setUserId(currentId);orders.setNumber(String.valueOf(orderId));orders.setUserName(byId.getName());orders.setConsignee(addressBook.getConsignee());orders.setPhone(addressBook.getPhone());orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())+ (addressBook.getCityName() == null ? "" : addressBook.getCityName())+ (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())+ (addressBook.getDetail() == null ? "" : addressBook.getDetail()));//向订单表插入数据,一条数据this.save(orders);// 4.订单明细表插入数据orderDetailServicel.saveBatch(orderDetails);// 5.下单完成后,清空购物车数据shoppingCartService.remove(lambdaQueryWrapper);}
spring:redis:host: localhostport: 6379database: 0
org.springframework.boot spring-boot-starter-data-redis
序列化器
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate
1.查询数据,保存redis里面
// 1.先从redis里面获取数据
/*** 查询套餐* @return*/@GetMapping("/list")public R> QueryDishCategoryId(Dish dish){List listDto = null;String key = "dish_" + dish.getCategoryId() +"_" +dish.getStatus();// 1.先从redis里面获取数据listDto = (List)redisTemplate.opsForValue().get(key);if (listDto != null){return R.success(listDto);}// 2.没有的话查询数据库//3. 如果不存在,查询数据库,把查询到的菜品缓存到redis里// key = dish_当前套餐的id_1 listDao是查询出的套餐结果redisTemplate.opsForValue().set(key,listDto,60, TimeUnit.MINUTES);return R.success(listDto);
2.修改/删除/更新后,删除当前redis
@PutMappingpublic R UpdateDish(@RequestBody DishDto dishDto){dishService.UpdateDish(dishDto);// 清理指定缓存String key = "dish_" + dishDto.getCategoryId() +"_1";redisTemplate.delete(key);return R.success("操作成功");}
注解:
EnableCaching 开启缓存注解功能,一般放在启动类上
使用map的方式
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Autowiredprivate CacheManager cacheManager;@Autowiredprivate UserService userService;/*** CachePut将方法的返回值放入缓存* value:缓存的名称* key :缓存的key,存储的值为 user对象*/@CachePut(value = "userCache" , key ="#user.id" )@PostMappingpublic User save(User user){userService.save(user);return user;}/*** 清理指定缓存根据value和key,删除缓存*/@CacheEvict(value = "userCache" , key = "#id")@DeleteMapping("/{id}")public void delete(@PathVariable Long id){userService.removeById(id);}/*** 更新数据时,删除缓存数据*/@CacheEvict(value = "userCache" , key = "#user.id")@PutMappingpublic User update(User user){userService.updateById(user);return user;}/** 在执行方法时,先查看cacheManager里有没有缓存的数据,如果有的话,注解返回数据,如果没有的话,查询数据* condition->条件,满足条件时,才缓存数据*/@Cacheable(value = "userCache" , key = "#id" , condition = "#result != null" )@GetMapping("/{id}")public User getById(@PathVariable Long id){User user = userService.getById(id);return user;}/*** 在执行方法时,先查看cacheManager里有没有缓存的数据,如果有的话,注解返回数据,如果没有的话,查询数据*/@Cacheable(value = "userCache" , key = "#user.id+'_'+#user.name" , condition = "#result != null" )@GetMapping("/list")public List list(User user){LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(user.getId() != null,User::getId,user.getId());queryWrapper.eq(user.getName() != null,User::getName,user.getName());List list = userService.list(queryWrapper);return list;}
}
1.启动类+ @EnableCaching 开启缓存注解功能
2.配置文件
spring:cache:redis:time-to-live: 1800000 #30分钟到期redis:host: localhostport: 6379username: rootdatabase: 0
3.jar包
org.springframework.boot spring-boot-starter-cache org.springframework.boot spring-boot-starter-data-redis
使用:
1.当查询时,有缓存的时候,直接走缓存,如果没有匹配到缓存的时候,那么查询数据库后,再将结果加入缓存中
/*** 查询套餐*/@GetMapping("/list")@Cacheable(value = "setSetmea" , key = "#dish.categoryId")// 设置名称为setSetmea,key为#dish.categoryId, value为返回值的缓存数据public R> QuerySetmea(Setmeal dish){LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Setmeal::getCategoryId,dish.getCategoryId());lambdaQueryWrapper.eq(dish.getStatus()!=null,Setmeal::getStatus,dish.getStatus());List list = setmealService.list(lambdaQueryWrapper);return R.success(list);}
2.当使用删除/新增/修改的方法时,会直接删除改缓存
/*** 删除菜单* @return*/@CacheEvict(value = "setSetmea" , allEntries = true)// 删除名称为setSetmea的全部数据@DeleteMappingpublic R DeleteDisable(@RequestParam List ids){setmealService.DeleteDisable(ids);return R.success("删除成功");}
其他方法
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Autowiredprivate CacheManager cacheManager;@Autowiredprivate UserService userService;/*** CachePut将方法的返回值放入缓存* value:缓存的名称* key :缓存的key,存储的值为 user对象*/@CachePut(value = "userCache" , key ="#user.id" )@PostMappingpublic User save(User user){userService.save(user);return user;}/*** 清理指定缓存根据value和key,删除缓存*/@CacheEvict(value = "userCache" , key = "#id")@DeleteMapping("/{id}")public void delete(@PathVariable Long id){userService.removeById(id);}/*** 更新数据时,删除缓存数据*/@CacheEvict(value = "userCache" , key = "#user.id")@PutMappingpublic User update(User user){userService.updateById(user);return user;}/** 在执行方法时,先查看cacheManager里有没有缓存的数据,如果有的话,注解返回数据,如果没有的话,* 查询数据后,将方法的返回值放到缓存中* condition->条件,满足条件时,才缓存数据* unless -> 条件满足时,不缓存*/@Cacheable(value = "userCache" , key = "#id" , unless = "#result == null" )@GetMapping("/{id}")public User getById(@PathVariable Long id){User user = userService.getById(id);return user;}/*** 在执行方法时,先查看cacheManager里有没有缓存的数据,如果有的话,注解返回数据,如果没有的话,* 查询数据后,将方法的返回值放到缓存中*/@Cacheable(value = "userCache" , key = "#user.id+'_'+#user.name" , condition = "#result != null" )@GetMapping("/list")public List list(User user){LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(user.getId() != null,User::getId,user.getId());queryWrapper.eq(user.getName() != null,User::getName,user.getName());List list = userService.list(queryWrapper);return list;}}
对同一时间段有大量并发,将数据库拆分为主库和从库,主库负责增删改操作,从库负责查询操作,能够有效的避免由数据更新导致的行锁,使整个系统的查询性能得到极大的改善
org.apache.shardingsphere sharding-jdbc-spring-boot-starter 4.0.0-RC1
server:port: 8080
mybatis-plus:configuration:#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: ASSIGN_ID
spring:shardingsphere:datasource:names:master,slave# 主数据源master:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.138.100:3306/rw?characterEncoding=utf-8username: rootpassword: root# 从数据源slave:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.138.101:3306/rw?characterEncoding=utf-8username: rootpassword: rootmasterslave:# 读写分离配置load-balance-algorithm-type: round_robin #轮询# 最终的数据源名称name: dataSource# 主库数据源名称master-data-source-name: master # 从库数据源名称列表,多个逗号分隔slave-data-source-names: slaveprops:sql:show: true #开启SQL显示,默认falsemain:allow-bean-definition-overriding: true
nginx是一款轻量级别的web服务器/反向代理服务器。内存少,并发能力强。
Linux安装
1.yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
2.wget 看看是否下载 yum install wget 下载
3. wget http://nginx.org/download/nginx-1.16.1.tar.gz 下载
4. tar -zxvf nginx-1.16.1.tar.gz 解压
5. cd nginx-1.16.1 mkdir /usr/local/nginx
./configure --prefix=/usr/local/nginx 安装到哪里去
6.make && make install; 安装
nginx 常用命令
全局块
和nginx运行相关的全局配置
events块
和网络连接相关的网络配置
http块
代理、缓存、日志记录
http全局快:
servere模:server全局块 / location 块
1.部署静态资源/vim nginx.conf 将静态资源放到html目录下,即可访问
2.反向代理
服务端进行设置,为服务器提供服务。
用户访问反向代理服务器,反向代理将用户发送的请求转到到指定服务上
3.负载均衡
客户端发送请求至负载均衡服务器,由负载均衡服务器进行web服务器的分发操作。
介绍:
Api管理平台
生成接口文档
com.github.xiaoymin knife4j-jfinal 3.0.2
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
4.配置拦截器的放行请求
可以直接查看接口
常用注解