St. Regis takeaway study notes

1. Project overview

新手入门的SpringBoot+SSM企业级项目

1. Overall introduction to software development

1.1 Software development process

List
We generally at the encoding layer

1.2 Role Hierarchy

insert image description here

1.3 Software environment

①Development environment (development): the environment used by developers during the development phase, which cannot be accessed by general external users
②Testing environment (testing): an environment specially used by testers for testing projects, which
cannot be accessed by general external users
③Production environment ( production): that is, the online environment, an environment that formally provides external services

2. St. Regis takeaway project introduction

This project is divided into 3 phases for development:
① The first phase mainly realizes the basic needs, in which the mobile terminal application is implemented through H5, and users can access it through mobile browsers.
② The second phase is mainly aimed at improving the mobile terminal application, which is realized by the WeChat applet, which is more convenient for users to use.
③ The third phase is mainly aimed at optimizing and upgrading the system to improve the access performance of the system.

2.1 Product prototype display

①The product manager makes it after demand analysis, usually a web page.
②The product prototype is mainly used to show the function of the project, not the final page effect.

2.2 Technology selection

insert image description here

2.3 Functional Architecture

insert image description here

2.4 Roles

Background system administrator: log in to the background management system and have all the operation rights in the background system
General staff of the background system: log in to the background management system to manage dishes, packages, orders, etc.
C-end users: log in to the mobile application to browse dishes, Add shopping cart, set address, place order online, etc.

2. Project practice

Ⅰ. Staff management

1. Development environment setup

insert image description here

The first step is
to build the database environment and import the sql file

The second step
is to build the maven project: the skeleton creates maven.
pom.xml file

<?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 : 继承的意思
    此处继承父工程 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>reggie_take_out</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </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>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</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>

The third step
is to import the SpringBoot configuration file application.yml
and import application.yml under the resources package

server:
  port: 8080 #tomcat端口号
spring:
  application:
    name: reggie_take_out #指定应用的名称 非必须
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: root
mybatis-plus:
  configuration:
    #address book---->AddressBook
    #user_name---->userName
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

The fourth step
is to write the startup class, you can try to start

@Slf4j
//可以使用log方法,打印info级别日志
@SpringBootApplication
//引导类or启动类
@ServletComponentScan//扫描webFilter注解 进一步创建过滤器
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功。。。。");//打印info级别日志
    }
}

Note : Static resources are generally placed in directories such as webapp/static or webapp/resources. By default, look for static resources in this directory. Do not report errors directly.
Resolved : :config map file

@Slf4j
//可以使用log方法,打印info级别日志
@Configuration//声明该类是配置类
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /*
    静态资源文件应该都在webapp目录下
    本项目没有webapp目录
    需要设置静态资源映射
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始进行静态资源映射。。。");
      registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
      registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
      /*
      tomcat服务器访问路径映射到相应的静态文件下
      pathPatterns:服务器访问路径
      classpath:(指定类文件的路径)此处指读取静态资源的路径resources目录
       */
    }

2. Function development

1. Background login function development

Step 1:
Create the entity class Employee and map it with the employee table.
Development habits: One table in the database corresponds to one entity class.
Import directly from the data

Step 2:
Create Controller, Service, Mappe

//mapper接口
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {//泛型实体
}
//EmployeeService接口 
@Mapper
public interface EmployeeService extends IService<Employee> {//继承IService泛型实体
}
//EmployeeServiceImpl继承serviceImpl(对应mapper类,对应实体类)继承 EmployeeService接口
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}
//EmployeeController类
@Slf4j
@RestController
@RequestMapping("/employee")//路径
public class EmployeeController {

    //注入
    @Autowired
    private EmployeeService employeeService;

Step 3:
Import the return class R.
This class is a general result class. All results responded by the server will eventually be packaged into this type and returned to the front-end page.

/*
通用返回结果,限务端响应的数据最终部会封装成此对练

 */
@Data
public class R<T> {

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }
}

Step 4:
Create a login method in the Controller
Processing logic:
1. Encrypt the password password submitted on the page with md5
2. Query the database according to the user name username submitted on the page
3. Return the login failure result if not found
4. Password Compare, if inconsistent, return the result of login failure
5. Check the status of the employee, if it is disabled, return the result of the employee is disabled
6. Login successfully, save the employee id into the Session and return the result of successful login
insert image description here

@Slf4j
@RestController
@RequestMapping("/employee")//路径
public class EmployeeController {

    //注入
    @Autowired
    private EmployeeService employeeService;
/*
员工登录
 */
    @PostMapping("/login")
    //json在接收时,要有注解@RequestBody 分装成emplouee对象
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {
        //Employee的id存在Session中,表示成功。获取当前用户:request来get
/**
 *         1、将页面提交的密码password进行md5加密处理,已经封装到Employee中
 *         密码已经封装到employee中
 */
        String password = employee.getPassword();//拿到密码
        password = DigestUtils.md5DigestAsHex(password.getBytes());//md5加密处理
//        2、根据页面提交的用户名username查询数据库
        //employee是泛型
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername, employee.getUsername());//指定sql语句条件
        Employee emp = employeeService.getOne(queryWrapper);
        //getOne 这个用户名是唯一的用getOne调用,调用后分装成employee对象
        //3、如果没有查询到则返回登录失败结果
        if (emp == null) {
            //返回结果封装成R对象
            return R.error("登录失败");
        }
        //4、密码比对,如果不一致则返回登录失败结果
        //数据库查的密码和处理后的密码对比
        if (!emp.getPassword().equals(password)) {
            return R.error("登录失败");
        }
        // 5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if (emp.getStatus() == 0) {
            //1账户可用。0账号不可用
            return R.error("账户已禁用");
        }
//        6、登录成功,将员工id存入Session并返回登录成功结果
        request.getSession().setAttribute("employee", emp.getId());

        return R.success(emp);
    }

2. Background exit function development

Just click the logout button on the right to log out of the system, and the page should jump back to the login page after logging out of the system

The user clicks the logout button on the page to send a request, the request address is /employee/logout, and the request method is POST.

We only need to create the corresponding processing method in the Controller, the specific processing logic;
1. Clean up the user id in the Session
2. Return the result

    /**
     * 员工退出页面
     */
        @PostMapping("/logout")
        public R<String> logout(HttpServletRequest request){
//            清理Session中保存的当前登录员工的id
            request.getSession().removeAttribute("employee");
            //把employee属性移除
            return R.success("退出成功");
    }

3. Staff management business development

3.1 Improve the login function
3.1.1 Problems

If the user does not log in and directly accesses the system page, he can directly access

3.1.2 Scheme

Use filters or interceptors. No login Jump to the login interface

3.1.3 Code Development

Implementation step
1. Create a custom filter LoginCheckFilter
2. Add the annotation @Servletcomponentscan to the startup class
3. Improve the processing logic of the filter
Create a filter package. Add the scanning annotation @ServletComponentScan to the startup class

/*
检查用户是否已将登陆
 */
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器 支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;//强转
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //1、获取本次请求的URI
        String requestURI = request.getRequestURI();
        log.info("拦截到请求:{}",requestURI);//{}requestURI占位符
        //不需要拦截的直接放行的
        String[] urls = new String[]{
                "/employee/login",//登录页面
                "/employee/logout",//退出页面
        "/backend/**",//静态资源
                "/front/**"
        };
//2、判断本次请求是否需要处理--》请求的页面是否在要放行的页面中
        boolean check = check(urls,requestURI);
//3、如果不需要处理,则直接放行
        if (check){
            log.info("本次请求{}不需要处理",requestURI);
            filterChain.doFilter(request,response);
            return;
        }
//4、判断登录状态,如果已登录,则直接放行
        if (request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
            filterChain.doFilter(request,response);
            return;
        }
        log.info("用户未登录");
