Day1
实现了登录模块:
利用Session实现会话登录
具体如下:
- 将页面提交的密码password进行md5加密处理
- 匹配用户名并取出用户信息进行判断
- 如果没有查询到则返回登录失败结果
- 密码比对,如果不一致则返回登录失败结果
- 查看员工状态,如果为已禁用状态,则返回员工已禁用结果
- 登录成功,将员工id存入Session并返回登录成功结果
- 用户退出直接清除Session中保存的当前登录员工的id
Day2
完善了登录模块
添加了拦截器
实现了增加用户接口
拦截器Filter:
- 实现Filter接口,重写doFilter方法
- 获取URI并定义不需要处理的请求路径
- 判断本次请求是否需要处理(路径匹配,检查本次请求是否需要放行)
- 不需要处理 ==> 放行
- 已登录(Session中有用户信息) ==> 放行
- 未登录 ==> 通过输出流方式向用户客户端发送数据
增加用户接口:
- 定义全局异常处理(防止用户重复)
- @ControllerAdvice(annotations = {RestController.class, Controller.class})
- @ExceptionHandler(发生异常的字节码)
Day3
实现了员工分页查询接口
- 添加MP的分页查询组件(MybatisPlusInterceptor)
- 定义分页构造器
- 定义条件构造器
- 分析查询条件
- 执行查询(调用Service.page(pageInfo, queryWrapper))
实现了启用、禁用员工账号接口
问题解决:
- 利用消息转换器将前端传来的时间格式对应成时分秒
- 利用消息转换器将用户id由JSON格式转换成Java格式(防止id溢出)
消息转换器的使用:
extendMessageConverters:
- 创建消息转换器对象
- 设置对象转换器,底层使用Jackson将Java对象转为json
- 将上面的消息转换器对象追加到mvc框架的转换器集合中
代码如下:
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
// 创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
// 设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
// 将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0, messageConverter);
}
其中JacksonObjectMapper
是我们自定义的消息转换器。
实现了编辑员工信息接口和公共字段自动填充
MP的公共字段自动填充:
- 在需要自动填充的字段添加注解@TableField(fill = FieldFill.INSERT)
- 实现MetaObjectHandler接口并重写insertFill和updateFill方法
- 使用metaObject.setValue(“字段名称”, 修改后的值)
问题:如果获取用户id以自动填充createUser和updateUser信息呢?
- 每个用户都是一个线程
- 使用ThreadLocal保存和获取当前用户id并封装在BaseContext中
- 在Longin的时候拿到Session中的用户id并set在BaseContext中
什么是ThreadLocal?
- Threadlocal并不是一个Thread,而是Thread的局部变量,当使用Threadlocal维护变量时,Threadlocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal的常用方法:
- public void set(Tvalue) 设置当前线程的线程局部变量的值
- public T get() 返回当前线程所对应的线程局部变量的值
Day4
实现了新增分类,分类信息分页查询,删除分类接口以及修改分类接口
分页查询复习:
- 定义分页构造器
- 定义条件构造器
- 分析查询条件
- 执行查询(调用Service接口)
重难点:删除分类接口
- 当前分类下关联了菜品,不能删除
- 当前分类下关联了套餐,不能删除
因此我们只需要根据id查询当前分类下关联的菜品(套餐)count 是否 > 0
如果 count > 0,则不能删除 –> 抛异常
抛异常的处理方法:
- 定义自定义异常处理器(CustomException)
- 定义全局异常处理器(GlobalExpectionHandler)
- @ExceptionHandler(RuntimeException.class)
代码如下:
@ExceptionHandler(RuntimeException.class)
public R<String> ExceptionHandler(RuntimeException ex) {
log.error(ex.getMessage());
return R.error(ex.getMessage());
}
实现了文件上传和文件下载接口
文件上传:
- 在properties中定义好reggie.path,在类中加入
@Value("${reggie.path}")
注解
@Value
注解是SpringFramework
中用于注入外部配置值的一个重要注解。它的主要作用是将配置文件(如application.properties
或 application.yml
)中的属性值注入到Spring
管理的 Bean
中。
- 代码如下:
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> 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(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
*/
@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) {
throw new RuntimeException(e);
}
}
Day5
实现了新增菜品接口
重难点:
flavor属性存在于DishFlavor中,其余的属性存在于Dish中,因此我们需要连接两张表进行查询
Dto:
- 用于封装页面提交的数据
- 全称为
Data Transfer Object
,即数据传输对象,一般用于展示层与服务层之间的数据传输。
这里就封装一个DishDto
并继承Dish
,这样就能接收前端发送的Dish所有的属性
前端传回来的数据flavors
中是一个列表,因此我们需要一个List[HTML_REMOVED]类型的属性。
代码如下:
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
实现方法:
- 在方法上添加事务注解
@Transactional
- 启动类上添加注解
@EnableTransactionManagement
- 通过
id
连接dish
和dishFlavors
两张表
saveBatch()
:批量添加数据,flavors
的数据是列表所以用saveBatch
。
代码如下:
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品,同时保存对应的口味数据
*
* @param dishDto
*/
@Override
@Transactional
public void saveWithFlavor(DishDto dishDto) {
this.save(dishDto);
Long dishId = dishDto.getId();
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item) -> {
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
实现了分页查询菜品信息接口
总结:
这个接口是非常有难度的。
由上表可知,dish
表中除了categoryName
字段,其余字段均存在。
我们之前封装的dishDto
中有categoryName
字段,但是我们不能直接返回DishDto
,因为dishDto
中包含了不需要返回的信息,只需要在dish
的基础上多返回一个categoryName
。
思路:
- 将
dish
表中的数据拷贝到dishDto
中(BeanUtils.copyProperties(pageInfo, dishDtoPage, "records"
) - 因为我们要求返回的泛型是
dishDto
,因此我们在拷贝的时候要忽略records
- 根据
id
查询分类对象,分类对象不为空的话,就setCategoryName
- 最后把
list
封装进Page
当中(dishDtoPage.setRecords(list)
)
代码如下:
/**
* 分页查询菜品信息
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
Page<Dish> pageInfo = new Page<>(page, pageSize);
Page<DishDto> dishDtoPage = new Page<>();
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(name != null, Dish::getName, name);
queryWrapper.orderByDesc(Dish::getSort);
dishService.page(pageInfo, queryWrapper);
BeanUtils.copyProperties(pageInfo, dishDtoPage, "records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> list = records.stream().map((item) -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item, dishDto);
Long categoryId = item.getCategoryId();//分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
if (category != null) {
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(list);
return R.success(dishDtoPage);
}