瑞吉外卖实战项目全攻略——优化篇第三天

时间:2022-10-31 08:07:01

该系列将记录一份完整的实战项目的完成过程,该篇属于优化篇第三天,主要负责完成前后端分离问题

案例来自B站黑马程序员Java项目实战《瑞吉外卖》,请结合课程资料阅读以下内容

该篇我们将完成以下内容:

  • 前后端分离开发
  • YApi接口管理平台
  • Swagger接口生成插件
  • 实际项目部署

前后端分离开发

我们在这一小节将主要介绍前后端分离的注意事项以及前端的部分知识

前后端分离开发介绍

在我们之前的工程中由于只有我们一人开发,所以程序大多数都堆积在一起进行开发:

瑞吉外卖实战项目全攻略——优化篇第三天

但是这种开发模式会导致很多的不利影响:

  • 开发效率低下
  • 对开发人员要求高,人员招聘困难
  • 前后端代码混杂在一个工程中,不便于管理
  • 开发人员同时负责前后端代码开发,人员分工不明确

所以我们提出了前后端分离开发的概念:

  • 在项目中,对于前端代码的开发由专门的前端开发人员开发,后端代码则有专门的后端人员来开发
  • 这样可以做到分工明确,各司其职,提高开发速率,前后端代码并行开发,加快项目开发进度,目前已经成主流开发方式

同时我们的项目分离后,打包部署方式也发生了变化:

  • 后端代码(Java代码)将会单独分离出来采用Tomcat技术进行打包部署
  • 前端工程(H5,CSS3,JS)将会单独分离出来采用Nginx技术进行打包部署

前后端分离开发流程

在目前,我们的前后端分离开发人员需要一个固定的流程来完成协商与开发:

瑞吉外卖实战项目全攻略——优化篇第三天

我们将流程分为以上四步:

  • 定义规范:定义我们的前后端http请求地址以及请求路径,请求方式,请求参数,响应数据等信息
  • 后端测试:前后代码开发完毕后,通常先进行内部测试,通过Postman以及mock来进行内部测试
  • 校验格式:内部测试完成后,双方将代码合并,同时开启项目,在项目中进行各个功能的调试与修复
  • 正式测试:将代码打包后发给专业测试人员进行调试测试,或发布测试版供外部人员测试等

其中定义规范是最为重要的一项,只有规范定义合适,前后代码才会开发完善,我们给出一个规范定义的模型:

瑞吉外卖实战项目全攻略——优化篇第三天

我们可以注意到里面包含了这几项重要内容:

  • 接口名称
  • 接口路径
  • 请求参数
  • 返回类型

我们在后续会逐渐介绍如何定义规范以及采用合适的工具加快规范以及接口的定义形式

前端技术栈展示

我们在这次项目中主要负责后端的任务,但是我们也需要适当了解前端的技术,这里简单介绍一下前端技术栈:

开发类型 开发名称 开发用途
开发工具 Visual Studio Code 前端开发软件
开发工具 hbuilder 前端开发软件
开发框架 nodejs 基本框架,相当于后端的JDK
开发框架 VUE 静态资源框架,用于布局静态资源H5,CSS3等
开发框架 ElementUI 静态资源框架,方便美化静态资源的部署
开发框架 mock 前端测试工具,模拟响应数据在前端的表现形式
开发框架 webpack 打包工具,前端有专门的打包类型,如js等

YApi接口管理平台

我们在这一小节将主要介绍一个API的网页管理平台

YApi接口管理平台介绍

我们首先来简单介绍一下YApi平台:

  • YApi是高效,易用,功能强大的API管理平台,旨在为开发,产品,测试人员提供更优雅的接口管理服务
  • 可以帮助开发者轻松创建,发布,维护API,YApi提供了优秀的交互体验,开发人员采用图形界面就可以实现接口的管理

YApi接口管理平台使用

我们可以直接在官网注册登录YApi并使用其产品

官网链接展示:YApi Pro-高效、易用、功能强大的可视化接口管理平台

下面我们来介绍YApi的具体使用细节:

  1. 首先我们进入官网,注册登录即可:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 登录之后进入到主页面:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 点击添加项目创建项目总览:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 我们进入到该项目的接口总览:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 首先我们可以添加分类,将各种类型的分类创建出来:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 然后我们在各分类中创建各类型接口即可:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 点击后我们可以在预览中看到基本信息:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 我们在编辑中编写其请求参数和响应数据等信息:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 完成信息编辑后来到预览页面可以看到修改的界面:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 点击运行可以进行基本的调试(这里仅支持Chrome,我就不做展示了~):

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 我们还可以在数据管理中进行数据导出(导出格式包括有html,md,json以及swaggerjson):

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 我们同样可以采用数据导入(这里导入数据通常采用postman和Swagger,我们在后面会介绍到):

