手把手教你前后分离架构(四) 前后端数据交互

前面的章节,系统雏形已经初步形成,前端项目的展示数据为固定数据活mock数据,今天我们来一起完善前后端项目数据交互。

1、后台统一接口

日常工作中,我们开发接口时,一般都会涉及到参数校验、异常处理、封装结果返回等处理。如果每个后端开发人员在参数校验、异常处理等都是各写各的,没有统一处理的话,代码即不优雅,也不容易维护。前端也很难对数据统一操作。所以,作为一名合格的后端开发工程师,我们需要统一校验参数,统一异常处理、统一结果返回,让代码更加规范、可读性更强、更容易维护。

1.1、集成swagger

后端服务的API接口可以查看文档和调试,通过swagger可减少与前端人员沟通成本,也可帮助后端人员了解后端API详情。

添加pom依赖

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.1</version>
        </dependency>

添加启动类

@Component
@Configuration
@EnableSwagger2
public class Swagger2Config extends WebMvcConfigurationSupport {
    @Bean
    public Docket createRestApi(){

        ParameterBuilder tokenPar = new ParameterBuilder();
        List<Parameter> pars = new ArrayList<>();
        tokenPar.name(Constant.token).description("令牌").
                modelRef(new ModelRef("string")).
                parameterType("header").required(false).build();
        pars.add(tokenPar.build());

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xgg"))
                .paths(PathSelectors.any())
                .build().globalOperationParameters(pars);
    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder().build();
    }

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("doc.html").
                addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
        //将所有/static/** 访问都映射到classpath:/static/ 目录下
        registry.addResourceHandler("/static/**")
                .addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX +"/static/");
        super.addResourceHandlers(registry);
    }
}

1.2、统一返回格式

success 接口返回是否成功状态;code返回的状态码;message状态码说明,info返回数据对象。

{
"success": true,  //是否成功 true 成功, false 失败
  "code": 200,  //返回状态码
  "message": "成功", //返回状态说明
  "info": "" //返回数据对象  
}

返回实体定义

@Slf4j
@ApiModel(value = "统一返回结果")
public class Result<T> {
    @ApiModelProperty("是否成功")
    private boolean success;
    @ApiModelProperty("返回码")
    private Integer code;
    @ApiModelProperty("返回码说明")
    private String message;
    @ApiModelProperty("返回对象数据")
    private T info;

    /**
     * 成功
     */
    public static Result ok() {
        Result r = new Result();
        r.setSuccess(true);
        r.setCode(ResultEnum.OK.getCode());
        r.setMessage(ResultEnum.OK.getName());
        return r;
    }

    /**
     * 错误
     */
    public static Result error() {
        Result r = new Result();
        r.setSuccess(false);
        r.setCode(ResultEnum.ERROR.getCode());
        r.setMessage(ResultEnum.ERROR.getName());
        return r;
    }

    /**
     * 无权限
     */
    public static Result noAccess() {
        Result r = new Result();
        r.setSuccess(false);
        r.setCode(ResultEnum.SIGNATURE_NOT_MATCH.getCode());
        r.setMessage(ResultEnum.SIGNATURE_NOT_MATCH.getName());
        return r;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }

    public Result code(Integer value) {
        this.setCode(value);
        return this;
    }
    public Result message(String value) {
        this.setMessage(value);
        return this;
    }

    public Result info(T value) {
        this.setInfo(value);
        return this;
    }
}

1.3、统一参数校验

添加依赖

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

添加参数实体类

@Data
public class TestParam {

    @NotBlank(message = "姓名不能为空")
    @ApiModelProperty(value = "姓名",required = true)
    private String username;

    @NotNull(message = "年龄不能为空")
    @ApiModelProperty(value = "年龄",required = true)
    private Long age;

    @ApiModelProperty(value = "毕业学校")
    private String school;
}

@NotNull适用于基本数据类型(Integer,Long,Double等等),当 @NotNull 注解被使用在 String 类型的数据上,则表示该数据不能为 Null(但是可以为 Empty)

@NotBlank适用于 String 类型的数据上,加了@NotBlank 注解的参数不能为 Null 且 trim() 之后 size > 0

@NotEmpty适用于 String、Collection集合、Map、数组等等,加了@NotEmpty 注解的参数不能为 Null 或者 长度为 0

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。

@Valid属于javax.validation包下,是jdk给提供的 是使用Hibernate validation的时候使用

@Validated是org.springframework.validation.annotation包下的,是spring提供的 是只用Spring Validator校验机制使用