//        5、如果未登录则返回未登录结果通,过输出流方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }
    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * 遍历要放行的数组。和请求的数组对比
     * @param requestURL
     * @return
     */
    public boolean check(String[] urls,String requestURL) {
        for (String url : urls){
            boolean match = PATH_MATCHER.match(url,requestURL);
            if (match){
                return true;//放回true表示匹配上
            }
        }
        return false;
    }
}
@Slf4j
//可以使用log方法,打印info级别日志
@SpringBootApplication
//引导类or启动类
@ServletComponentScan//扫描webFilter注解 进一步创建过滤器
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功。。。。");//打印info级别日志
    }
}

4. New employees

4.1 Data Model

Insert the employee data entered on the new page into the employee table.
It should be noted that a unique constraint is added to the username field in the employee table, because username is the employee's login account and must be unique

4.2 Code Development

The execution process of the whole program:
1. The page sends an ajax request, and submits the data entered in the new employee page to the server in the form of json.
2. The server Controller receives the data submitted by the page and calls Service to save the data
. 3. Service Call Mapper to operate the database and save the data.
insert image description here
There is still a problem in the previous program, that is, when we add an employee, the account number entered already exists. Since the unique
constraint is added to this field in the employee table, the program will throw an exception at this time:

java sql.SQLIntegrityConstraintviolationException: Duplicate entry
zhangsan for key idx_username

At this time, our program needs to catch exceptions, and there are usually two processing methods

1. Add try and catch to the Controller method for exception capture
2. Use exception handler for global exception capture

The key to the global exception handler
is the annotations @@ControllerAdvice and @ExceptionHandler.
See annotations for details

/**
 * 全局异常处理器
 * 关键在@@ControllerAdvice和@ExceptionHandler两个注解
 */
//
//拦截 类上加restController和controllee类
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody//封住成JSON数据
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 异常处理方法
     */
    //@ExceptionHandler声明要处理的类
    //此处处理的是SQLIntegrityConstraintViolationException的异常
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHamdler(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage());
        //异常有多种
        //判断异常信息里边是否有需要的关键字
        //用户名唯一,重复报一下错误
        //Duplicate entry 'zhangsan' for key'idx username
        //关键字=Duplicate entry

        if (ex.getMessage().contains("Duplicate entry")){
            //冬天截取重复的用户名  用空格隔开
            String[] split = ex.getMessage().split(" ");
            //split[2]  “zhangsan的下标”
            String msg = split[2] + "已经存在";
            //通用返回结果
            return R.error(msg);
        }
        return R.error("未知错误");
    }
}

Summary:
1. Clarify the business requirements according to the product prototype
2. Focus on analyzing the data transfer process and data format
3. Debug and track the program execution process through debug breakpoints
+++++++++++++++++ ++++++++++++++++++++++
The flow of other functions is the same as the flow of new functions. Data format is different +
+++++++++++++++++++++++++++++++++++++++

insert image description here

5. Paging query of employee information

5.1. The execution process of the program:

1. The page sends an ajax request, and submits the paging query parameters (page, pagesize, name) to the server
2. The server controller receives the data submitted by the page and calls the Service to query the data
3. The Service calls the Mapper to operate the database and query the paging data
4. The Controller responds the queried paging data to page 5, and the page receives the paging data and displays it on the page through the Table component of Elementul

5.2. Code development

Step 1:
MP paging plug-in configuration class under config package

/**
 * 配置MP的分页插件
 */
@Configuration//声明这是配置类
public class MybatisPlusConfig {
    @Bean//bean注解 spring来管理
    //通过拦截器的方式把插件加载进来
    public MybatisPlusInterceptor mybatisPlusConfigInterceptor(){
        //创建拦截器对象
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //加入插件
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

Step 2:
Controller layer EmployeeController code

    /**
     * 分页处理的数据 用page接受
     * 员工信息查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")//get请求 所以getmapping
    //设置默认页数和单页数据条数 name:查询的用户
    public R<Page> page(int page,int pageSize,String name){
        log.info("page ={},pageSize = {},name = {}",page,pageSize,name);
        //构造分页构造器
        Page pageInfo = new Page(page,pageSize);
        //构造条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        //添加过滤添条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        //执行查询
        employeeService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }

6. Enable/disable employee account

Only the administrator can enter the background and set the status of the account.
If the status of an employee account is normal, the button is displayed as "Disabled", and if the status of the employee account is disabled, the button is displayed as "Enabled"

6.1. Code development

How do you make it so that only the administrator admin can see the enable and disable buttons on the page?

insert image description here

6.2. Execution process

1. The page sends an ajax request and submits the parameters (id, status) to the server
2. The server controller receives the data submitted by the page and calls the Service to update the data
3. The Service calls the Mapper to operate the database
insert image description here

How to send the ajax request in the page?
insert image description here

Step 1:
Controller layer EmployeeController code

    /**
     * 根据id修改员工信息
     * @return
     */
    @PutMapping
    public R<String> updata(HttpServletRequest request,@RequestBody Employee employee){
        log.info(employee.toString());
        //获取修改人信息
        Long empId = (long)request.getSession().getAttribute("employee");
        employee.setUpdateTime(LocalDateTime.now());//当前修改时间
        employee.setUpdateUser(empId);//修改人信息。当前登录的人可以修改,所修改人=当前登陆人
        employeeService.updateById(employee);
        return R.success("员工信息修改成功");
    }

The database id is different from the disabled startup operation id (id 19 digits)
Reason: Long (6 digits before 1) type loses precision
insert image description here

Step 2:
Solve the above problems:
process when the server responds to the json data on the page, and convert the long data into String strings in a unified way. The
specific implementation steps:
1) Provide the object converter jacksonobjectMapper, and convert Java objects to json data based on jackson conversion (already provided in the data, directly copied to the project for use). Put the common package
2) Extend Springmvc's message converter in the WebMvcConfig configuration class, and use the provided object converter in this message converter to convert Java objects to json data


    /**
     * 扩展mve框架的消息转换器
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //创建消息转换器对象
//  我们自己new 的转换器放入 converters中。默认8个转换器,现在+1
        MappingJackson2CborHttpMessageConverter messageConverter = new MappingJackson2CborHttpMessageConverter();
        //设置对象转换器,底层使用Jackson将java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
//        将上面的消息转换器对象追加到mvc框架的转换器集合中
        //注意add导入index类。 0:优先使用自己的转换器
            converters.add(0,messageConverter );
    }

7. Edit employee information

7.1. Execution process:

1. When the edit button is clicked, the page jumps to add.html and carries the parameter [employee id] in the url
http://localhost:8000/backend/page/member/add.htmlid=1644891700080074753

2. Obtain the parameter employee id in the url on the add.html page]
3. Send an ajax request, request the server, and submit the employee id parameter at the same time
4. The server receives the request, queries the employee information according to the employee id, and stores the employee information in the form of json Respond to the page
5. The page receives the json data responded by the server, and echoes the employee information through VUE data binding.
6. Click the save button to send an ajax request, and submit the employee information in the page to the server in json form
7. The server receives employee information, processes it, and responds to the page after completion.
8. The page receives the response information from the server and performs corresponding processing.
Note: the add.html page is a public page, and both adding employees and editing employees are operated on this page

7.2. Code development

Controller layer EmployeeController code

    /**
     * 根据id查询员工信息
     * 复用根据id修改员工信息
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public R<Employee> getById(@PathVariable Long id){
        log.info("根据id查询员工信息。。。");
        Employee employee = employeeService.getById(id);
        if (employee != null){
            return R.success(employee);
        }
        return R.error("没有查询到对应员工信息");
    }

Ⅱ. Category management

1. Common fields are automatically filled

1.1. Problem analysis

When developing employee management functions, fields such as creation time, creator, modification time, and modification person need to be set when adding an employee, and fields such as modification time and modification person need to be set when editing an employee. These fields are common fields
> fields shared by multiple tables

1.2, code implementation

MybatisPlus public fields are automatically populated, which means assigning specified values ​​to specified fields when inserting or updating. The advantage of using it is that these fields can be processed uniformly, avoiding repeated codes.

Implementation steps:
1. Add the ***@TableField*** annotation to the attribute of the entity class to specify the automatic filling strategy
2. Write the metadata object processor according to the framework requirements, and assign values ​​to the public fields in this class. The class needs to implement the MetaObjectHandler interface

    /**
    第一步:1、在实体类的属性上加入***@TableField***注解,指定自动填充的策略
     * 那些是公共字段
     * 就加上@TableField注解
     * fill :填充  =号后边是填充策略
     * 详细策略看FieldFill类
     */
    @TableField(fill = FieldFill.INSERT)//插入时填充字段
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)//插入和更新时填充字段
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)//插入时填充字段
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)//插入和更新时填充字段
    private Long updateUser;

