【瑞吉外卖项目复写】基本部分复写笔记

Day1 瑞吉外卖项目概述

mysql的数据源配置

spring:datasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/regie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: root

注意要配置mysql其实是配置druid数据源。

注意url的后缀。

mybatisplus配置

mybatis-plus:configuration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: assign_id

①log-impl:在MybatisPlus中,log-impl是用于配置mybatis的日志实现方式的属性。log-impl属性允许您指定mybatis再执行sql语句时使用哪种日志实现。

其中“org.apache.ibatis.logging.stdout.StdOutImpl”是mybatis提供的一种日志实现,它将日志信息输出到标准输出(控制台)。

②assign-id:在mybatisplus中,global-config是全局配置的一部分,用于配置一些全局的属性和策略。在global-config中,db-config是数据库配置的子属性,用于配置数据库相关的一些选项。

具体来说,id-type是db-config的子属性,用于指定主键id的生成策略。

1.auto:自增逐渐,使用与数据库自增长类型的字段(如mysql的auto_increment)

2.input:用户输入主键值,用户手动输入主键的值

3.assign-id:分配id主键,通过代码手动分配主键的值

4.assign-uuid:分配uuid主键,通过代码手动分配uuid类型的主键值

5.none:无主键生成策略,需要手动设置主键的值,不推荐使用

修改静态资源映射路径

如果前端资源不在static或template目录下,则需要修改静态资源映射路径

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");}
}

第一步:创建config类型的类继承WebMvcConfigurationSupport

第二步:重写addResourceHandlers方法

将前端资源通过addResourceHandler方法、addResourceLocations方法映射到静态资源路径

后台登录功能开发

Java实体类实现序列化

在Java中,实现Serializable接口是为了表明该类的对象可以被序列化。序列化是将对象转换为字节流的过程,以便对象存储在磁盘上或通过网络进行传输。

在实现Serializable接口时,并没有需要实现的抽象方法,它只是一个标记接口(Marker Interface),标志着该类的对象是可以序列化的。

private static final long serialVersionUID=1L:

是在实现Serializable接口的类中顶一个序列化版本号(Serialization Version UID)。这个版本号是为了确保序列化和反序列化过程中的兼容性。

比如对于如下的MyClass类实现了Serializable接口,并显示的设置了serialVersionUID的值为123456789L。这样,当MyClass类发生变化时,版本号将保持一致,从而确保序列化和反序列化的兼容性。