瑞吉外卖实战项目全攻略——优化篇第三天

Swagger接口生成插件

我们在这一小节将主要介绍一个SwaggerAPI自动生成的IDEA插件

Swagger接口生成插件介绍

我们首先来简单介绍一下Swagger插件:

  • Swagger给定了我们固定的接口规范,我们只需要使用这些规范,就可以把我们代码中定义接口总结出来并做成页面展示
  • 由于Swagger固定接口规范过于繁琐,官方衍生出了knife4j作为java MVC框架集成Swagger生成API文档的增强解决方案

Swagger接口生成插件使用

下面我们来详细介绍Swagger的使用:

  1. 导入knife4j的maven坐标:
        <!--knife4j(Swagger)坐标-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>
  1. 导入knfie4j相关配置类
// 我们直接在WebMvcConfig中编写配置类即可

// 1.首先我们需要添加两个注解@EnableSwagger2,@EnableKnife4j表示开启Swagger以及knife4j

// 2.其次我们需要添加两个方法用分别来负责扫描接口并返回文档信息,以及编写文档基本信息

package com.qiuluo.reggie.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.qiuluo.reggie.common.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.List;

// 新导入两个注解@EnableSwagger2,@EnableKnife4j
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Bean
    public Docket createRestApi() {
        // 文档类型
        // (返回一个文档类型Docket,下面是返回文档的类型,基本为固定形式,除了basePackage,书写你的Controller包的位置)
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.qiuluo.reggie.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        // 描述文档内容
        return new ApiInfoBuilder()
                .title("瑞吉外卖")
                .version("1.0")
                .description("瑞吉外卖接口文档")
                .build();
    }
}
  1. 设置静态资源,否则接口文档无法访问
// 我们Swagger插件会自动生成一个doc.html网页,我们通过查询该网页来查看接口,我们需要将这个静态资源放开

package com.qiuluo.reggie.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.qiuluo.reggie.common.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.List;

@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始静态映射");

        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");

        registry.addResourceHandler("classpath:/static/*.html");
        registry.addResourceHandler("/static/**");

        // 系统自动帮忙生成这doc.html页面用于展示我们的接口信息,我们需要将他们放行
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    
}
  1. 在我们项目的login登录判定界面放行该请求路径
// 我们在之前的项目中设置了登录需求,我们在这里将该文档排除,否则我们需要先登录再访问该页面

package com.qiuluo.reggie.filter;

import com.alibaba.fastjson.JSON;
import com.qiuluo.reggie.common.BaseContext;
import com.qiuluo.reggie.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 检查用户是否已经完成登录
 */
@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;

        String requestURI = request.getRequestURI();

        log.info("拦截到请求:{}",requestURI);

        //定义不需要处理的请求路径("/doc.html","/webjars/**","/swagger-resources","/v2/api-docs")
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**",
                "/common/**",
                "/user/login",
                "/user/sendMsg",
                "/doc.html",
                "/webjars/**",
                "/swagger-resources",
                "/v2/api-docs"
        };

        boolean check = check(urls, requestURI);

        if(check){
            log.info("本次请求{}不需要处理",requestURI);
            filterChain.doFilter(request,response);
            return;
        }

        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));

            log.info("登录中...");
            log.info("线程id" + Thread.currentThread().getId());

            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);

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

        if(request.getSession().getAttribute("user") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));

            log.info("登录中...");
            log.info("线程id" + Thread.currentThread().getId());

            Long userId = (Long) request.getSession().getAttribute("user");
            BaseContext.setCurrentId(userId);

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

        log.info("用户未登录");
        response.getWriter().write(JSON.toJSONString(Result.error("NOTLOGIN")));
        return;

    }

    public boolean check(String[] urls,String requestURI){
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if(match){
                return true;
            }
        }
        return false;
    }
}

  1. 运行项目并打开doc.html页面即可:

瑞吉外卖实战项目全攻略——优化篇第三天

Swagger接口生成网页展示

下面我们来简单介绍一个doc.html网页都具备什么功能:

  1. 展示基本页面信息:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 查看所有基本信息(里面涵括了各种实体类以及服务层的方法和方法参数):

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 查看具体接口(包含了请求数据类型,请求参数,相应参数,响应状态码等信息):

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 进行网页调试(功能类似Postman):

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 文档导出功能(YApi需要的导入文件Swagger类型就是这里的OpenAPI类型):