1.3. Perfect functions

Note : ①Currently we set createUser and updateUser as fixed values, and we need to modify it to dynamically obtain the id of the currently logged-in user.
②ThreadLocal can be used to solve this problem, which is a class provided in DK.

Before learning ThreadLocal, first confirm one thing: every http request sent by the client, the server will allocate a new thread to process it, and the methods involved in the following classes in the processing process belong to the same thread :
1. The doFilter method of LogincheckFilter
2. The update method of EmployeeController
3. The updateFi method of MyMetaObjectHandler

/**
 * 测试
 * 可以在上面的三个方法中分别加入下面代码(获取当前线程id):
 * long id = Thread。currentThread().getId();
 * log.info("线程id:{}",id)
 */

What is ThreadLocal?
ThreadLocal is not a Thread (thread), but a local variable of Thread. When using ThreadLocal to maintain variables, Threadlocal
provides an independent copy of the variable for each thread that uses the variable, so each thread can change its own copy independently without affecting the corresponding copies of other threads.

ThreadLocal provides a separate storage space for each thread, which has the effect of thread isolation. The corresponding value can only be obtained within the thread, and cannot be accessed outside the thread.

/**
 * ThreadLocal常用方法:
 * public void  set(T value)  设置当前线程的线程局部变量的值
 * public T get() 返回当前线程所对应的线程局部变量的值
 *

We can obtain the current login user id in the doFilter method of LogincheckFilter, and call the set method of ThreadLocal to set the value of
the thread , and then call the get method of ThreadLocal in the updateFill method of MyMetaObjectHandler to obtain
The value of the thread local variable corresponding to the current thread (user id).


Implementation steps:
1. Write the Basecontext tool class, based on the tool class encapsulated by ThreadLocal
2. Call BaseContext in the doFilter method of LoginCheckFilter to set the id of the currently logged-in user
3. Call BaseContext in the method of MyMetaobjectHandler to obtain the id of the logged-in user

package com.itheima.reggie.common;
/**
 * 1、编写Basecontext工具类,基于ThreadLocal封装的工具类
 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
 */
public class BaseContext {
    //id 是lang类型 所以泛型是long
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);//保存值
    }
    public static Long getCurrentId(){
        return threadLocal.get();//取值
    }
}
// 第二步:在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
//4、判断登录状态,如果已登录,则直接放行
        if (request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));//获取id
        //获取id
            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);

            filterChain.doFilter(request,response);
            return;
        }
        log.info("用户未登录");
3、在MyMetaobjectHandler的方法中调用BaseContext获取登录用户的id        metaObject.setValue("updateUser",new Long(1));
metaObject.setValue("createUser",BaseContext.getCurrentId());

2. New categories

Classification of dishes and packages. Select a dish category when adding dishes in the background system

Before developing business functions, first create the basic structure of classes and interfaces that need to be used.
Entity class Category (directly imported from course materials)
Mapper interface CategoryMapper
business layer interface Categorysenvice
business layer implementation class Categoryservicelmpl
control layer Categorycontroller

2.1 Code development

2.2 The execution process of the program:

1. The page (backend/page/category/list.html) sends an ajax request, and submits the data entered in the new classification window to the server in the form of json. 2. The server controller receives the data submitted by the page
and calls the Service to save the data
3. Service calls Mapper to operate the database and save data

insert image description here

package com.itheima.reggie.controller;
/**
 * 分类管理
 */
@RestController
@RequestMapping("/categoty")
@Slf4j
public class CategoryController {
    @Autowired
    private CaregoryService caregoryService;
    /**
     * 新增分类成功
     */
    @PutMapping
    public R<String> save(@RequestBody Category category){
        log.info("category:{}",category);
        caregoryService.save(category);
        return R.success("新增分类成功");
    }
}

Functional test: you can add new dish categories

3. Paging query of classified information

3.1 Execution process:

1. The page sends an ajax request, and submits the paging query parameters (page, pagesize) to the server.
2. The server-side Controller receives the data submitted by the page and calls the Service to query the data.
3. The Service calls the Mapper to operate the database and query the paging data.
4. The Controller will The queried paging data is responded to page
5. The page receives the paging data and displays it on the page through the Table component of Elementul

package com.itheima.reggie.controller;
    /**
     * 分页查询
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize){
        //分页构造器
        //页数和一页的条件
        Page<Category> pageInfo = new Page<>(page,pageSize);
        //条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        //添加排序条件,根据sort进行排序
        queryWrapper.orderByAsc(Category::getSort);

        //进行分页查询
        categoryService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

4. Delete category

demand analysis

On the category management list page, you can delete a category. It should be noted that when a category is associated with dishes or set meals, this category cannot be deleted.

code development

1. The page sends an ajax request and submits the parameter (id) to the server
2. The server Controller receives the data submitted by the page and calls Service to delete the data
3. Service calls Mapper to operate the database

CategoryController层
   /**
     * 根据id删除分类
     * @param id
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long id){
        log.info("删除分类,id为:{}",id);

        categoryService.removeById(id);
        return R.success("分类信息删除成功");
    }

We have implemented the function of deleting categories according to id, but we have not checked whether the deleted categories are associated with dishes or set meals, so we need to improve the function
.

To improve the classification deletion function, you need to prepare basic classes and interfaces:
1. Entity classes Dish (menu entity class) and Setmeal (package entity class) (just copy from the course materials
2. Mapper interface DishMapper and SetmealMapper
3. Service Interface DishService and Setmealservice
4, Service implementation class DishServicelmpl and SetmealServicelmpl


impl under the service package

@Service
public class CategoryServiceImpl extends ServiceImpl<CateGoryMapper,Category> implements CaregoryService {
    //注入,
    //菜品分类id   private Long categoryId;
    @Autowired
    private DishService dishService;
    @Autowired
    private SetmealService setmealService;
    /**
     * 根据id删除分类,删除之前需要经进行判断
      * @param id
     */
    @Override
    public void remove(Long id) {
        //构造查询条件
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据分类categoryId 数据库是:category_Id 进行查询
        dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
        //count 统计数据量
        int count1 = dishService.count(dishLambdaQueryWrapper);

//        查询当前分类是否关联了菜品,如果已经关联、抛出一个业务异常
        if (count1 > 0){
            //已经关联菜品,抛出一个业务异常
            throw new CustomException("当前分类下关联了菜品,不能删除");
        }

//        查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
        LambdaQueryWrapper<Setmeal> setmealLambdQueryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据分类id进行查询
        setmealLambdQueryWrapper.eq(Setmeal::getCategoryId,id);
        int count2= setmealService.count();
        if (count2 > 0){
            //已经关联套餐,抛出一个业务异常
            throw new CustomException("当前分类下关联了套餐,不能删除");
        }
        //菜品和套餐都没有关联
        //可以正常删除分类   调用删除方法
        super.removeById(id);
    }
}

Custom exception class under common package


/**
 * 自定义业务异常
 */
public class CustomException extends RuntimeException{
    public CustomException(String message){
        super(message);
    }
}

Global exception class under the common package

    /**
     * 异常处理方法
     */
    //@ExceptionHandler声明要处理的类
    //此处处理的是SQLIntegrityConstraintViolationException的异常
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHamdler(CustomException ex){
        log.error(ex.getMessage());

        return R.error(ex.getMessage());
    }