import java.io.Serializable;public class MyClass implements Serializable{private static final long serialVersionUID=123456789L;//类的其他成员和方法private String name;private int age;}

封装通用响应类

在这个类中,泛型<T>被用作数据的类型参数,允许在运行中指定具体的数据类型。这使得R类在返回数据时可以根据实际需要返回不同类型的数据,而不限于特定类型。

其中map是一个HashMap对象,用于在响应中存储其他键值对的附加信息。

其中add(String key,Object value)实例方法,用于向响应中的map添加附加信息。它接收一个字符串key和一个对象value,将键值对添加到map中,并返回当前R<T>对象本身。这使得可以链式调用该方法来添加多个键值对。

public class R<T> {private int code;private String errMsg;private T data;private Map map=new HashMap();public static <T> R<T> success(T object){R<T> tr = new R<>();tr.data=object;tr.code=1;return tr;}public static <T> R<T> error(String msg){R<T> tr = new R<>();tr.errMsg=msg;tr.code=0;return tr;}public R<T> add(String key,Object value){this.map.put(key,value);return this;}
}

编写Controller报错

居然是因为依赖有问题:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.10</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.itheima</groupId><artifactId>reggie_take_out</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>compile</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.31</version><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.23</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.5</version></plugin></plugins></build></project>

备注:scope的用法

  1. compile(默认值):这是最常用的scope,表示该依赖在编译、测试、运行和打包时都是可见的。这意味着依赖将被包含在生成的JAR或WAR文件中,并且对所有阶段都是可用的。

  2. provided:该依赖在编译和测试阶段是可见的,但在运行和打包阶段不会包含在生成的JAR或WAR文件中。它假设运行时环境中已经存在该依赖,比如Java EE容器中的一些API,例如Servlet API、JSP API等。

  3. runtime:该依赖在运行和打包阶段是可见的,但在编译和测试阶段不会包含在生成的JAR或WAR文件中。它表示该依赖只在运行时才需要,例如数据库驱动。

  4. test:该依赖只在测试阶段可见,不会包含在生成的JAR或WAR文件中,它用于测试时所需的依赖。

  5. system:类似于provided,但需要明确指定依赖的路径。这样的依赖将不从Maven仓库获取,而是从本地文件系统中的特定路径加载。一般不推荐使用此scope,除非你确实需要。

  6. import:该scope用于定义一个依赖POM的依赖。它表示该依赖将被传递到项目中,并且不会用于构建项目本身。

通过合理使用scope属性,可以帮助优化项目的依赖管理,减少不必要的依赖传递和构建时的冗余。例如,对于只在编译时使用的依赖,可以设置为provided,从而在运行时不包含这些依赖,减小了最终生成的包的大小。

Day2 员工业务管理开发

完善登录功能

现存问题:即使没有登陆也可以直接访问index页面

改进思路:添加Filter

改进步骤:①实现Filter接口

                  ②重写doFilter方法

                  注意:1.匹配路径需要用到路径匹配器AntPatchMatcher。

                                匹配规则:?匹配一个字符

                                                  * 匹配任意字符序列,但不包括路径分隔符

                                                  ** 匹配任意字符序列,包含路径分隔符

                                在使用antPatchMatcher的时候,可以用match()方法进行匹配

                             2.获取请求路径用httpServletRequest.getRequestURI()方法

                             3.如果用户没有登陆,因为doFilter方法的返回值为void,所以应该用response的输出流返回响应数据。

                                       response.getWriter().write(JSON.toJSONString(R.error("NOT LOGIN")));

                        ③完成注解标注

                                i.要在Filter上方标注@WebFilter注解。其中filterName唯一,urlPatterns="/*"代表Filter将过滤所有HTTP请求,即对所有的请求进行拦截和处理。

                                ii.要在启动类上标注@ServletComponentScan,才能扫描到Filter

代码实现:

@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
public class LoginCheckFilter implements Filter {private AntPathMatcher PATCH_MATCHER=new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;//放行不需要被检查的资源String requestURI = request.getRequestURI();boolean check = checkURI(requestURI);if(check){filterChain.doFilter(request,response);return;}//判断用户是否登录,登录则直接放行if(request.getSession().getAttribute("employee")!=null){filterChain.doFilter(request,response);return;}if(request.getSession().getAttribute("user")!=null){filterChain.doFilter(request,response);return;}//如果未登录,则通过输出流方式向客户端响应数据response.getWriter().write(JSON.toJSONString(R.error("NOT LOGIN")));}private boolean checkURI(String requestURI){String[] uris=new String[]{"/employee/login","/employee/logout","/user/sendMsg","/user/login","/backend/**","/front/**"};for(String uri:uris){boolean match = PATCH_MATCHER.match(uri, requestURI);if(match){return true;}}return false;}
}

新增员工

对于新增员工,由于账号应该唯一不重复,所以如果账号重复会抛出异常:

可以编写全局异常处理器来解决这个问题:

 编写GlobalExceptionHandler

        1.@ControllerAdvice注解用于声明一个全局异常处理器类

                annotations属性指定了该全局异常处理器只处理带有@RestController或@Controller注解的控制器类(Controller)抛出的异常

        2.@ResponseBody注解,用于表示方法的返回值将直接作为响应体(Response Body)返回给客户端,而不会被视图解析器处理

          在全局异常处理器中,通过添加@ResponseBody注解,确保异常处理方法的返回值会被转换为JSON格式并返回给客户端

@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
public class GlobalExceptionHandler {@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R<String> handleCustomException(SQLIntegrityConstraintViolationException ex){if(ex.getMessage().contains("Duplicate entry")){String[] split = ex.getMessage().split(" ");String msg = split[2] + "已存在";return R.error(msg);}return R.error("未知错误");}
}

员工信息分页查询

第一步:添加mybatisplus分页器

@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor;}}