说明:java的JSR303声明了@Valid这类接口,而Hibernate-validator对其进行了实现
@Validation对@Valid进行了二次封装,在使用上并没有区别,但在分组、注解位置、嵌套验证等功能上有所不同,这里主要就这几种情况进行说明。
在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:
分组
@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述。@Valid:作为标准JSR-303规范,还没有吸收分组的功能。
注解位置
@Validated:用在类型、方法和方法参数上。但不能用于成员属性(field)
@Valid:可以用在方法、构造函数、方法参数和成员属性(field)上 所以可以用@Valid实现嵌套验证。

1.4、统一异常

前面咱们已经实现了前后交互的数据规范和基本流程,但是如果后端出现异常前端如何应对呢。

测试发现返回的数据和咱们制定的数据规范不一致,前端只能通过catch来捕获异常,这显然不利于前端同学研发的便利性。

 所以我们需要再后端建立统一异常规范,全局捕获已知和未知异常,按照我们之前制定的数据交互规范来统一返回数据。

添加全局异常捕获

@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {

   @ExceptionHandler({HttpMessageNotReadableException.class, ConstraintViolationException.class,MissingServletRequestParameterException.class})
   public Result messageExceptionHandler(Exception ex) {
      log.error(ResultEnum.BODY_NOT_MATCH.getName(), ex);
      return Result.error().code(ResultEnum.BODY_NOT_MATCH.getCode()).message(ResultEnum.BODY_NOT_MATCH.getName());
   }

   @ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
   public Result controllerException(Exception e, BindingResult bindingResult) {
      List<FieldError> listErrors = bindingResult.getFieldErrors();
        if (!listErrors.isEmpty()) {
         FieldError fieldError = listErrors.get(0);
            return Result.error().code(ResultEnum.BODY_NOT_MATCH.getCode()).message(ResultEnum.BODY_NOT_MATCH.getName()).info(fieldError.getField()+""+fieldError.getDefaultMessage());
        }
      return Result.error().code(ResultEnum.BODY_NOT_MATCH.getCode()).message(ResultEnum.BODY_NOT_MATCH.getName());
   }

   @ExceptionHandler({HttpRequestMethodNotSupportedException.class, MethodArgumentTypeMismatchException.class})
   public Result requestExceptionHandler(Exception ex ) {
      log.error(ResultEnum.BODY_NOT_MATCH.getName(), ex);
      return Result.error().code(ResultEnum.BODY_NOT_MATCH.getCode()).message(ResultEnum.BODY_NOT_MATCH.getName());
   }

   /**
    * 自定义异常
    */
   @ExceptionHandler({ BizException.class })
   public Result bizException(BizException e) {
      log.error("自定义系统异常", e);
      return Result.error().message(e.getMsg()).code(e.getCode());
   }

   /**
    * 全局异常
    */
   @ExceptionHandler({ Exception.class })
   // @ResponseStatus(HttpStatus.BAD_REQUEST)
   public Result sysException(Exception e) {
      log.error("系统异常", e);
      return Result.error();
   }

添加自定义异常

@Data
public class BizException extends RuntimeException {
    private Integer code;

    private String msg;
    public BizException(Integer code, String msg) {
        this.msg = msg;
        this.code = code;
    }
    public BizException(String msg) {
        this.msg = msg;
        this.code = ResultEnum.ERROR.getCode();
    }
    public BizException(String msg, Throwable t) {
        super(t);
        this.msg = msg;
        this.code = ResultEnum.ERROR.getCode();
    }
}

使用自定义异常

throw new SqException(201,"测试自定义异常");

1.5、验证测试

@ApiOperation(value = "测试查询")
@GetMapping("/test")
public Result test(@Valid TestParam testParam, BindingResult result) throws Exception{
    List<FieldError> fieldErrors = result.getFieldErrors();
    if (!fieldErrors.isEmpty()) {
        return Result.error().info(fieldErrors.get(0).getDefaultMessage());
    }
    return Result.ok().info(testParam);
}

@ApiOperation(value = "测试接口1")
@PostMapping("/test1")
public Result test1(@Valid TestParam testParam) throws Exception{
    throw new BizException(201,"测试自定义异常");
    testService.test("哈哈哈哈哈哈哈");
    return Result.ok().info(testParam);
}

1.6、接口规范

前后端交互接口遵循统一RESTful接口原则,构建优良的 REST API

避免在 URI 中使用动词

HTTP Method动词与 URI 的组合,比如 GET: / user/。一个端点可以被解释为对某种资源进行的某个动作。比如, POST: /user 代表“创建一个新的 user”而不是saveUser;查询用户:GET: /user而不是getUser。
HTTP Method: GET 代表查,POST代表增,PUT代表改, DELETE 代表删。

2、前后端交互

2.1前端整合axios

axios时目前最流行的ajax封装库之一,用于很方便地实现ajax请求的发送。

添加依赖

npm install axios

使用axios

我们使用之前的test.vue页面和SpringBoot后台的测试接口做测试。

axios
    .get('http://127.0.0.1:8888/test', {
      params: {
        username: 12345,
        age: 20
      }
    }).then(({data}) => {
      if(data && data.success){
        this.$message.success(data.message)
      }else{
        this.$message.error(data.info)
      }
    }).catch(error => { // 请求失败处理
      this.$message.error("系统异常,请稍后重试!");
    });
}