5. Modify classification

CategoryServiceImpl under the service layer impl

    /**
     * 根据id修改分类信息
     * @param category
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody Category category){
        log.info("修改分类信息:{}",category);

        categoryService.updateById(category);
        return R.success("修改分类信息成功");
    }

Ⅲ. Dishes management business development

1. File upload and download

Introduction to file upload
insert image description here

  • To receive files uploaded by the client page, the server usually uses two components of Apache:
    ①commons-fileupload
    ②commons-io
    /**
     * 文件上传
     * Spring框架在spring-web包中对文件上传进行了封装,简化服务端代码
     * 我们只需要在Controller的方法中声明
     * 一个MultipartFile类型的参数即可接收上传的文件,如下:
     * 本质还是上边的两种方式
     * @param file
     * @return
     */
    @PostMapping(value = "/upload")
    public R<String> upload (MultipartFile file){
        System.out.println(file);
        return null;
    }
}

File download introduction:
file download, download, refers to the process of transferring files from the server to the local computer.


There are usually two ways to download files through a browser:
①Download as an attachment, a save dialog box pops up, and save the file to the specified disk directory
②Open directly in the browser

  • Downloading files through the browser is essentially the process of the server writing the file back to the browser in the form of a stream.

1.1. Implementation of file upload code

File download code implementation
Step 1:insert image description here

Step 2: The controller layer creates the CommonController dish management class

/**
 * 文件上传和下载
 */
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
    //读取配置文件中转存位置
    //application.yml文件
    //reggie:
    //  path: D:\
    @Value("${reggie.path}")
    private String basePath;
    /**
     * 文件上传
     * @return
     */
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file){
        //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
        log.info(file.toString());
        /**
         * 防止总是被拦截器拦截,在拦截器类设置不需要拦截直接放行
         *         String[] urls = new String[]{
         *                 //菜品管理
         *                 "/common"
         *         };
         */
        //原始文件名
        String originalFilename = file.getOriginalFilename();//abc.jpg
        //动态截取文件后缀  suffix接受新的文件名 .之后的
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖  新文件 + 后缀(.jpg)
        String fileName = UUID.randomUUID().toString() + suffix;//qwertt.jpg

        //判断application.yml设置的指定位置目录是否存在
        File dir = new File(basePath);
        //判断当前目录是否存在
        if (!dir.exists()){
            //目录不存在 ,需要创建
            dir.mkdirs();
        }

        try {
            //将临时文件转存到指定位置.
            //转存位置可在application.ym中设置
            file.transferTo(new File(basePath + fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //文件名称需要放在数据库中  返回fileaName
        return R.success(fileName);
    }

    /**
     * 文件下载
     * @param name
     * @param response
     */
    //通过流写回数据  不需要返回值
//    输出流需要respinse获得
    @GetMapping("/download")
    public void download(String name , HttpServletResponse response){
        try {
            //输入流,通过输入流读取文件内容
            FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));

            //输出流,通过输出流将文件写回浏览器,在浏览器展示图片了  输出流需要respinse获得
            ServletOutputStream outputStream = response.getOutputStream();

//            设置想要返回什么类型文件
                    response.setContentType("image/jpeg");

//            将读到的内容,放到数组中去
            int len = 0;
            byte[] bytes = new byte[1024];
            //-1 说明没有读完
            while ((fileInputStream.read(bytes)) != -1){
//                用过输出流向浏览器写。从第一个写 写len这么长
                outputStream.write(bytes,0,len);
                outputStream.flush();//刷新
            }
//            关闭资源
            outputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2. New dishes

Data model:
Adding a new dish is actually inserting the dish information entered on the new page into the dish table. If you add a flavor method, you also need to insert data into the dish_flavor table. So when adding a new dish, two tables are involved:
o
dishdish flavordish flavor
table

Preparations:
Entity class DishFlavor (import directly from the course materials, Dish entity has been imported in the previous course)
Mapper interface DishFlavorMapper
business layer interface DishFlavorService
business layer implementation class DishFlavorservicelmpl
control layer Dishcontroller

1、Mapper接口DishFlavorMapper
@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
}
2、业务层接口DishFlavorService
public interface DishFlavorService extends IService<DishService> {
}
3、业务层实现类DishFlavorservicelmpl
public class DishFlavorServiceImpl extends ServiceImpl<DishFlavorMapper, DishFlavor> {
}
4、控制层Dishcontroller
/**
 * 菜品管理
 */
@RestController
@RequestMapping("/dish")
public class DishController {
    @Autowired
    private DishService dishService;

    @Autowired
    private DishFlavorService dishFlavorService;
}

2.1 Sort out the interaction process

1. The page (backend/page/food/add.html) sends an ajax request, requesting the server to obtain the dish classification data and displaying it in the drop-down box 2. The page sends a
request to upload pictures, and requests the server to save the pictures to the server
3. The page sends a request to download the image, and echoes the uploaded image
4. Click the save button, send an ajax request, and submit the relevant data of the dish to the server in the form of json


To develop the function of adding new dishes is actually to write code on the server side to process the 4 requests sent by the front-end page

   /**
   控制层CategoryController.java
     * 根据条件查询分类数据
     * @param category
     * @return
     */
        @GetMapping("/list")
    //list方法,根据条件查询方法
    public R<List<Category>> list(Category category){
        //条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        //动态添加条件
        queryWrapper.eq(category.getType() != null,Category::getType,category.getType());

        //添加排序条件
        //第一个:根据sort排序
        //第二个:根据创建时间排序
        queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);

        List<Category> list = categoryService.list(queryWrapper);
        return R.success(list);
    }

DTO, the full name is DataTransferObject, which is a data transfer object, which is generally used for data transfer between the presentation layer and the service layer.
The data and entity classes in the dish are not in one-to-one correspondence.
Import DTO

//导入DTO需要单独建包
@Data
public class DishDto extends Dish {
    //继承dish中的属性。扩展一些属性
//    flavors接受页面传过来的数据
    private List<DishFlavor> flavors = new ArrayList<>();
    private String categoryName;
    private Integer copies;
}
//controller层
/**
 * 菜品管理
 */
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
    //插入两张表
    @Autowired
    private DishService dishService;

    @Autowired
    private DishFlavorService dishFlavorService;

    /**
     * 新增菜品
     * @param dishDto
     * @return
     */
    @PostMapping
    //加@RequestBody接收JSON数据
    public R<String> save(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());

        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功");
    }
}

Table operation
service layer creates DishService interface

public interface DishService extends IService<Dish> {
    //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavol
    public void saveWithFlavor(DishDto dishDto);
}

Service layer impl package creates DishServiceImpl time DishService interface

@Slf4j
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
    @Autowired
    //控制口味表
    private DishFlavorService dishFlavorService;
    /**
     * 新增菜品,同时保存对应的口味数据
     * @param dishDto
     */
    @Transactional//事务控制(数据库操作)的注解
    public void saveWithFlavor(DishDto dishDto) {
        //保存菜品的基本信息到莱品表dish
        this.save(dishDto);
        Long dishId = dishDto.getId();//菜品id
        //菜品口味
        List<DishFlavor> flavors = dishDto.getFlavors();
        //遍历集合flavors 遍历DishFlavor实体类中的数据.此处使用流
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishId);
            return item;
        }).collect(Collectors.toList());
        //保存菜品口味数据到菜品口味表dish_flavor
        dishFlavorService.saveBatch(flavors);
    }
}

Controller layer creates DishController


/**
 * 菜品管理
 */
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
    //插入两张表
    @Autowired
    private DishService dishService;

    @Autowired
    private DishFlavorService dishFlavorService;

    /**
     * 新增菜品
     * @param dishDto
     * @return
     */
    @PostMapping
    //加@RequestBody接收JSON数据
    public R<String> save(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());

        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功");
    }
}