瑞吉外卖实战项目全攻略——优化篇第三天

Swagger接口生成常用注解

Swagger为我们提供了相关注解来帮助书写doc文档:

注解 说明
@Api 用于请求的类上,表示对类的说明(Controller)
@ApiModel 用于类上,通常是实体类,表示一个返回数据的信息(domain,Result)
@ApiModelProperty 用于属性上,描述相应类的属性(name)
@ApiOperation 用于请求的方法上,说明方法的用途,作用
@ApiImplicitParams 用于请求的方法上,表示一组参数说明
@ApiImplicitParam 用于请求的方法上,表示单个参数说明

我们先给出实体类的常用注解示例:

package com.qiuluo.reggie.common;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

// @ApiModel(value = "返回类型")表示解释实体类

@Data
@ApiModel(value = "返回类型")
public class Result<T> implements Serializable {

    // @ApiModelProperty(value = "状态码")表示解释实体类属性
    
    @ApiModelProperty(value = "状态码")
    private Integer code; //编码:1成功,0和其它数字为失败

    @ApiModelProperty(value = "错误信息")
    private String msg; //错误信息

    @ApiModelProperty(value = "数据信息")
    private T data; //数据

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

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

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

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

}

我们再给出服务层的常用注解案例:

package com.qiuluo.reggie.controller;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiuluo.reggie.common.Result;
import com.qiuluo.reggie.domain.Category;
import com.qiuluo.reggie.domain.Dish;
import com.qiuluo.reggie.domain.Setmeal;
import com.qiuluo.reggie.domain.SetmealDish;
import com.qiuluo.reggie.dto.DishDto;
import com.qiuluo.reggie.dto.SetmealDto;
import com.qiuluo.reggie.service.impl.CategoryServiceImpl;
import com.qiuluo.reggie.service.impl.DishServiceImpl;
import com.qiuluo.reggie.service.impl.SetmealDishServiceImpl;
import com.qiuluo.reggie.service.impl.SetmealServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

// @Api(tags = "套餐接口数据")表示服务层名称

@Slf4j
@RestController
@RequestMapping("/setmeal")
@Api(tags = "套餐接口数据")
public class SetmealController {

    @Autowired
    private DishServiceImpl dishService;

    @Autowired
    private SetmealServiceImpl setmealService;

    @Autowired
    private SetmealDishServiceImpl setmealDishService;

    @Autowired
    private CategoryServiceImpl categoryService;

    // @ApiOperation(value = "新增接口")表示服务层接口名称
    
    /*
    @ApiImplicitParams(
            {
                    @ApiImplicitParam(name = "page",value = "页码",required = true),
                    @ApiImplicitParam(name = "pageSize",value = "每页大小",required = true)
            }
    )
    用于解释内部参数信息
    @ApiImplicitParams作为整体框架,内部存在多个参数时,需要采用{}包括
    @ApiImplicitParam作为内部信息,name表示参数名,value表示文档名,required表示是否必须
    
    */
    
    /**
     * 新增
     * @param setmealDto
     * @return
     */
    @PostMapping
    @ApiOperation(value = "新增接口")
    @CacheEvict(value = "setmealCache",allEntries = true)
    public Result<String> save(@RequestBody SetmealDto setmealDto){

        setmealService.saveWithDish(setmealDto);

        log.info("套餐新增成功");

        return Result.success("新创套餐成功");
    }

