- 创建数据库,项目初始化
- 静态资源不在static目录下,如何映射
- 结果类
- 登录
- 过滤器拦截路径
- 全局异常处理器
- 分页查询
- 消息转换器
- 修改禁用
- 分页
- 编辑
- 公共字段自动填充使用ThreadLocal
- 新增用户
- 绑定的数据不可删除
- 上传
- 下载
- 前端传递的数据,不在同一张表时,DTO
- 响应的值,不在一个类中,多表分页
- 修改新增菜品,对两张表新增/修改数据
- 批量删除
- 用户下单
- 优化redis
- Springcache->redis简化开发
- 主从复制、读写分离,Sharding-JDBC
- Nginx概述
- Yapi
- swagger
数据库
- 员工表
- 菜品菜单分类表
- 菜品表
- 菜单表
- 套餐菜品关系表
- 菜品口味关系表
- 用户表
- 地址表
- 购物车表
- .订单表
- 订单明细表
流程:
- 创建实体类entity
- 创建controller
- 创建service 以及 impl
- 创建mapper
- 创建config配置类
- 创建common返回结果 R
静态资源不在static目录下,如何映射
编写web配置类,继承WebMvcConfigurationSupport 类
/**
* web配置类
*/
@Slf4j // 输出日志
@Configuration // 声明配置类
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("静态资源映射开始");
// 前端资源方式的请求 /backend/**
registry.addResourceHandler("/backend/**")
// 前端资源所在的位置
.addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
结果类
/**
* 通用返回结果类
* @param <T>
*/
@Data
public class R<T> {
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;
}
}
登录
//Mapper
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {}
//service
public interface EmployeeService extends IService<Employee> {}
//ServiceImpl
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {}
//config
@Slf4j
@RestController // @Controller + @ResponseBody(响应数据)
@RequestMapping("/employee")
public class EmployeeCpntroller {
@Autowired
EmployeeService employeeService;
@PostMapping("/login")
public R<Employee> EmployeeLogin(@RequestBody Employee employee, HttpServletRequest request){
// 1.获取提交的密码,进行加密
String password = employee.getPassword();
// 将密码转化为字体节数组继续加密后,赋值给password
password = DigestUtils.md5DigestAsHex(password.getBytes());
// 2.根据用户进行查询,查看数据是否存在
LambdaQueryWrapper<Employee> queryWrapper= new LambdaQueryWrapper<>();
// Employee::getUsername(创建Employee对象获取username属性) = 前端传递的username
queryWrapper.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<String> 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();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 向下转型
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
log.info("开始获取路径");
// 1.获取本次请求的URL
String 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<String> 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 {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
使用mybatis-plus进行分页
/*
* 分页查询
*/
@GetMapping("/page") // /employee/page?page=1&pageSize=10&name=123
public R<Page> page(int page,int pageSize,String name){
log.info("page = {} , pageSize = {} , name = {}",page,pageSize,name);
// 分页条件 查询的页码 每页数据
Page employeePage = new Page(page, pageSize);
LambdaQueryWrapper<Employee> queryWrapper= new LambdaQueryWrapper<>();
// 添加过滤条件 StringUtils.isNotEmpty()->判断当前属性是否为空,为空不执行 getUsername的值 ?= name
queryWrapper.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
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用jackson将对象转化为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的转换器,追加到mvc的框架集合中,优先使用
converters.add(0,messageConverter);
}
}
修改->禁用
/**
* 修改
*/
@PutMapping
public R<String> BanEmployee(@RequestBody Employee employee ,HttpServletRequest request){
//前端已修改禁用状态
Integer status = employee.getStatus();
System.out.println("禁用状态为:"+status);
// 拿出登录时存储的当前用户id
Long 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 {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
/*
* 分页查询
*/
@GetMapping("/page") // /employee/page?page=1&pageSize=10&name=123
public R<Page> page(int page,int pageSize,String name){
log.info("page = {} , pageSize = {} , name = {}",page,pageSize,name);
// 分页条件 查询的页码 每页数据
Page employeePage = new Page(page, pageSize);
LambdaQueryWrapper<Employee> queryWrapper= new LambdaQueryWrapper<>();
// 添加过滤条件 StringUtils.isNotEmpty()->判断当前属性是否为空,为空不执行 getUsername的值 ?= name
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getUsername,name);
// 添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
// 将分页 和 查询条件传入进去
employeeService.page(employeePage,queryWrapper);
return R.success(employeePage);
}
页面回显
/**
* 页面回显
*/
@GetMapping("/{id}")
public R<Employee> ShowDate(@PathVariable Long id){
log.info("当前id是:"+id);
LambdaQueryWrapper<Employee> queryWrapper= new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getId,id);
Employee one = employeeService.getOne(queryWrapper);
return R.success(one);
}
自动填充ThreadLocal
客户端每次发送的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<Long> 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();
}
}
新增用户
@PostMapping
public R<String> Employee(@RequestBody Employee employee, HttpServletRequest request){
String username = employee.getUsername();
// 2.根据用户进行查询,查看数据是否存在
LambdaQueryWrapper<Employee> queryWrapper= new LambdaQueryWrapper<>();
// Employee::getUsername(创建Employee对象获取username属性) = 前端传递的username
queryWrapper.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()));
// 获取当前session
HttpSession 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
*/
@Override
public void remove(Long id) {
// 查询当前分类是否关联了菜品
LambdaQueryWrapper<Dish> queryWrapper= new LambdaQueryWrapper<>();
queryWrapper.eq(Dish::getCategoryId,id);
int count = dishService.count(queryWrapper);
if (count > 0){
// 关联了菜品
throw new CustomException("当前分类已存在菜品,不可被删除");
}
// 查询当前分类是否关联了套餐
LambdaQueryWrapper<Setmeal> 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<String> 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<String> exceptionHandlerone(CustomException ex){
// 抛出异常提示
return R.error(ex.getMessage());
}
}
上传
1.获取当前文件名称,根据文件名称获取文件后缀,使用uuid生成新的文件名称
2.判断当前目录是否已存在
3.输出文件到指定位置
``
@PostMapping("/upload") // 文件上传
public R<String> 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<Page> pageDish(int page,int pageSize,String name){
Page<Dish> pg = new Page<>(page,pageSize);
Page<DishDto> pgDto = new Page<>(); //获取DishDto对象,里面有dish类中缺少的页面显示字段
LambdaQueryWrapper<Dish> 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<Dish> records = pg.getRecords();
// 得到records集合的原对象,map方法取出对象,
List<DishDto> list = records.stream().map((item)->{
DishDto dishDto = new DishDto();//创建dto对象
BeanUtils.copyProperties(item,dishDto);//将取出的对象拷贝到dto对象
Long categoryId = item.getCategoryId(); //获取分类id
Category 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
*/
@Override
public void saveWithFlaow(DishDto dishDto) {
//1.新增菜品表, 获取id
this.save(dishDto);
Long Id = dishDto.getId();
//2.获取dto对象的getFlavors()属性,属性封装了口味表
List<DishFlavor> 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 修改菜品
*/
@Override
public void UpdateDish(DishDto dishDto) {
//1.新增菜品
this.updateById(dishDto);
//2.删除口味表
LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper();
lambdaQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
dishFlavorService.remove(lambdaQueryWrapper);
//3.获取flavors属性 -> 为口味表
List<DishFlavor> 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
*/
@Override
public void DeleteDisable(List<Long> ids) {
LambdaQueryWrapper<Setmeal> 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<SetmealDish> 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
@Transactional
public void Submit(Orders orders) {
// 1.获取用户id
Long currentId = BaseContext.getCurrentId();
// 2.根据用户,获取当前购物车数据
LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(ShoppingCart::getUserId,currentId);
List<ShoppingCart> 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<OrderDetail> 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);
}
redis 优化
spring:
redis:
host: localhost
port: 6379
database: 0
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
序列化器
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 {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//默认的Key序列化器为:JdkSerializationRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
1.查询数据,保存redis里面
// 1.先从redis里面获取数据
/**
* 查询套餐
* @return
*/
@GetMapping("/list")
public R<List<DishDto>> QueryDishCategoryId(Dish dish){
List<DishDto> listDto = null;
String key = "dish_" + dish.getCategoryId() +"_" +dish.getStatus();
// 1.先从redis里面获取数据
listDto = (List<DishDto>)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
@PutMapping
public R<String> UpdateDish(@RequestBody DishDto dishDto){
dishService.UpdateDish(dishDto);
// 清理指定缓存
String key = "dish_" + dishDto.getCategoryId() +"_1";
redisTemplate.delete(key);
return R.success("操作成功");
}
SpringCache简化开发
注解:
EnableCaching 开启缓存注解功能,一般放在启动类上
使用map的方式
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private CacheManager cacheManager;
@Autowired
private UserService userService;
/**
* CachePut将方法的返回值放入缓存
* value:缓存的名称
* key :缓存的key,存储的值为 user对象
*/
@CachePut(value = "userCache" , key ="#user.id" )
@PostMapping
public 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")
@PutMapping
public 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<User> list(User user){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(user.getId() != null,User::getId,user.getId());
queryWrapper.eq(user.getName() != null,User::getName,user.getName());
List<User> list = userService.list(queryWrapper);
return list;
}
}
开发的方式:
1.启动类+ @EnableCaching 开启缓存注解功能
2.配置文件
spring:
cache:
redis:
time-to-live: 1800000 #30分钟到期
redis:
host: localhost
port: 6379
username: root
database: 0
3.jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
使用:
1.当查询时,有缓存的时候,直接走缓存,如果没有匹配到缓存的时候,那么查询数据库后,再将结果加入缓存中
/**
* 查询套餐
*/
@GetMapping("/list")
@Cacheable(value = "setSetmea" , key = "#dish.categoryId")
// 设置名称为setSetmea,key为#dish.categoryId, value为返回值的缓存数据
public R<List<Setmeal>> QuerySetmea(Setmeal dish){
LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Setmeal::getCategoryId,dish.getCategoryId());
lambdaQueryWrapper.eq(dish.getStatus()!=null,Setmeal::getStatus,dish.getStatus());
List<Setmeal> list = setmealService.list(lambdaQueryWrapper);
return R.success(list);
}
2.当使用删除/新增/修改的方法时,会直接删除改缓存
/**
* 删除菜单
* @return
*/
@CacheEvict(value = "setSetmea" , allEntries = true)
// 删除名称为setSetmea的全部数据
@DeleteMapping
public R<String> DeleteDisable(@RequestParam List<Long> ids){
setmealService.DeleteDisable(ids);
return R.success("删除成功");
}
其他方法
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private CacheManager cacheManager;
@Autowired
private UserService userService;
/**
* CachePut将方法的返回值放入缓存
* value:缓存的名称
* key :缓存的key,存储的值为 user对象
*/
@CachePut(value = "userCache" , key ="#user.id" )
@PostMapping
public 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")
@PutMapping
public 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<User> list(User user){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(user.getId() != null,User::getId,user.getId());
queryWrapper.eq(user.getName() != null,User::getName,user.getName());
List<User> list = userService.list(queryWrapper);
return list;
}
}
读写分离、主从复制
对同一时间段有大量并发,将数据库拆分为主库和从库,主库负责增删改操作,从库负责查询操作,能够有效的避免由数据更新导致的行锁,使整个系统的查询性能得到极大的改善
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
server:
port: 8080
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
spring:
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.138.100:3306/rw?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.138.101:3306/rw?characterEncoding=utf-8
username: root
password: root
masterslave:
# 读写分离配置
load-balance-algorithm-type: round_robin #轮询
# 最终的数据源名称
name: dataSource
# 主库数据源名称
master-data-source-name: master
# 从库数据源名称列表,多个逗号分隔
slave-data-source-names: slave
props:
sql:
show: true #开启SQL显示,默认false
main:
allow-bean-definition-overriding: true
Nginx
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 -v 查看版本
- ./nginx -t 查看配置文件是否错误
- ./nginx 启动nginx
- /usr/local/nginx/sbin/nginx(地址) -s stop 关闭nginx
- log里面 cat nginx.pid 当前进程id
- nginx -s reload 重新加载
vim /etc/profile 里面将Nginx的地址输入到里面去可以不用输入地址就可以使用了(PATH=/usr/local/nginx/sbin: J A V A H O M E / b i n : JAVA_HOME/bin: JAVAHOME/bin:PATH)
全局块
和nginx运行相关的全局配置
events块
和网络连接相关的网络配置
http块
代理、缓存、日志记录
http全局快:
servere模:server全局块 / location 块
1.部署静态资源/vim nginx.conf 将静态资源放到html目录下,即可访问
2.反向代理
服务端进行设置,为服务器提供服务。
用户访问反向代理服务器,反向代理将用户发送的请求转到到指定服务上
3.负载均衡
客户端发送请求至负载均衡服务器,由负载均衡服务器进行web服务器的分发操作。
yapi
介绍:
Api管理平台
Swagger
生成接口文档
- 导入maven坐标
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-jfinal</artifactId>
<version>3.0.2</version>
</dependency>
- 导入相关配置
3.设置静态资源映射
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
4.配置拦截器的放行请求
可以直接查看接口
常用注解