3. Paging query of dish information

3.1 Interaction process

1. The page (backend/page/food/list.html) sends an ajax request, and submits the pagination query parameters (page, pagesize, name)
to the server to obtain the paging data.
2. The page sends a request to request the server to download the image. It is used to display page pictures and
develop the page-by-page query function of dish information. In fact, it is to write code on the server side to process the two requests sent by the front-end page.

The page returns the name (dish name), no dish classification.
Dishes category cannot be displayed
Solution: ①Create the categoryName of the category attribute in Dto
②Dto inherits the entity class category=Add an attribute to the entity class (category category)
categoryName

3.2 Code development

Controller layer dishcontroller page query code

   /**
     * 菜品信息分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public  R<Page> page(int page,int pageSize, String name){

        //构造器分页构造器对象
        Page<Dish> pageInfo = new Page<>(page, pageSize);
        //将dish中属性的值拷贝给DishDto
        Page<DishDto> dishDtoPage = new Page<>();

        //条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        //根据name 通过like进行模糊查询。添加过滤条件
        queryWrapper.like(name != null,Dish::getName,name);
        //添加排序条件。根据更新时间updateTime 降序排序
        queryWrapper.orderByDesc(Dish::getUpdateTime);

//     执行分页查询  调用page方法 ,传入pageInfo queryWrapper
        dishService.page(pageInfo,queryWrapper);

//        拷贝属性  使用BeanUtils类  pageInfo拷贝到dishDtoPage,忽略records属性
        BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
//        处理pageInfo中忽略的records属性为DishDto属性

//        遍历records 最后手机起来
        List<Dish> records = pageInfo.getRecords();
        List<DishDto> list = records.stream().map((item) -> {
            DishDto dishDto = new DishDto();
            //Dish实体类中其他属性都是空的,需要拷贝
            BeanUtils.copyProperties(item,dishDto);

            Long categoryId  = item.getCategoryId();//分类id
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);

            if (category != null) {
                //获取分类名称  需要一个Dto并给Dto赋值
                String categoryName = category.getName();
                //需要一个Dto并给Dto赋值
                dishDto.setCategoryName(categoryName);
//            此处categoryName包含菜品分类(categoryName)和其他普通属性
            }
            return dishDto;
        }).collect(Collectors.toList());
        
        dishDtoPage.setRecords(list);
        return R.success(dishDtoPage);
    }

4. Modify the dishes

4.1 Interaction process

1. The page sends an ajax request, requesting the server to obtain the classification data, which is used for data display in the drop-down box of dish classification.
2. The page sends an ajax request, requesting the server to query the current dish information according to the id, which is used for displaying the dish information. 3.
Page Send a request to request the server to download the image for page image echo
4. Click the save button, the page sends an ajax request, and submits the modified dish-related data to the server in the form of json
to develop and modify the dish function, which is actually in the service Write code on the end to handle the 4 requests sent by the front-end page

4.2 Code Development

4.2.1 Obtain current dish information according to id

service层DishServiceImpl


    /**
     * 根据:id查询菜品信息和对应的口味信息
     * @param id
     * @return
     */
    public DishDto getByIdWithFlavor(Long id) {
        //查询菜品基本信息,从dish表查询
        Dish dish = this.getById(id);
//第一部分:拷贝普通用户值
        //拷贝
        DishDto dishDto = new DishDto();//创建dishDto对象
        BeanUtils.copyProperties(dish,dishDto);

//查询当前菜品对应的口味信息,从dish_flavor表查询
//        第二部分:拷贝口味的值
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>(); //构造条件过滤器   <对应的实体>
        //添加条件 调用DishFlavor中dishId,dish.getId:获取到id值
        queryWrapper.eq(DishFlavor::getDishId,dish.getId());
        //调用dishFlavorService查询
        List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);

//        单独给dishDto赋值
        dishDto.setFlavors(flavors);

        //查询完之后需要返回DishDto类型需要拷贝
//        把第一个和第二个的属性值放回给dishDto类型
        return dishDto;
    }

The controller calls the method getByIdWithFlavor to modify the dish in the service

    /**
     * 根据id查询菜品信息和对用的口味信息
     * Service层写完 调用controller层
     * @param id
     * @return
     */
    //口味不是普通属性,需要勇DishDto类型数据
    @GetMapping("/{id}")
    //@PathVariable 获取id注解
    public R<DishDto> get(@PathVariable Long id){
        //此处需要查两张表dish 和口味DISH_Flavor。在service层DishService接口中创建方法.并在dishserviceImpl实现接口方法
        //调用service层方法
        DishDto dishDto = dishService.getByIdWithFlavor(id);
        //直接返回
        return R.success(dishDto);
    }

4.2.2 Modify dishes

Modify code development

Create and modify the updateWithFlavor method in the DishService interface of the service business layer. and implement the interface method

public interface DishService extends IService<Dish> {
    //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavol
    public void saveWithFlavor(DishDto dishDto);

    //根据id查询菜品信息以及他所对应的口味信息
    public DishDto getByIdWithFlavor(Long id);
    //更新菜品信息,同时更新对应的口味信息
    public void updateWithFlavor(DishDto dishDto);
}

Modify the updateWithFlavor method in the DishServiceImpl implementation interface

 /**
     * 修改菜品信息和口味信息 
     * @param dishDto
     */
    @Override
    @Transactional//开启事务注解,保证事务一致性
    public void updateWithFlavor(DishDto dishDto) {
        //1、更新dish表基本信息
            //dishDto继承dish ,此处继承调用dishDto == 更新dish中的方法,也就是普通属性
        this.updateById(dishDto);

        //2、清理当前菜品对应口味数据--dish__flavor表的delete操作
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();//创建对象
        queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());//添加条件

        dishFlavorService.remove(queryWrapper);//执行sql

        //3、添加当前提交过来的口味数据--dish__flavor表的insert操作
            //插入来自页面的信息,在dishDto中已经封装
        List<DishFlavor> flavors = dishDto.getFlavors();
            //DishFlavor属性不全,
                // 解决:遍历flavors,把每一项拿出来,从dishDto中重新setDishId
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishDto.getId());
            return item;//处理之后重新付给flavors
        }).collect(Collectors.toList());

        dishFlavorService.saveBatch(flavors);

    }

controller层DishController

    /**
     * 修改菜品
     * @param dishDto
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());

        dishService.updateWithFlavor(dishDto);
        return R.success("修改菜品成功");
    }

Service business layer DishServiceimpl


    /**
     * 修改菜品信息
     * @param dishDto
     */
    @Override
    @Transactional//开启事务注解,保证事务一    致性
    public void updateWithFlavor(DishDto dishDto) {
        //1、更新dish表基本信息
            //dishDto继承dish ,此处继承调用dishDto == 更新dish中的方法,也就是普通属性
        this.updateById(dishDto);

        //2、清理当前菜品对应口味数据--dish__flavor表的delete操作
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();//创建对象
        queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());//添加条件

        dishFlavorService.remove(queryWrapper);//执行sql

        //3、添加当前提交过来的口味数据--dish__flavor表的insert操作
            //插入来自页面的信息,在dishDto中已经封装
        List<DishFlavor> flavors = dishDto.getFlavors();
            //DishFlavor属性不全,
                // 解决:遍历flavors,把每一项拿出来,从dishDto中重新setDishId
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishDto.getId());
            return item;//处理之后重新付给flavors
        }).collect(Collectors.toList());

        dishFlavorService.saveBatch(flavors);

    }

Ⅳ. Package management business development

1. New package

demand analysis

A set meal is a collection of dishes.
Package information can be managed in the background system, and a new package can be added through the new package function. When adding a package, you need to select the package
category and the dishes included in the current package, and you need to upload the corresponding picture of the package. On the mobile terminal, it will follow the Classify packages to display corresponding packages.

data model

Adding a new set meal is actually inserting the set meal information entered on the new page into the setmeal table, and also inserting the setmeal_dish table with related data about the set meal and dishes.
—————————————————————————————