第二步:编写Controller

    @GetMapping("/page")public R<Page<Employee>> getByPage(@RequestParam int page, @RequestParam int pageSize,@RequestParam String name){Page<Employee> employeePage = new Page<>(page,pageSize);LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(StrUtil.isNotEmpty(name),Employee::getName,name);queryWrapper.orderByDesc(Employee::getUpdateTime);employeeService.page(employeePage);return R.success(employeePage);}

备注:加与不加@RequestParam的区别

①不加@RequestParam前端的参数名需要和后端控制器的变量名保持一致才能生效

②不加@RequestParam参数为非必传,加@RequestParam则参数为必传。但是@RequestParam可以通过@RequestParam(required=false)设置为非必传

③@RequestParam可以通过@RequestParam("userId")或者@RequestParam(value="userId")指定传入的参数名(最主要的作用)

④@RequestParam可以通过@RequestParam(defaultValue="0")指定参数默认值

⑤如果接口除了前端调用还有后端RPC调用,则不能省略@RequestParam,否则RPC会找不到参数报错

⑥Get方式请求,参数放在url中时:

        不加@RequestParam注解:url可带参数也可不带参数,输入localhost:8080/list1以及localhost:8080/list1?userId=xxx方法都能执行

        加@RequestParam注解:url必须带有参数。也就是说你直接输入localhost:8080/list2会报错,不会执行方法。只能输入localhost:8080/list2?userId=xxx才能执行相应的方法

员工启用和禁用

在员工启用和禁用功能中,虽然后台已经修改了员工的状态,但是前台却不会显示出来。这是因为前台将整型以数值型类型读出,出现了精度丢失,导致员工id与后台id不一致。

此外,前台对时间的读取不方便阅读,也可以通过自定义的JacksonObjectMapper进行自定义的序列化和反序列化。

第一步:编写JacksonObjectMapper

①在默认情况下,Jackson对象映射器(ObjectMapper)在进行反序列化时,会尝试根据需要自动将字符串类型转换为其他数据类型,包括Long类型。这个转换是基于目标属性的数据类型和字符串内容进行判断的。

例如,如果目标属性是Long类型,而JSON中的对应值是一个合法的表示长整型的字符串,那么Jackson会自动将该字符串转换为Long类型。

②this.configure(FAIL_ON_UNKNOWN_PROPERTIES,false);这个配置是针对整个ObjectMapper对象的,它会将整个ObjectMapper实例的FAIL_ON_UNKNOWN_PROPERTIES设置为false,意味着该ObjectMapper在进行序列化和反序列化时,都不会报告未知属性的异常。

this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);这个配置是在进行反序列化时,针对当前ObjectMapper实例的DeserializationConfig对象,将其中的 "FAIL_ON_UNKNOWN_PROPERTIES" 设置为 false。这样,仅针对当前的 ObjectMapper,反序列化操作在遇到未知属性时才不会抛出异常。

③区别:

如果你只配置 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); 而不配置 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);,会产生如下影响:

  1. 序列化时的影响: 配置了 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); 后,在进行序列化时,无论是哪个 ObjectMapper 实例,都不会因为遇到未知属性而抛出异常。如果你的序列化操作中包含了未知属性,那么在序列化过程中,这些未知属性会被忽略,不会导致序列化失败。

  2. 反序列化时的影响: 配置了 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); 对反序列化的影响是不明显的。因为这个配置是针对整个 ObjectMapper 对象的,而在反序列化过程中,通常会使用局部的 DeserializationConfig 对象,例如 this.getDeserializationConfig(),而并不直接使用全局配置。所以,在反序列化时,未知属性是否会导致异常取决于局部的 DeserializationConfig 配置,而不是全局的配置。如果局部的 DeserializationConfig 也禁用了未知属性异常(即 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);),那么在反序列化时也会忽略未知属性,否则仍然可能抛出异常。

