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

news2024/9/24 9:18:35

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.coloradmin.cn/o/839290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智慧工地云平台源码,基于微服务+Java+Spring Cloud +UniApp +MySql开发

智慧工地可视化系统利用物联网、人工智能、云计算、大数据、移动互联网等新一代信息技术&#xff0c;通过工地中台、三维建模服务、视频AI分析服务等技术支撑&#xff0c;实现智慧工地高精度动态仿真&#xff0c;趋势分析、预测、模拟&#xff0c;建设智能化、标准化的智慧工地…

MySQL数据库面试题:如何定位慢查询?

MySQL数据库面试题&#xff1a;如何定位慢查询&#xff1f; 面试官&#xff1a;MySQL中&#xff0c;如何定位慢查询&#xff1f; 候选人&#xff1a;嗯~&#xff0c;我们当时做压测的时候有的接口非常的慢&#xff0c;接口的响应时间超过了2秒以上&#xff0c;因为我们当时的系…

【关于反馈电路的放电问题】2022-1-16

缘由关于反馈电路的放电问题 - 电源技术论坛 - 电子技术论坛 - 广受欢迎的专业电子论坛!图中的副绕组反馈给三极管基极&#xff0c;一般都是说通过三极管充电正反馈三极管导通&#xff0c;放电时负反馈三极管截止&#xff0c;负反馈时&#xff0c;电容C3是通过哪个回路放电的呢…

基于Open3D的点云处理15-特征点

Intrinsic shape signatures (ISS) 参考 ISS关键点: 基本原理是避免在沿主要方向表现出类似分布的点上检测关键点&#xff0c;在这些点上无法建立可重复的规范参考框架&#xff0c;因此后续描述阶段很难变得有效。在剩余点中&#xff0c;显着性由最小特征值的大小决定,以便仅包…

2685. 统计完全连通分量的数量;2718. 查询后矩阵的和;1600. 王位继承顺序

2685. 统计完全连通分量的数量 核心思想&#xff1a;枚举所有的连通分量&#xff0c;然后判断这些连通分量是不是完全连通分量&#xff0c;完全连通分量满足边数2e 点数v(v-1)。 2718. 查询后矩阵的和 核心思想&#xff1a;后面的改变更重要&#xff0c;所以我们直接逆向思维…

无脑入门pytorch系列(二)—— torch.mean

本系列教程适用于没有任何pytorch的同学&#xff08;简单的python语法还是要的&#xff09;&#xff0c;从代码的表层出发挖掘代码的深层含义&#xff0c;理解具体的意思和内涵。pytorch的很多函数看着非常简单&#xff0c;但是其中包含了很多内容&#xff0c;不了解其中的意思…

Spring源码——初识Spring容器

Spring源码之工厂&#xff08;容器&#xff09; 为什么把Spring的工厂又叫做容器呢&#xff1f; 工厂的责任是创建对象&#xff0c;但是创建完对象后还要进行存储&#xff08;针对于单例的对象来讲&#xff09;&#xff0c;以供其他地方使用&#xff0c;这就是容器。为了能存…

STL学习

STL 泛化编程template函数模板类模板 iterator迭代器C array(STL array)容器 STL中文名为标准库,是C标准的规定并且提供了自己编写STL的接口&#xff0c;在编译器实现中统一的分成立几个容器头文件和几个其他的头文件来完成数据结构和算法的抽象&#xff0c;现在编译器使用的是…

FDM3D打印系列——超可动可变形机体打印

大家好&#xff0c;我是阿赵。继续来分享一下3D打印的成果。   这次打印的对象不得了&#xff0c;是超时空要塞系列的可变形VF战机。打印完这个模型&#xff0c;绝对是学习到了很多的东西&#xff0c;下面给大家分享一下。 一、成果展示&#xff1a; 不要怀疑&#xff0c;不…

《GPU并行计算与CUDA编程》笔记

第一个GPU程序 #include <stdio.h>__global__ void square(float* d_out,float* d_in){int idx threadIdx.x;float f d_in[idx];d_out[idx] f * f; }int main(int argc,char** argv){const int ARRAY_SIZE 8;const int ARRAY_BYTES ARRAY_SIZE * sizeof(float);// …

