项目---外卖

122 阅读4分钟

整体介绍

c53719bd2f4d73f3933646f97d8859a.png

04 数据库表

1659861508611.png

05 maven搭建

dddeccb68d4bccec13dd2ce9447955b.png

设置静态资源映射

@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 后台登录

结构

8fc50daab136d7e4b9cc8a5a766db42.png

泛型方法

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;   //返回当前对象的引用
}

}

blog.csdn.net/weixin_4381…

业务逻辑

e28a4248cc8ae77db0845edad9b55c2.png

p12 退出

p15 完善登录

过滤器

1660021503534.png

@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 新增员工

ebc6b3132c51ab2ace4a618155202f5.png

f2e6de75a3ee792d43c0d7e2d35f419.png

异常处理

以为username是唯一的,所以对于相同的username需要异常处理

1660035861427.png

p26 员工信息分页

d75db027aa4953ad5a8538c93fa3108.png

配置分页信息

@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 禁用员工状态

03ab096972cbc171c69b6d8ad848c15.png 1660132825180.png

前端

1660133931126.png

后端

image.png

出现的问题

1f49636d436717a5db191eb9d0e0d9b.png 原因:js只对前16位精确,丢失了精度

解决方法

1660135950980.png

e2a86ba9d9be6ac5aa67352fcadffdb.png

9a27dabd064f7b89ce6339f5c83aaec.png

/**
 * 扩展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 编辑员工信息

3d638899e84c2071e0b39480783a5dc.png

90770a34b89b1558ced986fc8d0881b.png aeaea54dc05afb6e618853588e93f79.png 500b49548e9b186a95ac3b6e09a14e0.png 1d56e7150fd4e98bedccb3f7afddfc1.png

dceec8d2433be9a91bb38904cafe48d.png

公共字段自动填充

1660208977742_D04DB97B-B184-44f1-8518-1EC615047F4F.png

1660209293877_7AF17315-88BE-4ea2-B4F2-C039973F7087.png

1660209467741_AF04E760-8F7D-42f1-B3C6-EE10ACD71596.png

1660217814509_27D78192-E11D-4bec-A772-D882347D8A7E.png

image.png

1660218573967_F9357BED-EF46-4c6e-8FC0-ED047A6ECEC0.png

1660218974800_0685107A-A5EB-4a60-97AD-A7174B928A59.png

/**
 * 基于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();
    }

}

1660220344140_6B9F0263-2A06-444d-9BF6-21D8AC9B62AB.png

1660220369416_D58C9927-430A-4aa2-A02F-1CFD2C708530.png

p43 新增分类

1660802794319.png

d57a6115a4e2c9760a9f08497b37933.png

分类信息的分页查询

91dafc4fab833e8c75c81931f2b8c82.png

@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);

}

a577c2e69f4ef291e40e662aa0d174f.png

删除分类

5e37eafe0ad5d854ffcc52213df7833.png f36044c0ece6af21fda2fe06517a1a5.png 87b9cfe36fe7e85bcf7c6035b3b0856.png image.png 执行的带条件查询如下 977a258220a948e70afc79993a10bf1.png

p49 文件的上传下载

95850f3daee67d215c4a31596106755.png

556757a4d07274036a21445b22b5c40.png

09c3a1226947956e75db63b115b6bd0.png

e6ee5b69901fee84537ce2fde108707.png

40d2eecb352edfec83e2e69f6773c7d.png

a806db88f6688fe6304dfb838250e6b.jpg

@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();
    }

}

新增菜品

1660977783555_E1923886-65D6-42aa-83BE-C3B5D736739B.png

1660978779908_C36428D4-7411-4146-9C63-3D4FF7C98C34.png

1660980030767_114D31BE-0389-4f3d-A3D5-7FC8E4C7085F.png

保存菜品

1660985114086_A1E8ED66-4447-456d-AD29-15B6460038DC.png 1660984789354_2836931E-EE37-4640-87B4-98C6065C1964.png

1660988184279_ECEA4A25-9C07-4478-B05B-BAB79BD8AA0B.png

多表操作记得开启事物

1660988418199_EB97901B-8CB1-4ce8-8A95-375C1C81899F.png

1660988429322_B4E8AD59-25CF-4a30-9302-179D67FD2B5C.png

菜品分页

dcfda5f2653c2ca42075cccb027a7f8.png

def7fcaecaf99856569995ff256594b.jpg

2ae844023906fe41d7c6fe8d405afaf.png

32c896e857a693c9b0ef9d50b4b535f.png 所以要进行转换

@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);
}

菜品修改

64e2b9c77ca6374221dfb8c017b0129.png

f546d1f158d9db333568b33846f73b2.png

自定义查询同时查询菜品和口味 用于回显数据

@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 套餐管理业务开发

新增套餐

964c1631e1afc17f85fd3fcec160cf1.png 显示菜品 0bcd05bb1d7870914892486c86c19f7.png

/**
 * 根据条件查询对应的菜品数据
 */
