瑞吉外賣實戰項目全攻略——優化篇第三天

瑞吉外賣實戰項目全攻略——優化篇第三天

該系列將記錄一份完整的實戰項目的完成過程,該篇屬於優化篇第三天,主要負責完成前後端分離問題

案例來自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的完成版://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