    /**
     * 分页
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("page")
    @ApiOperation(value = "分页接口")
    @ApiImplicitParams(
            {
                    @ApiImplicitParam(name = "page",value = "页码",required = true),
                    @ApiImplicitParam(name = "pageSize",value = "每页大小",required = true),
                    @ApiImplicitParam(name = "name",value = "查找值",required = false)
            }
    )
    public Result<Page> page(int page, int pageSize, String name){

        // 构造分页器
        Page<Setmeal> pageInfo = new Page<>(page,pageSize);
        Page<SetmealDto> setmealDtoPage = new Page<>();

        // 构造条件
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(name != null,Setmeal::getName,name);
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        // 查询
        setmealService.page(pageInfo);

        // 赋值
        BeanUtils.copyProperties(pageInfo,setmealDtoPage,"records");

        List<Setmeal> records = pageInfo.getRecords();

        List<SetmealDto> list = records.stream().map((item) -> {

            SetmealDto setmealDto = new SetmealDto();

            BeanUtils.copyProperties(item,setmealDto);

            // 将CategoryName复制进去
            Long categoryId = item.getCategoryId();
            Category category = categoryService.getById(categoryId);

            if(category != null){
                String categoryName = category.getName();
                setmealDto.setCategoryName(categoryName);
            }

            return setmealDto;
        }).collect(Collectors.toList());

        setmealDtoPage.setRecords(list);

        // 返回结果
        return Result.success(setmealDtoPage);
    }

    /**
     * 删除
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation(value = "删除接口")
    @CacheEvict(value = "setmealCache",allEntries = true)
    public Result<String> delete(@RequestParam List<Long> ids){

        setmealService.removeWithDish(ids);

        return Result.success("删除成功");
    }

    /**
     * 修改状态
     * @param ids
     * @return
     */
    @PostMapping("/status/0")
    @ApiOperation(value = "修改状态接口")
    @CacheEvict(value = "setmealCache",allEntries = true)
    public Result<String> closeStatus(@RequestParam List<Long> ids){

        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.in(Setmeal::getId,ids);

        List<Setmeal> setmeals = setmealService.list(queryWrapper);

        for (Setmeal setmeal:setmeals
        ) {
            setmeal.setStatus(0);
            setmealService.updateById(setmeal);
        }

        return Result.success("修改成功");
    }

    /**
     * 修改状态
     * @param ids
     * @return
     */
    @PostMapping("/status/1")
    @ApiOperation(value = "修改状态接口")
    @CacheEvict(value = "setmealCache",allEntries = true)
    public Result<String> openStatus(@RequestParam List<Long> ids){

        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.in(Setmeal::getId,ids);

        List<Setmeal> setmeals = setmealService.list(queryWrapper);

        for (Setmeal setmeal:setmeals
        ) {
            setmeal.setStatus(1);
            setmealService.updateById(setmeal);
        }

        return Result.success("修改成功");
    }

    /**
     * 查询
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation(value = "查询接口")
    public Result<SetmealDto> getById(@PathVariable Long id){

        SetmealDto setmealDto = new SetmealDto();

        // 将普通数据传入

        Setmeal setmeal = setmealService.getById(id);

        BeanUtils.copyProperties(setmeal,setmealDto);

        // 将菜品信息传递进去

        LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId,id);

        List<SetmealDish> list = setmealDishService.list(queryWrapper);

        setmealDto.setSetmealDishes(list);

        return Result.success(setmealDto);
    }

    /**
     * 修改
     * @param setmealDto
     * @return
     */
    @PutMapping
    @ApiOperation(value = "修改接口")
    @CacheEvict(value = "setmealCache",allEntries = true)
    public Result<String> update(@RequestBody SetmealDto setmealDto){

        setmealService.updateById(setmealDto);

        return Result.success("修改成功");

    }

    /**
     * 根据条件查询套餐数据
     * @param setmeal
     * @return
     */
    @GetMapping("/list")
    @ApiOperation(value = "条件查询接口")
    @Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status")
    public Result<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 Result.success(list);
    }


    /**
     * 移动端点击套餐图片查看套餐具体内容
     * 这里返回的是dto 对象,因为前端需要copies这个属性
     * 前端主要要展示的信息是:套餐中菜品的基本信息,图片,菜品描述,以及菜品的份数
     * @param SetmealId
     * @return
     */
    @GetMapping("/dish/{id}")
    @ApiOperation(value = "点击查看接口")
    public Result<List<DishDto>> dish(@PathVariable("id") Long SetmealId) {
        LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId, SetmealId);
        //获取套餐里面的所有菜品  这个就是SetmealDish表里面的数据
        List<SetmealDish> list = setmealDishService.list(queryWrapper);

        List<DishDto> dishDtos = list.stream().map((setmealDish) -> {
            DishDto dishDto = new DishDto();
            //其实这个BeanUtils的拷贝是浅拷贝,这里要注意一下
            BeanUtils.copyProperties(setmealDish, dishDto);
            //这里是为了把套餐中的菜品的基本信息填充到dto中,比如菜品描述,菜品图片等菜品的基本信息
            Long dishId = setmealDish.getDishId();
            Dish dish = dishService.getById(dishId);
            BeanUtils.copyProperties(dish, dishDto);

            return dishDto;
        }).collect(Collectors.toList());

        return Result.success(dishDtos);
    }
}

Swagger接口生成注解网页展示

