SpringBoot前后端分离开发拆分和部署

2年前 (2022) 程序员胖胖胖虎阿
415 0 0

SpringBoot前后端分离开发拆分和部署

1、前后端分离介绍

以前的项目开发都是将前端代码和后端代码混合在一个java项目中,这样是不利于管理和项目进度的
前后端分离开发,就是在项目开发过程中,对于前端代码的开发由专门的前端开发人员负责,后端代码则由后端开发
人员负责,这样可以做到分工明确、各司其职,提高开发效率,前后端代码并行开发,可以加快项目开发进度。
目前,前后端分离开发方式已经被越来越多的公司所采用,成为当前项目开发的主流开发方式。
前后端分离开发后,从工程结构上也会发生变化,即前后端代码不再混合在同一个maven工程中,而是分为前端工程和后端工程

前端项目一般会打包部署到【nginx服务器上】
后端代码一般会打包部署到【Tomcat服务器上】
前端通过指定的接口来进行访问后端就可以实现数据交换,但是前后端分离项目最重要的一点就是需要【配置跨域】,否则前后端会请求不到

2、开发流程

SpringBoot前后端分离开发拆分和部署
接口(API接口):就是一个请求的http地址,主要是定义:请求路径、请求参数、请求方式、响应数据等内容

3、前端技术栈

开发工具:
1、Visual Studio Code
2、hbuilder

技术框架
1、node.js
2、vue
3、ElementUI
4、mock:测试模拟数据
5、webpack:打包工具

4、YAPI定义接口

YApi是高效、易用、功能强大的api管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助
开发者轻松创建、发布、维护API,YApⅰ还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写
入工具以及简单的点击操作就可以实现接口的管理。
YApi让接口开发更简单高效,让接口的管理更具可读性、可维护性,让团队协作更合理。

官网地址:https://hellosean1025.github.io/yapi/
需要使用yapi需要自己部署

YAPL安装和部署

自己百度,后面我整理一遍也会发出

5、Swagger

使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,
就可以做到生成各种格式的接口文档,以及在线接口调试页面等等。

官网地址:https://swagger.io/

knife4j是为ava MVC框架集成Swagger生成Api文档的增强解决方案。

<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-spring-boot-starter -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

5.1、使用步骤

1、导入坐标
2、导入相关配置

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

    @Bean
    public Docket createRestApi() {
        // 定义文档的类型
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                // 扫描控制器生成接口
                .apis(RequestHandlerSelectors.basePackage("com.zcl.reggie.controller"))
                .build();
    }

    /**
     * 描述接口文档
     * @return
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title("瑞吉外卖").version("1.0").description("瑞吉外卖接口文档").build();
    }
}

3、设置静态资源。否则接口文档页面无法访问
改接口生成页面不是我们自己写的是由筷架自动生成的,需要在上面的类中添加该方法

/**
 * 设置静态资源映射放行
 * @param registry
 */
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    log.info("开始进行静态支援映射...");
    registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
    registry.addResourceHandler("webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}

4、在LonginCheckFilter中设置不需要处理请求路径

启动项目访问
SpringBoot前后端分离开发拆分和部署

6、Swagger常用注解

注解 说明
@Api 用在请求的类上,例如Controller,表示对类的说明
@ApiModel 用在类上,通常是实体类,表示一个返回值响应数据的信息
@ApiModelProperty 用在属性上,描述响应类的属性
@ApiOperation 用在请求的方法上,说明方法的用途、作用
@ApilmplicitParams 用在请求的方法上,表示一组参数说明
@ApilmplicitParam 用在@ApilmplicitParams注解中,指定一个请求参数的各个方面

使用在类或属性上的注解示例

/**
 * 套餐
 */
@Data
@ApiModel("套餐")
public class Setmeal implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("主键")
    private Long id;


    //分类id
    @ApiModelProperty("分类id")
    private Long categoryId;


    //套餐名称
    @ApiModelProperty("套餐名称")
    private String name;


    //套餐价格
    @ApiModelProperty("套餐价格")
    private BigDecimal price;


    //状态 0:停用 1:启用
    @ApiModelProperty("状态")
    private Integer status;


    //编码
    @ApiModelProperty("编码")
    private String code;


    //描述信息
    @ApiModelProperty("描述信息")
    private String description;


    //图片
    @ApiModelProperty("图片")
    private String image;


    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;


    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;


    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;


    //是否删除
    @ApiModelProperty("删除标识")
    private Integer isDeleted;
}

使用在请求的方法上

package com.zcl.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zcl.reggie.common.R;
import com.zcl.reggie.dto.SetmealDto;
import com.zcl.reggie.entity.Category;
import com.zcl.reggie.entity.Setmeal;
import com.zcl.reggie.service.CategoryService;
import com.zcl.reggie.service.SetmealDishService;
import com.zcl.reggie.service.SetmealService;
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.CacheManager;
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;

/**
 * 项目名称:reggie_take_out
 * 描述:套餐管理控制器
 *
 * @author zhong
 * @date 2022-08-04 11:14
 */