因此,如果你只配置了 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);,并没有配置 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);,那么在序列化时未知属性会被忽略,但在反序列化时未知属性可能仍然会导致异常,具体取决于反序列化时局部的 DeserializationConfig 配置。如果你希望在序列化和反序列化时都忽略未知属性,建议两个配置都使用。

ToStringSerializer.instance 是 Jackson 库中的一个特殊的序列化器对象,用于将对象的值以字符串形式进行序列化。

在默认情况下,Jackson 库会根据对象的实际类型进行序列化,并输出相应的 JSON 格式。例如,对于 Java 对象的整数属性,Jackson 会将其序列化为 JSON 中的数值类型(例如整数),而对于字符串属性,Jackson 会将其序列化为 JSON 中的字符串类型。

然而,有时候我们希望将某些属性以字符串形式进行序列化,而不是根据实际类型进行序列化。这时,可以使用 ToStringSerializer.instance 来达到这个目的。

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

第二步:重写WebMvcConfig类的extendMessageConverters方法

记得将自定义的ObjectMapper对应的消息转换器放在第一个优先使用。

    @Overrideprotected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();messageConverter.setObjectMapper(new JacksonObjectMapper());converters.add(0,messageConverter);}

Day3 分类管理业务开发

公共字段填充

在后台系统的员工管理功能开发中,新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时,也需要设置修改时间和修改人等字段。这些字段属于公共字段,也就是很多表中都有这些字段。

我们可以用mybatisplus提供的公共字段自动填充功能统一处理。

第一步:编写通用工具类封装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();}
}

第二步:在LoginCheckFilter中为已登录的用户添加id到ThreadLocal

        //判断用户是否登录,登录则直接放行if(request.getSession().getAttribute("employee")!=null){Long empId =(Long) request.getSession().getAttribute("employee");BaseContext.setCurrentId(empId);filterChain.doFilter(request,response);return;}if(request.getSession().getAttribute("user")!=null){Long userId =(Long) request.getSession().getAttribute("user");BaseContext.setCurrentId(userId);filterChain.doFilter(request,response);return;}

第三步:自定义类实现接口MetaObjectHandler,实现公共字段自动填充

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {metaObject.setValue("createTime", LocalDateTime.now());metaObject.setValue("updateTime", LocalDateTime.now());Long currentId = BaseContext.getCurrentId();metaObject.setValue("createUser", currentId);metaObject.setValue("updateUser", currentId);}@Overridepublic void updateFill(MetaObject metaObject) {metaObject.setValue("updateTime", LocalDateTime.now());Long currentId = BaseContext.getCurrentId();metaObject.setValue("updateUser", currentId);}
}

第四步:删除EmployeeController中创建时间、创建人、修改时间、修改人相关的冗余代码

删除分类

删除分类的时候需要检查该分类是否关联了菜品或者套餐,若关联应该抛出异常

第一步:自定义删除异常

public class CustomDeleteException extends RuntimeException{public CustomDeleteException(String message){super(message);}}

第二步:注册自定义删除异常

    @ExceptionHandler(CustomDeleteException.class)public R<String> handleCustomDeleteException(CustomDeleteException ex){return R.error(ex.getMessage());}

第三步:自定义删除方法

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {@Autowiredprivate DishService dishService;@Autowiredprivate SetmealService setmealService;@Overridepublic void deleteCategory(Long ids) {LambdaQueryWrapper<Dish> dishQueryWrapper = new LambdaQueryWrapper<>();dishQueryWrapper.eq(Dish::getCategoryId,ids);int countDish = dishService.count(dishQueryWrapper);if(countDish>0){throw new CustomDeleteException("该分类含菜品,无法删除");}LambdaQueryWrapper<Setmeal> setmealQueryWrapper = new LambdaQueryWrapper<>();setmealQueryWrapper.eq(Setmeal::getCategoryId,ids);int countSetmeal = setmealService.count(setmealQueryWrapper);if(countSetmeal>0){throw new CustomDeleteException("该分类含套餐,无法删除");}this.removeById(ids);}
}