@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);
}

分页

bc668de107be6d65e0fe29f29ad73af.png

1661348130590.png 0f46a4018847c8399164fc0b466a0b1.png

/**
 * 分页查询
 */
@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 手机发送验证码登录

88bb05eee80f15cc676997032973363.png

236170b64e43df92d96fe49d1d506a6.png

2c71755b4be769ae203da76413b1b7f.png

6e732578597326cdeccad6b1a349e68.png

lQLPJxafoCO9DdDNAh3NBIKwgMMHLbPVZPsDBpjHagCWAA_1154_541.png

lQLPJxafoNVcPqjNAfrNArawfGPtPpWCinMDBpnqbcCEAA_694_506.png

lQLPJxafoPRa1GbNAgzNAt6w0DVuyI9AZ60DBpod-oBCAA_734_524.png

lQLPJxafoSedYE7NAlnNA9uwJJr2opBK5sADBppxQgCJAA_987_601.png

lQLPJxafoZjdFmjNAlvNBIWwkgFqFctXr3cDBpsrdsCEAA_1157_603.png

lQLPJxafogHEjtnNAnPNBNawZhXyQ_B_fmgDBpvXaYCHAA_1238_627.png

lQLPJxafohmcEDPNARHNAimwycFITGPCRvQDBpv-BUCHAA_553_273.png

lQLPJxafpDr9-m7NAbvNAhywkDhGRXaWOfUDBp97R0AsAA_540_443.png

lQLPJxafpLeQ2njNAd_NA42wsScRp7z6IU0DBqBHsoBjAA_909_479.png

lQLPJxafp6cuMRLNAW_NA3iwxJQUHohowMQDBqUXmgDSAA_888_367.png

ea08d75dc125c7b63b0e2dd83d67385.jpg

0a2ed8f8f38b4a48dcfcf8b8c212b44.png

lQLPJxafr2uXYiHNArLNA-qw2grTkYhHlbgDBrHQuUAnAA_1002_690.png

导入用户地址簿

8c6d66b7ab086b77f9524e7adb9bbb0.png

0f8f728449cafbbdf6da884d2d58fdb.png

32330d2f64e6dbee54dc388041dfe76.png

设置默认地址

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);
}

移动端菜品展示

7b56a232cd1caf9a69b31664f1b4e62.png

请求路径

9f8d3c1df3b30a2db35cb7a30cda061.png

2b84d6986808d205212dba2bc15f994.png

初始化函数

2ec4db583be1a8c3ee09831ae1dc61c.png

添加购物车

dc2517cedd06d010f7115de024c8e26.png

adac3145f845c73fdb6302ddb9cb67c.jpg

88652d11b37b30d06f540569feef06a.png

20b78f0f0aa98093d445de0a733b9bd.png

9eee6024babf624503ca072c43e3be4.png

6224aa0c80eb7afcbb8035cfd988e1e.png

51a3ab72d2c56b114b7f77ea0b7be08.png

eb5ab52b98f4fbc2b87fe3a024f1913.png

ea264408a5557ab043a064478745630.png

424db212f89ed5b356f326389829b4a.png

用户下单

a583323c667492031e1b564dae0571b.png

c76bc564a2f038e7a39ea6282a50d24.png

表单

352431e8cf21febbcb3fe41b213f1ab.png

b7b91c535c723e2acdef18a8183fbbf.png

首页面

46437039073aab0ecf70a8d37d91f84.png

49a3a9fdc237f8e7bf8f6a70ef99a05.png

初始化页面的两个请求

b88634692084d0527e850ca70b4ad0b.png

36cea3ec8a5e3e89d828f988b4d63d6.png

---------------项目优化------------

缓存相关的优化

1662879554973.png

环境搭建

image.png 自己创建redisTemplate 方便序列化 也可以直接使用StringRedisTemplate 1662882361383.png

缓存短信验证码_思路梳理&代码改造&功能测试

1662883759200.png 9333b85290b4f2607ee7ba544fea96c.png 设置值 26507a0ab8b8961d81fea0257ae1e13.png 获取值

1662885219471.png 删除值

466b44ec3074e0df32867b0fac1b2c5.png

缓存菜品

  • 每次点击分类都会查询一边数据库,导致性能下降
  • 要按分类进行菜品缓存
  • 要防止脏数据,当菜品信息发生改变时,要对缓存进行清空 image.png
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);

缓存菜品数据_清理缓存思路分析&代码改造并测试

fd8ecd3d7a7b84bb4899e03cae5d738.png

直接清理全部缓存

@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("新增菜品成功");
}

精确清理

1662901854956.png

上传到git并且合并到主分支

image.png

Spring Cache_框架介绍&常用注释

Spring Cache的基本包都在spring-context下所以不需要特意导入

image.png

1663139485268.png

1663139573412.png

CachePut注解

d6f7270399afe75f1c2a0ca81838029.jpg

CacheEvict注解

见下一章