Involved table:
setmeat set meal table
setmeaLdish set meal dish relationship table

Preparation

实体类SetmealDish(直接从课程资料中导入即可,Setmeal实体前面课程中已经导入过了
DTOSetmealDto(直接从课程资料中导入即可)
Mapper接口SetmealDishMapper
业务层接口SetmealDishservice
业务层实现类SetmealDishServicelmpl
控制层Setmealcontroller

SetmealDishMapper

@Mapper
//<>里的是实体
public interface SetmealDishMapper extends BaseMapper<SetmealDish> {
}

SetmealDishservice

//<>里是实体类
public interface SetmealDishservice extends IService<SetmealDish> {
}

SetmealDishServicelmpl

@Slf4j
@Service
//ServiceImpl两个泛型类型<对应的mapper,对应的实体类>
public class SetmealDishServicelmpl extends ServiceImpl<SetmealDishMapper, SetmealDish> implements SetmealDishservice {
}

Setmealcontroller

/**
 * 套餐管理
 */
@RestController
@RequestMapping("/setmeal")
public class Setmealcontroller {
//    注入
    @Autowired
    private SetmealService setmealService;

    @Autowired
    private SetmealDishservice setmealDishservice;
}

1.1 Interaction process

1. The page (backend/page/combo/add.htmD) sends an ajax request, requesting the server to obtain the package classification data and display it in the drop-down box 2. The page sends an ajax
request, requesting the server to obtain the dish classification data and display it to add dishes In the window
3. The page sends an ajax request, requests the server to query the corresponding dish data according to the dish classification and displays it in the add dish window 4. The page
sends a request to upload the picture, and requests the server to save the picture to the server
5. The page sends the request Download the picture and echo the uploaded picture
6. Click the save button, send an ajax request, and submit the package-related data to the server in the form of json
to develop a new package function. In fact, it is to write code on the server to process the sending of the front-end page These 6 requests are enough.

Controller layer
3. The page sends an ajax request to request the server, query the corresponding dish data according to the dish classification and display it in the add dish window

    /**
     * 根据条件查询对应的菜品数据
     * @param dish
     * @return
     */
    @GetMapping("/lsit")
    public R<List<Dish>> list(Dish dish){

        //构造查询条件  泛型Dish
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        //添加条件
        queryWrapper.eq(dish.getCategoryId() != null,Dish::getCategoryId,dish.getCategoryId());

        //添加条件,查询状态为1(起售状态) 的菜品
        queryWrapper.eq(Dish::getStatus,1);
//        添加排序条件, 根据实体类Dish类sort排序 desc降序排序
        queryWrapper.orderByDesc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

        //调用dishService 把queryWrapper分装成list类型
        List<Dish> list = dishService.list(queryWrapper);
        return R.success(list);
    }

Save data to the corresponding table
service layer SetmealService interface and implement the interface method SemealServiceImpl
SemealServiceImpl implements the interface method, and the controller layer calls the service method

SetmealService

public interface SetmealService extends IService<Setmeal> {
    /**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     * 将套餐基本信息和关联的菜品信息保存,所以泛型是SetmealDto
     */
    public void saveWithDish(SetmealDto setmealDto);
}

SemealServiceImpl

    public class SemealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
    @Autowired
    private SetmealDishservice setmealDishservice;
    /**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     */
    @Transactional//事务注解 要么全成功要么全失败
    public void saveWithDish(SetmealDto setmealDto) {
//        保存套餐的基本信息,操作setmeal(套餐表),执行insert操作
        this.save(setmealDto);

        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes.stream().map((item) ->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());
//        保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
        //saveBatch批量保存
        setmealDishservice.saveBatch(setmealDishes);
    }
}

The controller layer calls the service layer

/**
 * 套餐管理
 */
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class Setmealcontroller {
//    注入
    @Autowired
    private SetmealService setmealService;

    @Autowired
    private SetmealDishservice setmealDishservice;

    /**
     * 新增套餐
     * 除了setmel属性还有其他属性(setmealDto)的属性
     * 所以不能用setmel当泛型
     * @RequestBody接受Json格式数据
     * @param setmealDto
     * @return
     */
    @PostMapping
    public R<String> save (@RequestBody SetmealDto setmealDto){
        log.info("套餐信息:{}",setmealDto);

        setmealService.saveWithDish(setmealDto);

        return R.success("新增套餐成功");
    }
}

2. Paging query for package information

1.1 Interaction process

1. The page (backend/page/combo/list.html) sends an ajax request, submits the pagination query parameters (page, pagesizename) to the server, and obtains the pagination data 2. The page sends a request, and requests the server to download pictures
for Page picture display
> Developing the page-by-page query function of package information is actually writing code on the server side to process the two requests sent by the front-end page

    /**
     * 套餐分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize,String name){
//        分页构造器
        Page<Setmeal> pageInfo = new Page<>(page,pageSize);//Setmeal实体类没有套餐分类
        //SetmealDto继承Setmeal实体类,并添加套餐分类属性categoryName.
//        但SetmealDto中普通属性(Setmeal)没有值--》把分页查询的结果拷贝给dtoPage
        Page<SetmealDto> dtoPage = new Page<>();

        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
//        添加查询条件,根据(参数)name进行1ike模糊查询
        queryWrapper.like(name != null,Setmeal::getName,name);
//        Setmeal实体类中有    updateTime
        //添加排序条件,根据更新时间降序排列
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        //查询结果:调用setmealService page方法查询
        setmealService.page(pageInfo,queryWrapper);

        //拷贝对象(将pageInfo拷贝给dtoPage,recoreds是不用拷贝)
        //recoreds是集合,把查询的数据封装进去,他的泛型是T,此处泛型是SetmealDto或Setmeal
        BeanUtils.copyProperties(pageInfo,dtoPage,"recoreds");

        //recoreds忽略,需要赋值
        List<Setmeal> recoreds = pageInfo.getRecords();

        //直接勇list集合收集

        List <SetmealDto> list = recoreds.stream().map((item) -> {
            //定义SetmealDto类型的List,此处定义一个SetmealDto
            SetmealDto setmealDto = new SetmealDto();

            //new的setmealDto此时只有categoryName,没有其他普通属性
            //拷贝
            BeanUtils.copyProperties(item,setmealDto);

            //获得套餐分类ID
            Long categoryId = item.getCategoryId();
//            通过分类id查询分类对象  .此处获得一个对象。是查询的分类的分类对象
            Category category = categoryService.getById(categoryId);
            if (category !=null){
                //分类名称
                String categoryName = category.getName();
                //获得套餐分类categoryName
                setmealDto.setCategoryName(categoryName);
                //new的setmealDto此时只有categoryName,没有其他普通属性
            }
            //值返回给setmeaDto
            return setmealDto;
            //收集起来 并转成list集合
        }).collect(Collectors.toList());

        //recoreds计算之后 赋值给list 而List泛型为SetmealDto
        //将数据勇list集合接受写在上方
//        List<SetmealDto> list = null;

        //页面套餐数据没有,页面数数据构和此处返回的数据结构不同。导致不匹配 套餐分类没有数据
//        return R.success(dtoPage);

        //此时list包含所有属性的值,将list给dtopage
        dtoPage.setRecords(list);
        return R.success(dtoPage);
    }
}

3. Delete package

Single or multiple selection to delete. On sale must be discontinued to be deleted
insert image description here

service layer

SetmealService interface

    /**
     * 删除套餐,同时需要删除套餐和莱品的关联数据
     * @param ids
     */
    public void removeWithDish(List<Long> ids);