第四步:Controller调用自定义删除方法

    @DeleteMappingpublic R<String> delete(Long ids){categoryService.deleteCategory(ids);return R.success("删除分类成功");}

Day4 菜品管理业务开发

文件上传下载

文件上传:

前端要求:①表单提交,method="post" ②enctype="multipart/form-data" ③type="file"

后端要求:使用MultipartFile作为形参类型接收上传的文件

file.transferTo()方法,将文件上传到服务器指定位置

文件下载:

图片以流的形式读出并写回网页

@RestController
@RequestMapping("/common")
public class CommonsController {@Value("${reggie.path}")private String basePath;@PostMapping("/upload")public R<String> upload(MultipartFile file){String originalFilename = file.getOriginalFilename();String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));String prefix = IdUtil.simpleUUID();String filename = prefix+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);}@GetMapping("/download")public void download(String name, HttpServletResponse response){try {FileInputStream fileInputStream = new FileInputStream(basePath + name);ServletOutputStream outputStream = response.getOutputStream();response.setContentType("image/jepg");int len = 0;byte[] bytes = new byte[1024];while ((len = fileInputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, len);outputStream.flush();}fileInputStream.close();outputStream.close();}catch (Exception e){e.printStackTrace();}}
}

新增菜品

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据

注意:因为要同时操作两张表,所以需要在方法上加上注解@Transactional,同时在启动类上加注解@EnableTransactionManagement

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {@Autowiredprivate DishFlavorService dishFlavorService;@Override@Transactionalpublic void saveWithFlavor(DishDTO dishDTO) {System.out.println(dishDTO.getId());this.save(dishDTO);System.out.println(dishDTO.getId());Long dishId = dishDTO.getId();List<DishFlavor> dishFlavors = dishDTO.getDishFlavors();dishFlavors = dishFlavors.stream().map(item -> {item.setDishId(dishId);return item;}).collect(Collectors.toList());dishFlavorService.saveBatch(dishFlavors);}
}

我添加了两条打印菜品ID的语句:

 由此可见,尽管传递过来的数据菜品ID为空,但是在保存菜品到数据库以后,会将菜品ID返回至dishDTO实体类中,并可以通过dishDTO.getId()得到菜品的ID

菜品信息分页查询

注意不能在DishServiceImpl注入CategoryService,因为之前已经在CategoryService中注入过DishServiceImpl了。

解决方法:直接在DishController中写分页信息查询:

        因为页面需要的是CategoryName而非CategoryId,所以需要用categoryService查询

        返回的DishDTO里包含categoryName属性

        注意DishDTO作为一种传输手段,只需要满足需要的属性不为空即可,这里用不到DishFlavor,可以为空

    @GetMapping("/page")public R<Page<DishDTO>> getByPage(int page,int pageSize,String name) {Page<Dish> dishPage = new Page<>(page, pageSize);LambdaQueryWrapper<Dish> dishQueryWrapper = new LambdaQueryWrapper<>();dishQueryWrapper.like(name != null, Dish::getName, name);dishQueryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);dishService.page(dishPage, dishQueryWrapper);Page<DishDTO> dishDTOPage = new Page<>();BeanUtils.copyProperties(dishPage, dishDTOPage, "records");List<Dish> dishRecords = dishPage.getRecords();List<DishDTO> dishDTOList = dishRecords.stream().map(item -> {DishDTO dishDTO = new DishDTO();BeanUtils.copyProperties(item, dishDTO);Long categoryId = item.getCategoryId();String categoryName = categoryService.getById(categoryId).getName();dishDTO.setCategoryName(categoryName);return dishDTO;}).collect(Collectors.toList());dishDTOPage.setRecords(dishDTOList);return R.success(dishDTOPage);}

修改菜品

