• 开发前后端交互规范与请求封装


    一,背景说明

    假设要实现系统登录的功能,用户输入用户名和密码,点击发送,后端接收到请求,拿到输入的数据,并对输入的数据进行验证,如果账号密码都正确返回正确标识,错误则返回失败标识。这里涉及到三个需要考虑的地方,数据对象的封装,前后端交互采用哪种方式,前端请求如何发送。

    二,Axios请求封装

    在这里插入图片描述
    Axios是一个基于promise的网络请求库,作用于node.js和浏览器中,在Vue项目中使用前需先安装

    npm install axios --save --在生产环境中安装
    
    • 1
    import axios from 'axios'
    // import ElementUI from 'element-ui'
    const caseRequest = axios.create({
      baseURL: 'http://localhost:7080/tick_tack',
      headers: { 'Content-Type': 'application/json' }
      // timeout: 5000
    })
    export default caseRequest
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三,数据对象封装

    1,前端封装

    前端数据假设只有账号,密码两个字段,业务比较复杂可以再新加验证码这些。数据对象采用模块化的方式,方便后期管理和维护,在Vue项目的views文件夹下新建一个sign-in文件夹,用来实现用户的登录功能。也可以建在component下,只是component中一般存放组件等功能相对通用的模块。
    在这里插入图片描述
    login-user-system.class.js:存放字段信息,可引入Vue文件中使用

    class UserInSystemClass {
      constructor (userAccount, userName) {
        this.userAccount = userAccount
        this.password = password
      }
    }
    export default UserInSystemClass
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    login-to-system.service.js:caseRequest是对axios请求的封装

    import caseRequest from '@/request/request'
    
    const loginToSystem = {
      queryPerson (data) {
        // 因为params是添加到url的请求字符串中的,用于get请求。
        // data是添加到请求体(body)中的, 用于post请求。
        return caseRequest.request({
          url: '/loginUser',
          method: 'POST',
          data
        })
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    login-to-system.vue:

    <template>
     <div>
      <el-form ref="form" :model="loginUserSystemForm" label-width="80px">
       <el-form-item label="用户名">
        <el-input v-model="loginUserSystemForm.userAccount"></el-input>
       </el-form-item>
       <el-form-item label="密码">
        <el-input v-model="loginUserSystemForm.password"></el-input>
       </el-form-item>
       <el-form-item>
        <el-button type="primary" @click="login()">登录</el-button>
       </el-form-item>
      </el-form>
     </div>
    </template>
    
    <script>
    import loginToSystemService from '@/views/sign-in/login-to-system.service'
    import LoginUserSystemClass from '@/views/sign-in/class/login-user-system.class'
    const resource = loginToSystemService.resource
    export default {
      name: 'LoginToSystem',
      data () {
       return {
          loginUserSystemForm: new LoginUserSystemClass()
      },
      methods:{
       async login () {
            const result = await resource.loginToSystem.queryPerson(this.loginUserSystemForm)
            if (result.code === '200') {
              this.$message.success('登录成功')
              this.$router.push('/front/home')//路由跳转
            } else {
              this.$message.error('Failure')
            }
        }
      }
    </script>
    
    • 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

    2,后端封装

    后端数据对象封装,简单点假设只有两个字段,账号和密码

    public class LoginUser{
        private String userAccount;
        private String password;
    }
    
    • 1
    • 2
    • 3
    • 4

    Controller方法

        @PostMapping("/loginUser")
        //@RequestBody:将前端传过来的json转成后端的对象
        public Result loginSystem(@RequestBody LoginUser user) {
            if (StringUtils.isBlank(user.getUserAccount())||StringUtils.isBlank(user.getPassword())){
                return Result.error(Constants.CODE_400, "参数错误");
            }
            //校验用户输入的信息,此处可以自行发挥即可
            LoginUser loginUser = loginService.loginSystem(user);
            return Result.success(loginUser);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Result接口统一返回包装类

    @Data
    @NoArgsConstructor //无参构造
    @AllArgsConstructor //有参构造
    public class Result {
        private String code;//状态码
        private String msg;//返回信息
        private Object data;//返回数据参数
        //无参请求成功
        public static Result success(){
            return new Result(Constants.CODE_200,"",null);
        }
        //有参数返回请求成功
        public static Result success(Object object){
            return new Result(Constants.CODE_200,"",object);
        }
        //返回错误信息
        public static Result error(String code,String msg){
            return new Result(code, msg, null);
        }
        //返回简单的错误信息
        public static Result error(){
            return new Result(Constants.CODE_500, "系统错误", null);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    接口中定义变量

    • 接口中定义的变量都是静态变量
    • 接口中定义的变量都默认加上public static final关键字,可以直接使用,无需初始化
    • 接口中的变量可起到在多个类中共享变量的效果
    public interface Constants {
        String CODE_200 = "200";//成功
        String CODE_401 = "401";//权限不足
        String CODE_400 = "400";//参数错误
        String CODE_500 = "500";//系统错误
        String CODE_600 = "600";//其它业务异常
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    四,总结

    在前端和后端定义相同类型的对象,且约定好状态码的意义,例如200代表成功,500是系统错误,401是权限不足。前端通过Axios发送请求,后端接收到请求后进行业务处理,返回相应的状态码,说明信息,以及响应的参数,前端拿到后端返回的数据后,根据不同的状态码进行对应的操作,例如赋值,或是报错信息的提示。
    在开发过程中应该运用模块化的思想,高内聚,低耦合,这在后期的维护和扩展中是相当有益的。

    五,Axios拦截器

    对Axios进行封装,发送请求时调用封装后的类还有一个好处就是可以进行拦截操作,只需在封装处做拦截,其它调用的地方不用再做重复的操作。

    import axios from 'axios'
    import ElementUI from 'element-ui'
    const caseRequest = axios.create({
      baseURL: 'http://localhost:7080/test_demo',
      headers: { 'Content-Type': 'application/json' }
      // timeout: 5000
    })
    
    // request拦截器
    // 可以在请求发送前对请求做一些处理
    // 比如统一加token,对请求参数统一加密
    caseRequest.interceptors.request.use(config => {
      config.headers['Content-Type'] = 'application/json;charset=utf-8'
      //此处是将token信息放入了浏览器的localStorage中,通过getItem()方法取出
      let user = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : null
      if (user) {
        config.headers['token'] = user.token// 设置请求头
      }
      return config
    }, error => {
      return Promise.reject(error)
    })
    
    // response响应拦截器
    caseRequest.interceptors.response.use(
      response => {
        let res = response.data
        // 如果返回的是文件
        if (response.config.responseType === 'blob') {
          return res
        }
        // 兼容服务端返回的字符串数据
        if (typeof res === 'string') {
          // JSON.parse是从一个字符串中解析出json
          // JSON.stringify是从一个对象中解析出字符串
          res = res ? JSON.parse(res) : res
        }
        // 当权限验证不通过的时候给出提示
         if (res.code === '401') {
           ElementUI.Message({
             message: '权限校验未通过',
             type: 'error'
           })
         }
        return res
      }
    )
    
    export default caseRequest
    
    
    • 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

    七,Restful请求

    1,Restful的由来

    • 在Restful之前的操作:
    URL说明
    http://localhost:7080/test_demo/user/query/1GET 根据用户id查询用户数据
    http://localhost:7080/test_demo/savePOST 新增用户
    http://localhost:7080/test_demo/user/update POST修改用户信息
    http://localhost:7080/test_demo/user/delete/1GET/POST 删除用户信息
    • RESTful用法:
    URL说明
    http://localhost:7080/test_demo/user/1GET 根据用户id查询用户数据
    http://localhost:7080/test_demo/userPOST 新增用户
    http://localhost:7080/test_demo/userPUT 修改用户信息
    http://localhost:7080/test_demo/userDELETE 删除用户信息

    之前的操作其实也没什么问题,query表示查询,以Get的方式,save表示保存,以POST方式发送,但是有的大神觉得这样不太好,太繁琐了,比如Get请求,再加个id,那就是要做查询操作,没必要路径里面还要加个query,保存操作也是,POST方式就是要保存数据,没必要再加个save,在这种情况下,Restful规范诞生了。这里也说了,Restful是一种规范,建议大家能够遵守,如果不遵守那也没事,只是会产生很多多余的URL,不方便接口的维护和文档的维护。

    2,Restful方法说明

    http方法资源操作幂等性安全性
    GETSELECT
    POSTINSERT
    PUTUPDATE
    DELETEDELETE

    幂等性:对同一接口的多次访问,得到的资源状态是相同的
    安全性:对该REST接口访问,不会使服务器端资源的状态发生改变。

    3,Restful请求实例

    前端Axios请求

    import caseRequest from '@/request/request'
    //将userAccount拼接在URL后端,类似于http://localhost:7080/test_demo/user?userAccount=2
    queryParam (userAccount) {
        return caseRequest.request({
          url: '/user/param?userAccount=' + userAccount,
          method: 'GET'
        })
      },
      //将userAccount拼接在URL上,类似于http://localhost:7080/test_demo/user/2
      query (userAccount) {
        return caseRequest.request({
          url: '/user/' + userAccount,
          method: 'GET'
        })
      },
    save(data) {
        return caseRequest.request({
          url: '/user',
          method: 'POST',
          data
        })
      },
     update (data) {
        return caseRequest.request({
          url: '/user',
          method: 'PUT',
          data
        })
      },
    delete (userId) {
        return caseRequest.request({
          url: '/user/' + userId,
          method: 'DELETE'
        })
      }
    
    • 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

    后端Controller代码

    @RestController
    @RequestMapping("/user")
    public class UserController {
        @GetMapping("/param")
        public Result queryParam(@RequestParam String userAccount){
           User user= userService.query(userAccount);
           return Result.success(user);
        }
        @GetMapping("/{userAccount}")
        public Result query(@PathVariable String userAccount){
           User user= userService.query(userAccount);
           return Result.success(user);
        }
        @DeleteMapping("/{userId}")
        //@PathVariable:取路径中传过来的值
        public boolean delete(@PathVariable Integer userId) {
            return userService.delete(userId);
        }
    
        @PostMapping
        //通过@requestBody可以将请求体中的JSON字符串绑定到相应的bean上
        public boolean save(@RequestBody User user) {
            return userService.save(user);
        }
    
        @PutMapping
        public boolean update(@RequestBody User user) {
            return userService.save(user);
        }
    }
    
    • 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

    说明:
    1.@PathVariable 注解:参数拼接在url上,value的方式入参

    http://localhost:7080/test_demo/user/2
    
    • 1

    2.@RequestParam 注解:参数拼接在url上(只能用Params方式,不能用Body),以key=value的方式入参

    http://localhost:7080/test_demo/user?userAccount=2
    
    • 1
  • 相关阅读:
    CoreData 同步 iCloud 数据导致 App 启动超时被系统 watchdog 终止的原因及解决
    C Primer Plus(6) 中文版 第6章 C控制语句:循环 6.12 使用函数返回值的循环示例
    数据库——JDBC基本连接步骤
    第二十四篇 ref 访问子组件
    canal 与 dbz ddl与dml数据结构
    适配器模式:转换接口,无缝对接不同系统
    yum安装jdk环境
    读书感悟【Vue.js设计与实现】第1章 权衡的艺术 【Vue进阶系列】
    C编程入门到精通 专辑目录
    java 计算对象在内存中占用空间 大小
  • 原文地址:https://blog.csdn.net/weixin_44924882/article/details/127927078