SemealServiceImpl

    /**
     * 删除套餐,同时需要删除套餐和莱品的关联数据
     * @param ids
     */
    @Override
    @Transactional
    public void removeWithDish(List<Long> ids) {
//第一步:查询套餐状态,确定是否可用删除
    //构造查询条件对象
        //select count() from setmeal where id in (3) and status=1
        LambdaQueryWrapper<Setmeal>  queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Setmeal::getId,ids);//多查询多条件。第一个条件
        queryWrapper.eq(Setmeal::getStatus,1);//第二个条件

        int count =this.count(queryWrapper);
        if (count > 0) {
            //如果不能删除,抛出一个业务异常
            throw new CustomException("正在售卖中,不能删除");
        }
        //第二步:如果可以删除,先删除套餐表中的数据--setmeal
        this.removeByIds(ids);//批量删除

        //delete from setmeal_dish where setmealid in (l,2,3)
        LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
        //第三步:删除关系表中的数据
        setmealDishservice.remove(lambdaQueryWrapper);
    }

The controller layer calls the service layer

Setmealcontroller

    /**
     * 删除套餐
     * ids有多个,Long类型
     * 使用liST集合接收
     * @return
     */
    @DeleteMapping
    public R<String> delete(@RequestParam List<Long> ids){
        log.info("id:{}",ids);

        //controller层调用Service层
        setmealService.removeWithDish(ids);
        return R.success("套餐数据删除成功!");
    }

Ⅴ. Mobile phone verification code login

1. SMS sending

The third-party SMS service will connect with various operators (China Mobile, China Unicom, China Telecom)
Commonly used SMS services:
Alibaba Cloud/Huawei Cloud/Tencent Cloud/JD/Monternet/Lexin

Step 1: Alibaba Cloud SMS Service - Register Account
https://www.aliyun.com/

Step 2: Alibaba Cloud SMS Service - Set SMS Signature
SMS Message - Domestic Message Settings
SMS signature is the signature of the SMS sender, indicating the identity of the sender

Step 3: Alibaba Cloud SMS Service - Set SMS Template

insert image description here

Step 4: Alibaba Cloud SMS Service - Set Accesskey
insert image description here

Refer to the official documents provided.
Specific development steps:
1. Import maven coordinates
2. Call API
1
3. Data import tools

        <!--短信发送 -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.16</version>
        </dependency>
        <dependency>
            <groupId>com.liyun</groupId>
            <artifactId>aliiyun-java-sdk-dysmsapi</artifactId>
            <version>2.1.0</version>
        </dependency>

2. Mobile phone verification code login

2.1. Interaction process

1. Enter the mobile phone number on the login page (front/page/login.html), click the [Get Verification Code] button, the page sends an ajax request, and calls the SMS service API on the server side to send a verification code text message
to the specified mobile phone number
. 2. In Enter the verification code on the login page, click the [Login] button, send an ajax request, and process the login request on the server side to develop the
mobile phone verification code login function. In fact, it is enough to write code on the server side to process the two requests sent by the front-end page.

2.2 preparation

Entity class User (import directly from course materials)
Mapper interface UserMapper
business layer interface Userservice
business layer implementation class Userservicelmpl
control layer Usercontroller
tool class SMSUtils, ValidatecodeUtils (import directly from course materials)

2.3 Code Development

Code Development - Modify LoginCheckFilter
to expand the logic in the LoginCheckFilter filter to determine the login status of mobile users

  //4-2、移动端登录  判断登录状态,如果已登录,则直接放行
// 第二步:在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
        if (request.getSession().getAttribute("user") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));//获取id
            //获取id
            Long userId = (Long) request.getSession().getAttribute("user");
            BaseContext.setCurrentId(userId);

            filterChain.doFilter(request,response);
            return;
        }

        log.info("用户未登录");
//        5、如果未登录则返回未登录结果通,过输出流方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }

Login Validation
UserController

    /**
     * 移动端用户登录
     * @param map
     * @param session
     * @return
     */
    @PostMapping("/login")
    //{phone:“13412345678“,code:“1234}
    //①Dto继承实体类扩展一个属性
    //②类型是kv 对 使用map
    //登录成功给页面返回登录信息 泛型勇user
    public R<User> sendMsg(@RequestBody Map map, HttpSession session){
        log.info(map.toString());
//一、获取手机号
        String phone = map.get("phone").toString();
//二、获取验证码
        String code = map.get("code").toString();
//三、从Session中获取保存的验证码
        Object codeInSession = session.getAttribute(phone);
//四、进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
        if (codeInSession != null && codeInSession.equals(code)){
            //如果能够比对成功,说明登录成功
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone,phone);

            User user = userService.getOne(queryWrapper);
            if (user == null){
                //判断当前(查询数据库有无)手机号对应的用户是否为新用户,如果是新用户就自动完成注册
            user = new User();
            user.setPhone(phone);
            user.setStatus(1);//设置状态
                userService.save(user);
            }
            //登录成功给页面返回登录信息 泛型勇user
            return R.success(user);
        }
        return R.error("登录失败");
}

Ⅵ. Dishes Display, Shopping Cart, Order Placement

1. Import user address book related function codes

Entity class AddressBook (directly imported from course materials)
Mapper interface AddressBookMapper
business layer interface AddressBookservice
business layer implementation class AddressBookservicelmp
control layer AddressBookcontroller (directly imported from course materials)

2. Display of dishes

2.1. Interaction process

1. The page (front/index.html) sends an ajax request to obtain classification data (dish classification and package classification)
2. The page sends an ajax request to obtain the dishes or packages under the first category
. The development of the dish display function is actually serving Write code on the client side to process the two requests sent by the front-end page.
Note: After the homepage is loaded, an ajax request is also sent to load the shopping cart data. Here you can temporarily modify the address of this request from the static json file Get the data and modify it later when developing the shopping cart function, as follows:
insert image description here

Modify the list method of DisController and test
the mobile terminal to open and click to add 1 to have a choice of flavors

@GetMapping("/lsit")
public R<List<DishDto>> list(Dish dish){

    //构造查询条件  泛型Dish
    LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
    //添加条件
    queryWrapper.eq(dish.getCategoryId() != null,Dish::getCategoryId,dish.getCategoryId());

    //添加条件,查询状态为1(起售状态) 的菜品
    queryWrapper.eq(Dish::getStatus,1);
//        添加排序条件, 根据实体类Dish类sort排序 desc降序排序
    queryWrapper.orderByDesc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

    //调用dishService 把queryWrapper分装成list类型
    List<Dish> list = dishService.list(queryWrapper);


    List<DishDto> dishDtoList = list.stream().map((item) -> {
        DishDto dishDto = new DishDto();
        //Dish实体类中其他属性都是空的,需要拷贝
        BeanUtils.copyProperties(item,dishDto);

        Long categoryId  = item.getCategoryId();//分类id
        //根据id查询分类对象
        Category category = categoryService.getById(categoryId);

        if (category != null) {
            //获取分类名称  需要一个Dto并给Dto赋值
            String categoryName = category.getName();
            //需要一个Dto并给Dto赋值
            dishDto.setCategoryName(categoryName);
//            此处categoryName包含菜品分类(categoryName)和其他普通属性
        }

        //查询口味
        Long dishID = item.getId();//菜品iD

        //根据菜品id查询口味
        LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加条件
        lambdaQueryWrapper.eq(DishFlavor::getDishId,dishID);
        //sQL:select * from dish_flavor where dish_id = ?
        //查出口味的集合
        List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
        dishDto.setFlavors(dishFlavorList);
        return dishDto;
    }).collect(Collectors.toList());
    return R.success(dishDtoList);
}

Create the list method of SetmealController and test it

    /**
     * 根据条件查询套餐数据
     * @param setmeal
     * @return
     */
    @GetMapping("/list")
    public R<List<Setmeal>> list( Setmeal setmeal){
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
        queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        List<Setmeal> list = setmealService.list(queryWrapper);
        return R.success(list);
    }

3. Shopping cart

insert image description here

Entity class Shoppingcart (directly imported from course materials)
Mapper interface ShoppingCartMapper
business layer interface Shoppingcartservice
business layer implementation class ShoppingCartservicelmp
control layer Shoppingcartcontroller

Add shopping cart
ShoppingCartController

