整体介绍
04 数据库表
05 maven搭建
设置静态资源映射
@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/");
}
}
06 后台登录
结构
泛型方法
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) {
map.put(key, value);
return this; //返回当前对象的引用
}
}
业务逻辑
p12 退出
p15 完善登录
过滤器
@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); //放行
}
}
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class,args);
log.info("项目启动成功");
}
}
- SpringBootApplication 上使用@ServletComponentScan 注解后
- Servlet可以直接通过@WebServlet注解自动注册
- Filter可以直接通过@WebFilter注解自动注册
- Listener可以直接通过@WebListener 注解自动注册
p18 新增员工
异常处理
以为username是唯一的,所以对于相同的username需要异常处理
p26 员工信息分页
配置分页信息
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
分页代码
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("page={},pageSize={},name={}",page,pageSize,name);
// 构造分页条件
Page pageInfo = new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<Employee>();
// StringUtils.isNotEmpty(name)意为 当name不为空的时候,后边的条件才成立
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
// 添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
p30 禁用员工状态
前端
后端
出现的问题
原因:js只对前16位精确,丢失了精度
解决方法
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器 底层使用jackson将java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上边的消息转换器对象追加到mvc框架的转换器集合中 0代表第一个 即优先使用
converters.add(0,messageConverter);
}
p36 编辑员工信息
公共字段自动填充
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
* 已一个线程为作用域
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id){
threadLocal.set(id);
}
public static Long getCurrentId(){
return threadLocal.get();
}
}
p43 新增分类
分类信息的分页查询
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
Page<Category> pageInfo = new Page<>(page,pageSize);
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByDesc(Category::getSort);
categoryService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
删除分类
执行的带条件查询如下
p49 文件的上传下载
@PostMapping("/upload")
public R<String> upload(MultipartFile file){
// 当前的file是一个临时文件,需要转储到指定位置,否则本次请求完成后临时文件会删除
log.info(file.toString());
String originakFilename = file.getOriginalFilename();
String suffix = originakFilename.substring(originakFilename.lastIndexOf("."));
//生成随机字符串
String filename = UUID.randomUUID().toString()+suffix;
File dir = new File(basePath);
// 判断目录是否存在,不存在就创建
if (!dir.exists()){
dir.mkdir();
}
try {
// 将临时文件转存
file.transferTo(new File(basePath+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(basePath+name));
// 输出流 通过输出流将文件写回浏览器 在浏览器展示
ServletOutputStream outputStream = response.getOutputStream();
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();
}
}
新增菜品
保存菜品
多表操作记得开启事物
菜品分页
所以要进行转换
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//构建分页构造器对象
Page<Dish> pageInfo = new Page<>(page,pageSize);
//DishDto中含有categoryName
Page<DishDto> dishDtoPage = new Page<>();
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(name!=null,Dish::getName,name);
queryWrapper.orderByDesc(Dish::getUpdateTime);
dishService.page(pageInfo,queryWrapper);
// 对象拷贝 不拷贝records
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
List<Dish> records = pageInfo.getRecords();
// 转化 类似于for循环
List<DishDto> list = records.stream().map((item)->{
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto);
Long categoryId = item.getCategoryId();
Category category = categoryService.getById(categoryId);
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(list);
return R.success(dishDtoPage);
}
菜品修改
自定义查询同时查询菜品和口味 用于回显数据
@Override
public DishDto getByIdWithFlavor(Long id) {
//查询菜品信息
Dish dish = this.getById(id);
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish,dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dish.getId());
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(flavors);
return dishDto;
}
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id){
DishDto dishDto = dishService.getByIdWithFlavor(id);
return R.success(dishDto);
}
更新数据
@Override
@Transactional
public void updateWithFlayor(DishDto dishDto) {
// 更新dish表基本信息
this.updateById(dishDto);
// 清理当前菜品对应口味数据---dish_flavor表的delete操作
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
dishFlavorService.remove(queryWrapper);
// 添加当前提交过来的口味数据---dish_flavor表的insert操作
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item)->{
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.updateWithFlayor(dishDto);
return R.success("新增菜品成功");
}
day5 套餐管理业务开发
新增套餐
显示菜品
/**
* 根据条件查询对应的菜品数据
*/
@GetMapping("/list")
public R<List<Dish>> list(Dish dish){ //使用dish为参数是为了增加可扩展性
LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
// 添加条件 查询状态为1的菜品
lambdaQueryWrapper.eq(Dish::getStatus,1);
//添加排序
lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> list = dishService.list(lambdaQueryWrapper);
return R.success(list);
}
分页
/**
* 分页查询
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//分页构造器
Page<Setmeal> pageInfo = new Page<>(page,pageSize);
Page<SetmealDto> pagDto = new Page<>(); //解决categoryName字段不对应的问题
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
// 添加查询条件
queryWrapper.like(name!=null,Setmeal::getName,name);
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
setmealService.page(pageInfo,queryWrapper);
// 对象拷贝
BeanUtils.copyProperties(pageInfo,pagDto,"records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> list =records.stream().map((item)->{
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(item,setmealDto);
// 分类id
Long categoryid = item.getCategoryId();
// 根据分类id查询分类对象
Category category = categoryService.getById(categoryid);
if (category!=null){
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
pagDto.setRecords(list);
return R.success(pagDto);
}
删除操作
p79 手机发送验证码登录
导入用户地址簿
设置默认地址
LambdaUpdateWrapper
/**
* 设置默认地址
*/
@PutMapping("default")
public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
log.info("addressBook:{}", addressBook);
LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
wrapper.set(AddressBook::getIsDefault, 0);
//SQL:update address_book set is_default = 0 where user_id = ?
addressBookService.update(wrapper);
addressBook.setIsDefault(1);
//SQL:update address_book set is_default = 1 where id = ?
addressBookService.updateById(addressBook);
return R.success(addressBook);
}
移动端菜品展示
请求路径
初始化函数
添加购物车
用户下单
表单
首页面
初始化页面的两个请求
---------------项目优化------------
缓存相关的优化
环境搭建
自己创建redisTemplate 方便序列化 也可以直接使用StringRedisTemplate
缓存短信验证码_思路梳理&代码改造&功能测试
设置值
获取值
删除值
缓存菜品
- 每次点击分类都会查询一边数据库,导致性能下降
- 要按分类进行菜品缓存
- 要防止脏数据,当菜品信息发生改变时,要对缓存进行清空
List<DishDto> dishDtoList =null;
//动态构造key
String key = "dish_"+dish.getCategoryId()+"_"+dish.getStatus();
// 先从redis中获取缓存数据
dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
if (dishDtoList != null){
// 如果存在,直接返回 无需查询数据库
return R.success(dishDtoList);
}
// 如果不存在 查询数据库 把查询到的菜品缓存到redis,注意这里的dishDtoList是查完数据库的dishDtoList和上边的不是一个
redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES);
缓存菜品数据_清理缓存思路分析&代码改造并测试
直接清理全部缓存
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
log.info(dishDto.toString());
dishService.updateWithFlayor(dishDto);
// 清理所有菜品的缓存
Set keys = redisTemplate.keys("dish_*");
redisTemplate.delete(keys);
return R.success("新增菜品成功");
}
精确清理
上传到git并且合并到主分支
Spring Cache_框架介绍&常用注释
Spring Cache的基本包都在spring-context下所以不需要特意导入
CachePut注解
CacheEvict注解
见下一章