第一步:菜品内容回显

    @Overridepublic DishDTO editWithFlavor(Long id) {DishDTO dishDTO = new DishDTO();Dish dish = this.getById(id);BeanUtils.copyProperties(dish,dishDTO);LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(DishFlavor::getDishId,id);List<DishFlavor> dishFlavors = dishFlavorService.list(queryWrapper);dishDTO.setDishFlavors(dishFlavors);return dishDTO;}

        注意前后端内容传递与接收,前台需要用res.data.dishFlavors接收后台传递的dishFlavors,如果接收不到的话回显是会失败的

第二步:修改菜品信息

    @Overridepublic void updateWithFlavor(DishDTO dishDTO) {this.updateById(dishDTO);LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(DishFlavor::getDishId,dishDTO.getId());dishFlavorService.remove(queryWrapper);List<DishFlavor> dishFlavors = dishDTO.getDishFlavors();dishFlavors=dishFlavors.stream().map(item->{item.setDishId(dishDTO.getId());return item;}).collect(Collectors.toList());dishFlavorService.saveBatch(dishFlavors);}

Day5 套餐业务管理开发

删除套餐

在套餐管理列表页面点击删除按钮,可以删除对应的套餐信息。也可以通过复选框选择多个套餐,点击批量删除按钮一次删除多个套餐。 注意,对于状态在售卖中的套餐不能删除,需要先停售,然后才能删除。

    @Override@Transactionalpublic void deleteWithDish(List<Long> ids) {LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();setmealLambdaQueryWrapper.in(Setmeal::getId,ids).eq(Setmeal::getStatus,1);int count = this.count(setmealLambdaQueryWrapper);if(count>0){throw new CustomDeleteException("套餐正在售卖中,不能删除");}this.removeByIds(ids);LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.in(SetmealDish::getSetmealId,ids);setmealDishService.remove(queryWrapper);}

注意,当接收的参数不是基本类型也不是实体类的时候,应该使用@RequestParam注解

    @DeleteMappingpublic R<String> delete(@RequestParam List<Long> ids){setmealService.deleteWithDish(ids);return R.success("删除套餐成功");}

手机验证码登录

第一步:发送验证码

第二步:登录

优化:存储“code”的时候,拼接了phone-code,这样就能避免传递过来code正确,而phone悄悄改了的问题

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/sendMsg")public R<String> getCode(@RequestBody User user, HttpSession session){String phone = user.getPhone();String code = RandomUtil.randomNumbers(6);session.setAttribute("code", phone+"-"+code);return R.success(code);}@PostMapping("/login")public R<User> login(@RequestBody UserDTO userDTO, HttpSession session){String phone = userDTO.getPhone();String code = userDTO.getCode();String testCode =(String) session.getAttribute("code");if(testCode==null){return R.error("验证码已失效");}code = phone+"-"+code;if(!testCode.equals(code)){return R.error("验证码或手机号有误");}LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getPhone, phone);User one = userService.getOne(queryWrapper);if(one == null){one=new User();one.setPhone(phone);userService.save(one);}session.setAttribute("user", one.getId());return R.success(one);}}

Day6 菜品展示、购物车、下单

设置默认地址

第一步:将收件人的所有地址改为非默认

第二步:通过updateById()方法将指定收件地址改为默认

    @PutMapping("/default")public R<AddressBook> setDefault(@RequestBody AddressBook addressBook){Long userId = BaseContext.getCurrentId();LambdaUpdateWrapper<AddressBook> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.set(AddressBook::getIsDefault,0).in(AddressBook::getUserId,userId);addressBookService.update(updateWrapper);addressBook.setIsDefault(1);addressBookService.updateById(addressBook);return R.success(addressBook);}

菜品展示

前端会根据返回的结果是否含有flavors做判断,从而对没有口味选择的菜品展示【+】,对有口味选择的菜品展示【选规格】。所以只需要改造listDishes,将返回值改为R<List<DishDTO>>,并对每一个DishDTO填充flavors(如果有)即可。