在进行部分注解修饰后,我们的doc网页会发生部分变化,我们来简单看一下:

  1. 实体类方面所有数据修改名称或者携带注释:

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 服务层接口名称发生变化参数也携带注释:

瑞吉外卖实战项目全攻略——优化篇第三天

实际项目部署

在前面我们已经完成了项目并且掌握了项目部署的根本需求,下面我们来完成项目部署

部署架构

我们首先给出部署架构图:

瑞吉外卖实战项目全攻略——优化篇第三天

我们可以看到:

  • 客户和微信端用户可以通过网络来连接到我们的服务器发送请求
  • 我们首先通过第一个服务器来使用Nginx部署前端页面
  • 然后第一个服务器通过反向代理传给第二个服务器使用Tomcat部署后端需求
  • 而我们的第二个服务器连接着Mysql数据库和Redis数据库等信息

部署环境说明

我们给出整个部署所需要的环境:

  1. 服务器A:192.168.44.128
  • Nginx:部署前端项目,部署反向代理
  • Mysql:主从复制结构中的主库
  1. 服务器B:192.168.44.129
  • JDK:运行Java项目
  • git:版本控制工具
  • maven:项目构建工具
  • jar:Spring Boot项目打成jar包基于内置Tomcat运行
  • Mysql:主从复制结构中的从库
  1. 本机:localhost
  • Redis:缓存中间件

前端项目部署

我们首先来完成前端项目的部署:

  1. 在服务器中安装Nginx,并将课程中的dist目录(已打包的前端数据)上传至Nginx下的html页面

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 修改Nginx配置文件nginx.conf

瑞吉外卖实战项目全攻略——优化篇第三天

  1. 在主机进行网页访问,访问成功即可(输入192.168.44.128即可):

瑞吉外卖实战项目全攻略——优化篇第三天

到这里我们的前端部署就结束了

我们来简单解释一下以上操作:

首先是页面展示问题:

  • location / : 前端页面部署文件夹,root我们将文件部署文件夹更换到dist中;index负责将主页面更换为dist下的index.html

然后是请求跳转问题:

  • location ^~ : 请求跳转,rewrite负责url重写;proxy_pass负责url请求传送地址书写

后端项目部署

我们再来完成后端项目的部署:

  1. 在服务器B中安装JDK,git,maven,Mysql,使用git clone命令将git远程仓库的代码克隆下来:
# 进入到/usr/local/javaapp目录下
cd /usr/local/javaapp

# git命令复制(可以到我的git仓库下载V1.2的完成版:https://gitee.com/QiuLuoYuWeiLiang/qiu-luo-reggie)
git clone SSH地址
  1. 将资料中的reggieStart.sh文件上传到服务器B中,通过chmod命令设置权限
# 上传直接采用FinalShell的上传机制,这里不做解释了

# 上传后我们可以通过chmod设置权限
chmod 777 reggieStart.sh

# 下面我们给出reggieStart.sh文件做简单讲解

#!/bin/sh
echo =================================
echo  自动化部署脚本启动
echo =================================

echo 停止原来运行中的工程
APP_NAME=qiu-luo-reggie # 这里的名字是你的项目名称(记得修改)

tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`
if [ ${tpid} ]; then
    echo 'Stop Process...'
    kill -15 $tpid
fi
sleep 2
tpid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v kill|awk '{print $2}'`
if [ ${tpid} ]; then
    echo 'Kill Process!'
    kill -9 $tpid
else
    echo 'Stop Success!'
fi

echo 准备从Git仓库拉取最新代码
cd /usr/local/javaapp/qiu-luo-reggie # 这里改为你的目录(记得修改)

echo 开始从Git仓库拉取最新代码
git pull
echo 代码拉取完成

echo 开始打包
output=`mvn clean package -Dmaven.test.skip=true`

cd target

echo 启动项目
nohup java -jar reggie_take_out-1.0-SNAPSHOT.jar &> reggie_take_out.log &
echo 项目启动完成
  1. 然后我们直接执行sh文件即可,后端开启
# 执行sh文件(第一次需要下载大量依赖,时间较长)
./reggieStart.sh
  1. 可查看日志
# 查看log日志
vim reggie/target/reggie_take_out.log

# 可以实时查看
tail -f reggie/target/reggie_take_out.log

结束语

整个项目到这里就结束了,希望能为你带来帮助~

附录

该文章属于学习内容,具体参考B站黑马程序员的Java项目实战《瑞吉外卖》

这里附上视频链接:项目优化Day3-01-本章内容介绍_哔哩哔哩_bilibili