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


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

    1、前后端分离介绍

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

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

    2、开发流程

    在这里插入图片描述
    接口(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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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/");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    启动项目访问
    在这里插入图片描述

    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    使用在请求的方法上

    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("新增套餐数据成功");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153

    再次启动项目访问生成的文档信息
    在这里插入图片描述

    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文件中

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

    在这里插入图片描述
    修改配置文件

    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;
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

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

    7.4、后端项目部署

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

  • 相关阅读:
    2024Selenium自动化常见问题!
    第三章 六大设计原则 (SOLID)
    c++ 贪心法
    最短路算法总结
    【打工日常】使用docker部署StackEdit编辑器-Markdown之利器
    Python基于PC版微信实现机器人
    Oauth2系列4:密码模式
    java毕业生设计在线药物配送系统计算机源码+系统+mysql+调试部署+lw
    h5禁止滚动穿透(页面)
    JUC面试点汇总
  • 原文地址:https://blog.csdn.net/baidu_39378193/article/details/126206295