public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;

    /**
     * 添加购物车
     * @param shoppingCart
     * @return
     */
    @PostMapping("/add")
    public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
        log.info("购物车数据:{}",shoppingCart);

        //设置用户id,指定当前是哪个用户的购物车数据
        Long currentId = BaseContext.getCurrentId();
        shoppingCart.setUserId(currentId);

        Long dishId = shoppingCart.getDishId();

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,currentId);

        if(dishId != null){
            //添加到购物车的是菜品
            queryWrapper.eq(ShoppingCart::getDishId,dishId);

        }else{
            //添加到购物车的是套餐
            queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
        }

        //查询当前菜品或者套餐是否在购物车中
        //SQL:select * from shopping_cart where user_id = ? and dish_id/setmeal_id = ?
        ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);

        if(cartServiceOne != null){
            //如果已经存在,就在原来数量基础上加一(更新操作)
            Integer number = cartServiceOne.getNumber();
            cartServiceOne.setNumber(number + 1);
            shoppingCartService.updateById(cartServiceOne);
        }else{
            //如果不存在,则添加到购物车,数量默认就是一
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartService.save(shoppingCart);
            cartServiceOne = shoppingCart;
        }

        return R.success(cartServiceOne);
    }

View, empty cart

 /**
     * 查看购物车
     * @return
     */
    @GetMapping("/list")
    public R<List<ShoppingCart>> list(){
        log.info("查看购物车...");

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
        queryWrapper.orderByAsc(ShoppingCart::getCreateTime);

        List<ShoppingCart> list = shoppingCartService.list(queryWrapper);

        return R.success(list);
    }

    /**
     * 清空购物车
     * @return
     */
    @DeleteMapping("/clean")
    public R<String> clean(){
        //SQL:delete from shopping_cart where user_id = ?

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());

        shoppingCartService.remove(queryWrapper);

        return R.success("清空购物车成功");
    }

4. User places an order

Go to the payment jump page
—————————
orders: order table
order_detail: order details

1. Interaction process

insert image description here

2. Prepare

Entity classes Orders, OrderDetail (directly imported from course materials)
Mapper interface OrderMapper, OrderDetailMapper
business layer interface OrderService, OrderDetailservice
business layer implementation class Orderservicelmpl, OrderDetailSenicelmpl
control layer Ordercontroller, OrderDetailcontroller

OrderController

/**
 * 订单
 */
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 用户下单
     * @param orders
     * @return
     */
    @PostMapping("/submit")
    public R<String> submit(@RequestBody Orders orders){
        log.info("订单数据:{}",orders);
        orderService.submit(orders);
        return R.success("下单成功");
    }

OrderServiceImpl

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {

    @Autowired
    private ShoppingCartService shoppingCartService;

    @Autowired
    private UserService userService;

    @Autowired
    private AddressBookservice addressBookService;

    @Autowired
    private OrderDetailService orderDetailService;

    /**
     * 用户下单
     * @param orders
     */
    @Transactional
    public void submit(Orders orders) {
        //获得当前用户id
        Long userId = BaseContext.getCurrentId();

        //查询当前用户的购物车数据
        LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ShoppingCart::getUserId,userId);
        List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);

        if(shoppingCarts == null || shoppingCarts.size() == 0){
            throw new CustomException("购物车为空,不能下单");
        }

        //查询用户数据
        User user = userService.getById(userId);

        //查询地址数据
        Long addressBookId = orders.getAddressBookId();
        AddressBook addressBook = addressBookService.getById(addressBookId);
        if(addressBook == null){
            throw new CustomException("用户地址信息有误,不能下单");
        }

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

Ⅶ. Cache optimization

A large number of users and a large amount of system visits
Frequent access to the database, system performance degradation, and poor user experience

1. Environment construction

1. maven coordinates

        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>

2. Configuration file
Add redis-related configuration to the application.yml of the project:

spring:
  redis:
    host:127.0.0.1
    port: 6379
    password: root
    database: 0

3. Configuration class

Add the configuration class Redisconfig to the item
to facilitate observation. The key in redis
is another serialization method by default.

1. Cache SMS verification code

The session is valid for 30 minutes

1.1 Implementation ideas

insert image description here

1, 2, code modification

Controller层UserController

① injection

> //    1、在服务端Usercontroller中注入RedisTemplate对象,用于操作Redis
    @Autowired
    private RedisTemplate redisTemplate;

② Method of sending mobile SMS verification code

            //第四步:需要将生成的验证码保存到Session
//            session.setAttribute(phone,code);

            //将生成的验证码缓存到Redis中,并且设置有效期为5分钟
            //object key,Object value,Duration timeout)
            redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);

③The mobile terminal user logs in to obtain the verification code & successfully deletes the verification code

//三、从Session中获取保存的验证码
//        Object codeInSession = session.getAttribute(phone);

        //从Redis中获取缓存的验证码
        Object codeInSession=redisTemplate.opsForValue().get(phone);
(略)

            //如果用户登录成功,删除Redis中缓存的验证码
            redisTemplate.delete(phone);

2. Caching dish data

Frequent data query, database performance degradation

1. Modify the list method of Dishcontroller, first obtain the dish data from Redis, if there is, return it directly without querying the database; if not, query the database, and put the queried dish data into Redis.
2. Transform the save and update methods of Dishcontroller and add the logic of clearing the cache
Note: In the process of using the cache, pay attention to ensure that the data in the database is consistent with the data in the cache. If the data in the database changes, you need to clean up the cached data in time .

code modification

DishController layer

    @Autowired
    private RedisTemplate redisTemplate;

list method

@GetMapping("/lsit")
public R<List<DishDto>> list(Dish dish){
        List<DishDto> dishDtoList = null;
        //动态构造key查询现根据菜品(分类 川 粤)和分类下的菜品,请求数据有分类 id 状态
    //dish_135456454553_1
    String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();

    //先从redis中获取缓存数据 强转
    dishDtoList =(List<DishDto>) redisTemplate.opsForValue().get(key);

    if (dishDtoList != null){
        //如果存在,直接返回,无需查询数据库
        return R.success(dishDtoList);
    }

    //构造查询条件  泛型Dish
    LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
    //添加条件
    queryWrapper.eq(dish.getCategoryId() != null,Dish::getCategoryId,dish.getCategoryId());

    //添加条件,查询状态为1(起售状态) 的菜品
    queryWrapper.eq(Dish::getStatus,1);
//        添加排序条件, 根据实体类Dish类sort排序 desc降序排序
    queryWrapper.orderByDesc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

    //调用dishService 把queryWrapper分装成list类型
    List<Dish> list = dishService.list(queryWrapper);


     dishDtoList = list.stream().map((item) -> {
        DishDto dishDto = new DishDto();
        //Dish实体类中其他属性都是空的,需要拷贝
        BeanUtils.copyProperties(item,dishDto);

        Long categoryId  = item.getCategoryId();//分类id
        //根据id查询分类对象
        Category category = categoryService.getById(categoryId);

        if (category != null) {
            //获取分类名称  需要一个Dto并给Dto赋值
            String categoryName = category.getName();
            //需要一个Dto并给Dto赋值
            dishDto.setCategoryName(categoryName);
//            此处categoryName包含菜品分类(categoryName)和其他普通属性
        }

        //查询口味
        Long dishID = item.getId();//菜品iD

        //根据菜品id查询口味
        LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加条件
        lambdaQueryWrapper.eq(DishFlavor::getDishId,dishID);
        //sQL:select * from dish_flavor where dish_id = ?
        //查出口味的集合
        List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
        dishDto.setFlavors(dishFlavorList);
        return dishDto;
    }).collect(Collectors.toList());

    //如果不存在,需要查询数据库,将查询到的菜品数据缓存到Redis,1h过期
    redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES);

    return R.success(dishDtoList);
}

Spring Cache
caches package data

Guess you like

Origin blog.csdn.net/weixin_55008454/article/details/130030166