@Slf4j
@RestController
@RequestMapping("/setmeal")
@Api(tags = "套餐相关接口")
public class SetmealController {
    @Autowired
    private SetmealService setmealService;

    @Autowired
    private SetmealDishService setmealDishService;

    /**
     * 注入分类对象
     */
    @Autowired
    private CategoryService categoryService;


    /**
     * 前端页面根据条件查询套餐信息
     * @param setmeal
     * @return
     */
    @Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status")
    @GetMapping("/list")
    @ApiOperation("前端页面根据条件查询套餐信息接口")
    public R<List<Setmeal>> list(Setmeal setmeal){
        // 1、封装查询条件
        LambdaQueryWrapper<Setmeal> lambda = new LambdaQueryWrapper<>();
        lambda.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId()).eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
        lambda.orderByDesc(Setmeal::getCreateTime);

        // 查询出套餐列表数据
        List<Setmeal> setmealList = setmealService.list(lambda);
        return R.success(setmealList);
    }

    /**
     * 根据传递的is进行删除套餐
     * @param ids
     * @return
     */
    @CacheEvict(value = "setmealCache",allEntries = true)
    @DeleteMapping
    @ApiOperation(value = "根据传递的is进行删除套餐接口")
    public R<String> delete(@RequestParam List<Long> ids){
        log.info("接收到需要删除套餐的id:{}",ids);
        setmealService.removeWithDish(ids);
        return R.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 R<Page> page(int page,int pageSize,String name){
        // 1、f分页构造器
        Page<Setmeal> pageInfo = new Page<>(page, pageSize);

        // 真正返回的封装数据】
        Page<SetmealDto> dtoPage = new Page<>();

        // 2、创建分页查询对象【模糊查询】
        LambdaQueryWrapper<Setmeal> lambdaQuery = new LambdaQueryWrapper<>();
        lambdaQuery.eq(name != null,Setmeal::getName,name);
        // 根据创建时间倒排序
        lambdaQuery.orderByDesc(Setmeal::getCreateTime);
        setmealService.page(pageInfo,lambdaQuery);

        // 对象拷贝
        BeanUtils.copyProperties(pageInfo,dtoPage,"records");
        List<Setmeal> records = pageInfo.getRecords();
        // 遍历获取我们需要的对象
        List<SetmealDto> list = records.stream().map((item) -> {
            SetmealDto setmealDto = new SetmealDto();
            // 拷贝对象
            BeanUtils.copyProperties(item,setmealDto);

            // 获取分类的id
            Long categoryId = item.getCategoryId();
            // 根据分类的id查询分类对象
            Category category = categoryService.getById(categoryId);
            // 判断不为空的收获
            if(category != null){
                // 赋值分了名称
                String categoryName = category.getName();
                setmealDto.setCategoryName(categoryName);
            }
            return setmealDto;
        }).collect(Collectors.toList());
        // 给返回对象重新赋值
        dtoPage.setRecords(list);
        return R.success(dtoPage);
    }

    /**
     * 新增套餐数据保存
     * @param setmealDto
     * @return
     */
    @CacheEvict(value = "setmealCache",allEntries = true)
    @PostMapping
    @ApiOperation(value = "新增套餐数据保存接口")
    public R<String> save(@RequestBody SetmealDto setmealDto){
        log.info("进入套餐新增保存:{}",setmealDto);
        setmealService.saveWithDish(setmealDto);
        return R.success("新增套餐数据成功");
    }
}

再次启动项目访问生成的文档信息
SpringBoot前后端分离开发拆分和部署

7、项目拆分部署

7.1、部署架构

1、nginx前端部署服务器
2、tomcat后端代码部署服务器
3、MySQL主从复制,两台服务器
4、redis缓存服务器
也可以使用docker进行开发,减少服务器的使用

7.2、部署环境说明

服务器1:
Nginx:部署前端项目、配置反向代理
MySQL:主从复制结果中的主库

服务器2:
jdk:运行java项目
git:版本控制工具
maven:项目构建工具
jar:SpringBoot项目打包成jar包基于内置Tomcat运行
MySQL:主从复制结构中的从库

服务器三:
Redis:缓存中间件

7.3、前端项目部署

将前端项目进行打包然后上传到Linux虚拟机的nginx的html文件中

前端有前端的打包方式,可以自己去了解一下

SpringBoot前后端分离开发拆分和部署
修改配置文件

server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html/dist;
            # root   html;
            index  index.html;
            # proxy_pass http://targetserver;
        }
        # 反向代理配置,将请求转发到指定的服务
        location ^~ /api/ {
        	# api重写,正则表达式删除api 
        	# /api/employee/login -----> /employee/login
            rewrite ^/api/(.*)$ /$1 break;
            proxy_pass http://targetserver;
        }
        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
}

刷新配置文件和启动nginx访问宿主机ip80端口,即可成功

7.4、后端项目部署

上传jar包后台运行,或者使用自动部署脚本来完成拉取和启动

版权声明:程序员胖胖胖虎阿 发表于 2022年9月3日 下午4:24。
转载请注明:SpringBoot前后端分离开发拆分和部署 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...