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

Day1 瑞吉外卖项目概述

mysql的数据源配置

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

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

注意url的后缀。

mybatisplus配置

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

①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 {
    @Override
    protected 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();
    @Override
    public 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 {
    @Bean
    public 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对应的消息转换器放在第一个优先使用。

    @Override
    protected 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 {
    @Override
    public 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);
    }

    @Override
    public 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 {
    @Autowired
    private DishService dishService;
    @Autowired
    private SetmealService setmealService;

    @Override
    public 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调用自定义删除方法

    @DeleteMapping
    public 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 {
    @Autowired
    private DishFlavorService dishFlavorService;

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

修改菜品

第一步:菜品内容回显

    @Override
    public 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,如果接收不到的话回显是会失败的

第二步:修改菜品信息

    @Override
    public 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
    @Transactional
    public 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注解

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

手机验证码登录

第一步:发送验证码

第二步:登录

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

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private 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 {
    @Autowired
    private ShoppingCartService shoppingCartService;
    @Autowired
    private UserService userService;
    @Autowired
    private AddressBookService addressBookService;
    @Autowired
    private OrderDetailService orderDetailService;

    public OrdersServiceImpl() {
    }

    @Override
    @Transactional
    public void submit(Orders orders) {
        //获得当前用户id
        Long 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.mfbz.cn/a/61853.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】深入浅出STL之vector类

文章篇幅较长&#xff0c;越3万余字&#xff0c;建议电脑端访问 文章目录 一、前言二、vector的介绍及使用1、vector的介绍2、常用接口细述1&#xff09;vector类对象的默认成员函数① 构造函数② 拷贝构造③ 赋值重载 2&#xff09;vector类对象的访问及遍历操作① operator[]…

windows永久暂停更新

目录 1.winr,输入regedit打开注册表 2.打开注册表的这个路径: 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键空白地方新建QWORD值命名为:FlightSettingsMaxPauseDays 3.双击FlightSettingsMaxPauseDays,修改里面的值为100000,右边基数设置…

区块链实验室(14) - 编译FISCO-BCOS

FISCO-BCOS是一种区块链平台&#xff0c;与Hyperledger和Ethereum有些不同&#xff0c;详见FISCO BCOS 区块链 编译FISCO BCOS源码的目的是修改或者新增其中功能模块&#xff0c;进行对比实验&#xff0c;验证新想法、新创意的效果。编译的步骤很简单&#xff0c;按技术文档一…

JavaWeb三大组件 —— Servlet

目录 servlet 注册servlet 父pom pom文件 1、通过注解注册 2、使用ServletRegistrationBean注册 API三生三世 第一生Servlet 第二生SpringMVC 今生SpringBoot servlet Servlet的作用&#xff1a; 接受请求参数、处理请求&#xff0c;响应结果&#xff0c;&#xff08;就…

flask中的应用上下文

flask中的应用上下文 Flask应用上下文主要包含两个对象&#xff1a;current_app和g。这两个对象在处理请求期间都是全局可访问的&#xff0c;但在每个请求结束时都会被重置。 current_app&#xff1a;这是当前激活的Flask应用的实例。在大多数情况下&#xff0c;你可以将其视为…

回归预测 | MATLAB实现SO-CNN-GRU蛇群算法优化卷积门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现SO-CNN-GRU蛇群算法优化卷积门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现SO-CNN-GRU蛇群算法优化卷积门控循环单元多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现SO-CNN-GRU蛇群算法优化卷积门控循…

go-zero超强工具goctl的常用命令api,rpc,model及其构建的服务解析

goctl api 详情移步&#xff1a; go-zero的路由机制解析 基于go-zero的api服务刨析并对比与gin的区别 goctl rpc goctl支持多种rpc&#xff0c;较为流行的是google开源的grpc&#xff0c;这里主要介绍goctl rpc protoc的代码生成与使用。 protoc是grpc的命令&#xff0c;作用…

uniapp uview文件上传的文件不是文件流,该如何处理?用了uni.chooseImage预览功能要如何做

在使用uniapp开发&#xff0c;运用的ui是用uview&#xff0c;这边需要做一个身份认证&#xff0c;如下图 使用的是uview的u-upload组件&#xff0c;可是这个组件传给后端的不是文件流 后端接口需要的是文件流格式&#xff0c;后面使用了uniapp的选择图片或者拍照的api&#x…

UltraToolBars Crack,动画菜单和多种显示样式

UltraToolBars Crack,动画菜单和多种显示样式 创建模仿Microsoft Office 2000外观的健壮应用程序。 UltraToolBars包括11个用于创建可自定义工具栏的界面增强控件&#xff0c;包括&#xff1a;个性化菜单、弹出型工具栏、集成选项卡控件等。PictureRegion技术使表单和组件能够采…

实现vscode上用gdb调试stm32

实现vscode上用gdb调试stm32 这周负责编写设备的某个模块&#xff0c;其中遇到了一些变量地址不正确的错误&#xff0c;按理这种底层变量错误用gdb一类的调试器就能很快查到&#xff0c;可是初入嵌入式一行&#xff0c;此C语言非彼C语言&#xff0c;对于gdb怎么对接到项目上根…

微服务系列<3>---微服务的调用组件 rpc 远程调用

什么是rpc调用,让我们调用远程方法就像调用本地方法一样 这就属于rpc调用 rpc是针对于本地来说的 调用远程方法根调用本地方法一样 如果能达到这种效果 就是rpc调用如果达到一种效果 调用远程和调用本地一样 他就是一种rpc框架2个微服务 之间发的调用 我们之前通过ribbon的方式…

【数模】预测模型

一、灰色系统 白色系统&#xff1a;系统信息完全明确灰色系统&#xff1a;系统部分信息已知&#xff0c;部分信息未知 对在一定范围内变化的、与时间有关的灰色过程进行预测。过程&#xff1a;原始数据找规律→生成强规律性的数据序列→建立微分方程来预测未来趋势 黑色系统&a…

JavaWeb项目工程结构介绍

介绍idea创建Web项目工程下的项目结构&#xff08;新建的web工程&#xff09; 了解目录大致作用 一级目录 :.idea、out、src、web、工程名.iml .idea目录&#xff1a;记录了IntelliJ IDEA 的配置目录&#xff0c;包含项目的配置信息、工程设置、构建配置等。它是用来存储项目…

谈谈网络安全

目录 1.概念 2.发展现状 3.主要问题 1.概念 网络安全是指保护计算机网络和其中的数据免受未经授权访问、损坏、窃取或破坏的过程和技术。网络安全涉及预防和检测潜在的威胁和漏洞&#xff0c;并采取措施保护网络的机密性、完整性和可用性。 网络安全的概念包括以下几个方面&am…

Vue系列第七篇:Element UI之el-main,el-table,el-dialog,el-pagination,el-breadcrumb等控件使用

本篇实现主页面功能&#xff0c;包括主页面排版布局&#xff0c;学生管理模块实现&#xff0c;后台接口实现等功能。 目录 1.运行效果 1.1登录页面 1.2主页面 1.3学生管理 - 信息列表 1.4学生管理 - 信息管理 1.5学生管理 - 作业列表 1.6学生管理 - 作业管理 2.前端代码…

开源元数据管理平台Datahub最新版本0.10.5——安装部署手册(附离线安装包)

大家好&#xff0c;我是独孤风。 开源元数据管理平台Datahub近期得到了飞速的发展。已经更新到了0.10.5的版本&#xff0c;来咨询我的小伙伴也越来越多&#xff0c;特别是安装过程有很多问题。本文经过和群里大伙伴的共同讨论&#xff0c;总结出安装部署Datahub最新版本的部署手…

Flowise AI:用于构建LLM流的拖放UI

推荐&#xff1a;使用NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景 什么是Flowise AI&#xff1f; Flowise AI是一个开源的UI可视化工具&#xff0c;用于帮助开发LangChain应用程序。在我们详细介绍 Flowise AI 之前&#xff0c;让我们快速定义 LangChain。LangChain是…

pytorch实战-图像分类(二)(模型训练及验证)(基于迁移学习(理解+代码))

目录 1.迁移学习概念 2.数据预处理 3.训练模型&#xff08;基于迁移学习&#xff09; 3.1选择网络&#xff0c;这里用resnet 3.2如果用GPU训练&#xff0c;需要加入以下代码 3.3卷积层冻结模块 3.4加载resnet152模 3.5解释initialize_model函数 3.6迁移学习网络搭建 3.…

迭代器模式(Iterator)

迭代器模式是一种行为设计模式&#xff0c;可以在不暴露底层实现(列表、栈或树等)的情况下&#xff0c;遍历一个聚合对象中所有的元素。 Iterator is a behavior design pattern that can traverse all elements of an aggregate object without exposing the internal imple…

SIFT算法原理:SIFT算法详细介绍

前面我们介绍了Harris和Shi-Tomasi角点检测算法&#xff0c;这两种算法具有旋转不变性&#xff0c;但不具有尺度不变性&#xff0c;以下图为例&#xff0c;在左侧小图中可以检测到角点&#xff0c;但是图像被放大后&#xff0c;在使用同样的窗口&#xff0c;就检测不到角点了。…