菜品:

    @GetMapping("/list")public R<List<DishDTO>> listDishes(Long categoryId){LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Dish::getCategoryId,categoryId);queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);List<Dish> dishList = dishService.list(queryWrapper);List<DishDTO> dishDTOList = dishList.stream().map(item -> {DishDTO dishDTO = new DishDTO();BeanUtils.copyProperties(item, dishDTO);Category category = categoryService.getById(categoryId);if (category != null) {dishDTO.setCategoryName(category.getName());}LambdaQueryWrapper<DishFlavor> wrapper = new LambdaQueryWrapper<>();wrapper.eq(DishFlavor::getDishId, item.getId());List<DishFlavor> flavors = dishFlavorService.list(wrapper);dishDTO.setFlavors(flavors);return dishDTO;}).collect(Collectors.toList());return R.success(dishDTOList);}

套餐:

    @GetMapping("/list")public R<List<Setmeal>> list(Setmeal setmeal){LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(setmeal.getCategoryId()!=null,Setmeal::getCategoryId,setmeal.getCategoryId());queryWrapper.orderByDesc(Setmeal::getUpdateTime);List<Setmeal> list = setmealService.list(queryWrapper);return R.success(list);}

将菜品/套餐添加至购物车

将菜品/购物车添加至购物车的时候需要判断是否为第一次添加,如果不是则只修改数量

要区分是哪个用户添加的

    @PostMapping("/add")public R<ShoppingCart> save(@RequestBody ShoppingCart shoppingCart){Long userId = BaseContext.getCurrentId();shoppingCart.setUserId(userId);LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ShoppingCart::getUserId,userId);Long dishId = shoppingCart.getDishId();if(dishId!=null){queryWrapper.eq(ShoppingCart::getDishId,dishId);}else{queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());}ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);if(cartServiceOne!=null){Integer number = cartServiceOne.getNumber();cartServiceOne.setNumber(number+1);shoppingCartService.updateById(cartServiceOne);}else{shoppingCart.setNumber(1);shoppingCartService.save(shoppingCart);cartServiceOne=shoppingCart;}return R.success(cartServiceOne);}

用户下单