在linux调试进程PID的方法

当我们谈论调试 PID&#xff08;进程标识符&#xff09;时&#xff0c;我们通常是指诊断和解决与操作系统中的特定进程相关的问题。有许多工具和方法可用于调试 PID&#xff0c;以下是一些常见的方法&#xff1a; 1. 使用ps命令 ps命令是最基本的调试工具&#xff0c;用于查看…

python数据处理程序代码,如何用python处理数据

大家好&#xff0c;给大家分享一下python数据处理程序代码&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 要求&#xff1a;分别以james&#xff0c;julie&#xff0c;mikey&#xff0c;sarah四个学生的名字建立文本文件&#xff0c;分别存…

Win32 EditControl多行文本框自动换行,并在添加新行时自动将光标移到末尾

【文本框属性设置】 设为多行文本框&#xff1a;MultilineTrue 允许按回车键换行&#xff1a;Want ReturnTrue 自动换行&#xff1a;Auto HScrollFalse 在最后一行按回车键&#xff0c;自动向上滚动&#xff1a;Auto VScrollTrue 显示垂直滚动条&#xff1a;Vertical ScrollTru…

Cocos Creator的rigidBody.applyForce变成了滚动

序: 1、原因是因为没有调整摩擦系数physics-material 2、摩擦系数调整你要在你的节点 一个物理材料才会有的&#xff0c;教程没跳过去了所以没有 3、扩展阅读第一话&#xff1a;入行程序员的一波三折 最终效果&#xff1a; git录屏会卡&#xff0c;其实过程很平滑 正…

PostgreSQL和MySQL多维度对比

文章目录 0.前言1. 基础对比2.PostgreSQL和MySQL语法对比3. 特性4. 参考文档 0.前言 在当今的软件开发和数据管理领域&#xff0c;数据库是至关重要的基础设施之一。选择正确的数据库管理系统&#xff08;DBMS&#xff09;对于应用程序的性能、可扩展性和数据完整性至关重要。…

无涯教程-Perl - do...while 语句函数

与 for 和 while 循环不同&#xff0c;它在循环的顶部测试循环条件&#xff0c;而 do ... while 循环在以下位置检查其条件:循环的底部。 do ... while 循环与while循环相似&#xff0c;除了保证do ... while循环至少执行一次。 do...while - 语法 do {statement(s); }while(…

PintOS lab1 threads 实验记录

Background 首先完成这个实验我们需要理清线程怎么启动的和切换的 下面这张图可以大体表示线程状态的切换 让我们看看thread init的前世今生吧&#xff08;: 从start.S汇编调用了一个c函数 pintos_init初始化了一堆东西&#xff0c;当然里面也包括了thread啦 int pintos_in…

计算机网络(5) --- http协议

计算机网络&#xff08;4&#xff09; --- 协议定制_哈里沃克的博客-CSDN博客协议定制https://blog.csdn.net/m0_63488627/article/details/132070683?spm1001.2014.3001.5501 目录 1.http协议介绍 1.协议的延申 2.http协议介绍 3.URL 4.urlencode和urldecode 2.HTTP协…

langchain-ChatGLM源码阅读:参数设置

文章目录 上下文关联对话轮数向量匹配 top k控制生成质量的参数参数设置心得 上下文关联 上下文关联相关参数&#xff1a; 知识相关度阈值score_threshold内容条数k是否启用上下文关联chunk_conent上下文最大长度chunk_size 其主要作用是在所在文档中扩展与当前query相似度较高…

HCIP MPLS综合实验

目录 题目 实验步骤 第一步、IP地址规划 第二步、配置接口IP地址 第三步、IGP配置OSPF 第五步、公网配置MPLS 第五步、使用MPLS-VPN 第六步、R2-R4使用BGP建邻并实现VPN建邻 第六步、配置B静态路由 第七步、配置B动态路由 第八步、重发布 第九步、测试 题目 1、R1…