点击测试按钮会发现浏览器控制台报错了

这是跨域问题引起的,因为我们浏览器访问的是9081端口,而访问后台的端口是8888端口,而且部署到服务器以后可能不光两者端口不同,连ip可能都会不同了。这就是跨域问题。

2.2 、SpringBoot跨域支持

后端跨域有很多种方式,咱们这里采用过滤器的方式。

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //1,允许任何来源
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        //2,允许任何请求头
        corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);
        //3,允许任何方法
        corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);
        //4,允许凭证
        corsConfiguration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(source);
    }
}  

重启后端服务,测试点击按钮发现成功返回后端数据。

2.3、表单验证实践

<template>
<div>
  <el-form :inline="true" :model="formInline" :rules="rules" ref="formInline" class="demo-form-inline">
    <el-form-item label="姓名" prop="username">
      <el-input v-model="formInline.username" placeholder="姓名"></el-input>
    </el-form-item>
    <el-form-item label="年龄" prop="age">
      <el-input v-model="formInline.age" placeholder="年龄"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="onSubmit('formInline')">查询</el-button>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="test" icon="el-icon-search">测试</el-button>
    </el-form-item>
  </el-form>
</div>
</template>

<script>
import axios from 'axios'
export default {
  name: "test1",
  data() {
    return {
      formInline: {
        username: '',
        age: ''
      },
      rules: {
        username: [
          {required: true, message: '请姓名', trigger: 'blur'},
          {min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur'}
        ],
        age: [
          { required: true, message: '年龄不能为空', trigger: 'blur'},
          { type: 'number', message: '年龄必须为数字值'}
        ]
      }
    }
  },
  methods:{
    test(){
      axios
        .get('http://127.0.0.1:8888/test', {
          params: {
            username: 12345,
            age: 20
          }
        }).then(({data}) => {
          if(data && data.success){
            this.$message.success(data.message)
          }else{
            this.$message.warning(data.info)
          }
        }).catch(error => { // 请求失败处理
          this.$message.error("系统异常,请稍后重试!");
        });
    },
    onSubmit(formName){
      this.$refs[formName].validate((valid) => {
        if (valid) {
          axios.get('http://127.0.0.1:8888/test', {
            params: this.formInline
          }).then(({data}) => {
            if (data && data.success) {
              this.$message.success(data.message)
            } else {
              this.$message.warning(data.info)
            }
          }).catch(error => { // 请求失败处理
            this.$message.error("系统异常,请稍后重试!");
          });
        }
      })
    },
  }
}
</script>

 

关注公众号”小猿架构“,发送 "前后分离架构" ,下载课程视频+课程源码+课件。

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源介绍 本次提供的资源是一个基于SSM(Spring + SpringMVC + MyBatis)框架开发的高校专业信息管理系统设计与实现的完整项目,附带详尽的毕业论文以及源代码压缩包。该项目旨在通过现代化的信息技术手段,实现对高校专业信息的有效管理,提升管理效率,为高校的学与管理工作提供有力支持。 该系统采用了当前流行的SSM框架进行开发,确保了系统的稳定性、可扩展性和可维护性。Spring框架提供了强大的依赖注入和面向切面编程的功能,使得系统的各个组件能够松耦合地协同工作;SpringMVC框架则负责处理用户的请求和响应,实现了MVC模式的分离,使得前端页面与后端逻辑的交互更加清晰明了;MyBatis框架则提供了灵活的数据库操作方式,使得数据的增删改查变得简单高效。 在功能方面,该系统涵盖了专业信息的添加、修改、删除、查询等基本操作,同时支持对专业数据进行统计分析,为学校的学管理和决策提供数据支持。系统界面简洁美观,操作便捷,用户体验良好。 除了系统本身的功能实现,本次资源还附带了详细的毕业论文,对系统的设计思路、实现过程以及遇到的问题进行了深入的剖析和讨论,对于想要了解SSM框架开发流程以及高校专业信息管理系统的读者来说,具有很高的参考价值。 此外,该项目的源代码清晰规范,注释详尽,便于读者阅读和理解。同时,系统采用了模块化的设计思想,各个模块之间相对独立,方便进行二次开发和定制,以满足不同高校的实际需求。 总的来说,这是一份非常有价值的学习资源,无论是对于SSM框架的学习,还是对于高校专业信息管理系统的开发,都能提供很大的帮助。
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 课程设计-基于Vue+Express实现的新闻聚合网站项目源码+运行说明(含前端+后端).zip 本次项目是Web编程项目demo,为新闻聚合网站(聚合了网易新闻/新浪新闻/新华网/人民网),在此网站中有独立的热搜模块(较为简陋),个新闻平台热点关键词词云及其数据量,同时您可以在网站中进行筛选搜索,结果会以模态框形式弹出,点击对应的结果即可跳转到对应的新闻原URL,所有结果均为定时爬虫所爬取。 本站使用了Vue3+NaiveUI作为前端框架与组件,使用了Nodejs+Express作为项目后端,阿里云MySQL数据库作为项目数据库。 项目运行 本项目前后端分离前端为front文件夹,后端为back文件夹,进入前后端文件夹后分别运行 ```shell npm install ``` 下载相应的module文件(如果没有的话) 由于本项目为demo项目,因此并没有做过深的负载等考虑,为开发模式,仅作为演示,因此Vue项目并未打包,均为源码开发模式呈现。 在front文件夹下运行 ```shell npm run serve ``` 启动前端测试(默认运行在8080端口) 在back文件夹下运行 ```shell node main.js ``` 启动服务器(默认运行在8000端口) 然后打开浏览器访问http://localhost:8080/即可测试项目 项目组成 ### 前端 前端为单页面应用,所有交互均以模态框(或类模态框)进行,结构如下: ``` |-- front/src |-- main.js |-- App.vue |-- components | |-- Hot.vue | |-- Display.vue | |-- Result.vue |-- assets | ... ``` 其中App.vue负责整体页面(主页),Hot.vue负责热搜模态框,Display.vue负责展示模态框,Result.vue负责搜索结果展示模态框(包含分页)。 ### 后端 后端由于是第一次写nodejs后端,因此封装或者模块化都比较简陋,以后有兴趣的话可以将后端用Python或Java重写,结构如下: ``` |-- back |-- main.js |-- crawler | |-- common.js | |-- crawler.js | |-- websites | |-- wangyi.js | |-- xinlang.js | |-- xinhua.js | |-- renmin.js | ... ``` 主体部分还是在爬虫上,由于针对不同网站的爬虫会有所不同,因此我将匹配的关键词放在了websites文件夹中的每个js文件中,种子页面处理放在crawler.js中,common.js用来放一些常用模块的二次封装(或者测试的时候可以拿来用,比如我的连接数据库操作是放在main中的,其他地方没有数据库,因此测试某个网站是否成功导入数据库中也可以在common中加入数据库连接操作并exports出来)。 其余定时器,search,热搜,展示的数据均在main.js中生成(这里是由于nodejs不太熟悉不会很好的封装,因此统一整合到了main中)
使用 Spring Boot 3 开发一个前后端分离的生产级系统需要以下步骤: 第一步:环境准备 1. 安装 Java 开发工具包(JDK) 2. 安装集成开发环境(IDE),如Eclipse或IntelliJ IDEA 3. 安装Maven构建工具 4. 安装数据库(如MySQL)和相关工具(如MySQL Workbench) 第二步:创建后端项目 1. 使用IDE创建一个新的Spring Boot项目 2. 配置项目的基本信息,如项目名称、包名等 3. 添加必要的依赖,如Spring Boot Starter Web、Spring Data JPA等 4. 定义实体类、控制器、服务等后端代码 第三步:创建前端项目 1. 使用前端开发工具,如Vue.js或React.js,创建一个新的前端项目 2. 配置项目的基本信息,如项目名称、包名等 3. 定义前端路由、页面、组件等前端代码 第步:前后端集成 1. 在后端项目中配置跨域访问,允许前端项目访问后端接口 2. 在前端项目中调用后端接口,实现数据交互 第五步:开发和测试 1. 根据需求逐步开发后端和前端功能模块 2. 使用测试框架,如JUnit和Selenium,对系统进行单元测试和端到端测试 第六步:部署和上线 1. 打包后端项目为可执行的JAR文件 2. 部署JAR文件到生产环境的服务器上 3. 配置服务器的环境变量、数据库连接等 4. 启动服务器,验证系统是否正常运行 通过以上步骤,我们可以完成一个使用Spring Boot 3开发的前后端分离的生产级系统。这种架构可以提高开发效率、降低系统耦合性,并且适合大型项目的开发和部署。同时,我们还可以根据实际需求,进一步优化系统性能、可维护性和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值