@Service
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements OrdersService {@Autowiredprivate ShoppingCartService shoppingCartService;@Autowiredprivate UserService userService;@Autowiredprivate AddressBookService addressBookService;@Autowiredprivate OrderDetailService orderDetailService;public OrdersServiceImpl() {}@Override@Transactionalpublic void submit(Orders orders) {//获得当前用户idLong currentId = BaseContext.getCurrentId();//查询当前用户的购物车数据LambdaQueryWrapper<ShoppingCart> shoppingCartLambdaQueryWrapper = new LambdaQueryWrapper<>();shoppingCartLambdaQueryWrapper.eq(ShoppingCart::getUserId,currentId);List<ShoppingCart> shoppingCarts = shoppingCartService.list(shoppingCartLambdaQueryWrapper);if(shoppingCarts==null || shoppingCarts.size()==0){throw new CustomDeleteException("购物车为空,不能下单!");}//查询用户数据User user = userService.getById(currentId);//查询地址数据Long addressBookId = orders.getAddressBookId();AddressBook addressBook = addressBookService.getById(addressBookId);if(addressBook==null){throw new CustomDeleteException("用户地址信息有误,不能下单!");}//向订单表插入数据,一条数据long orderId = IdWorker.getId();AtomicInteger amount=new AtomicInteger(0);List<OrderDetail> orderDetails=shoppingCarts.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.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(user.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);//向订单明细表插入数据,多条数据orderDetailService.saveBatch(orderDetails);//清空购物车数据shoppingCartService.remove(shoppingCartLambdaQueryWrapper);}}

 


复写部分基本完成~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/56289.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 HAL 驱动PM2.5传感器(GP2Y10AU气体检测模块)

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 ADC外设配置 2.3 串口外设配置 2.4 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 ADC数据采集 3.3 主函数代 3.4 效果展示 1、简介 本文通过STM32F103C8T6单片机通过HAL库方式对G…

Element-plus中tooltip 提示框修改宽度——解决方案

tooltip 提示框修改宽度方法&#xff1a; 在element中&#xff0c;想要设置表格的内容&#xff0c;超出部分隐藏&#xff0c;鼠标悬浮提示 可以在el-table 上添加show-overflow-tooltip属性 同时可以通过tooltip-options配置提示信息 如下图代码 <el-tableshow-overflo…

中信银行与华为联合编制:《2023金融数据可信流通技术白皮书》

导读 中信银行与华为技术有限公司联合编制的《金融数据可信流通技术白皮书》&#xff0c;从技术、业务、管理、法律等维度探讨了金融业数据流通的现状、问题、机遇和挑战&#xff0c;并提出了一套金融业数据流通的技术解决方案。该方案基于华为OceanStor存储&#xff0c;结合…

前台自动化测试:基于敏捷测试驱动开发(TDD)的自动化测试原理

一、自动化测试概述 自动化测试主要应用到查询结果的自动化比较&#xff0c;把借助自动化把相同的数据库数据的相同查询条件查询到的结果同理想的数据进行自动化比较或者同已经保障的数据进行不同版本的自动化比较&#xff0c;减轻人为的重复验证测试。多用户并发操作需要自动…

前端下载文化部几种方法(excel,zip,html,markdown、图片等等)和导出 zip 压缩包

文章目录 1、location.href2、location.href3、a标签4、请求后端的方式5、文件下载的方式6、Blob和Base647、下载附件方法(excel,zip,html,markdown)8、封装下载函数9、导出 zip 压缩包相关方法(流方式) 总结 1、location.href //get请求 window.location.href url;2、locati…

APP外包开发的开发语言对比

在开发iOS APP时有两种语言可以选择&#xff0c;Swift&#xff08;Swift Programming Language&#xff09;和 Objective-C&#xff08;Objective-C Programming Language&#xff09;&#xff0c;它们是两种不同的编程语言&#xff0c;都被用于iOS和macOS等苹果平台的软件开发…

Vue3 列表渲染简单应用

去官网学习→列表渲染 | Vue.js 运行示例&#xff1a; 代码&#xff1a;HelloWorld.vue <template><div class"hello"><h1>Vue 列表渲染</h1><p v-for"item in dataList">{{item}}</p><p v-for"(item,index)…

vscode连接远程Linux服务器

文章目录 一、环境安装1.1 下载vscode1.2 下载vscode-sever 二、ssh链接2.1 安装Remote-SSH2.2 设置vscode ssh2.3 设置免密登录2.3.1 本地生成公私钥2.3.2 服务器端添加公钥 三、安装插件3.1 vscode安装插件3.1.1 在线安装插件3.1.2.1 下载插件3.1.2.2 安装插件 3.2 vscode-se…

Grafana集成prometheus(2.Grafana安装)

查找镜像 docker search grafana下载指定版本 docker pull grafana/grafana:10.0.1启动容器脚本 docker run -d -p 3000:3000 --namegrafana grafana/grafana:10.0.1查看是否启动 docker ps防火墙开启 检查防火墙3000端口是否开启 默认用户及密码 admin/admin 登录 ht…

cocos creator 的input.on 不生效

序&#xff1a; 1、执行input.on的时候发现不生效 2、一直按控制台也打印不出来console.log 3、先收藏这篇&#xff0c;因为到时候cocos要开发serveApi的时候&#xff0c;你得选一款趁手的后端开发并且&#xff0c;对习惯用ts写脚本的你来说&#xff0c;node是入门最快&#xf…

上海亚商投顾:沪指缩量调整 超导概念逆势大涨

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 沪指今日低开低走&#xff0c;深成指、创业板指盘中均跌超1%。医药医疗股全线调整&#xff0c;丽珠集团跌停&#…

EtherCAT转EtherCAT网关FX5U有EtherCAT功能吗两个ETHERCAT设备互联

1.1 产品功能 捷米JM-ECT-ECT是自主研发的一款ETHERCAT从站功能的通讯网关。该产品主要功能是将2个ETHERCAT网络连接起来。 本网关连接到ETHERCAT总线中做为从站使用。 1.2 技术参数 1.2.1 捷米JM-ECT-ECT技术参数 ● 网关做为ETHERCAT网络的从站&#xff0c;可以连接倍福、…