阿里Ice实现与Spring Boot间的前后端通信实例

实例概述

本文提供一个使用阿里Ice前端框架(封装React)与服务Spring Boot项目进行相互通信的项目实例。具体项目环境可参见:阿里ICE前端工程创建过程。该实例中不对Spring Boot项目的创建和使用进行介绍,仅提供相应的Controller方法作为与React前端工程通信的API。该实例具有的相关组件组成的页面如下:

  • UserCreateForm:提供表单信息进行Post请求创建用户。
  • UserListTable:用于获取数据库中的user列表并展示。
  • UserDetailForm:用户渲染指定用户的详细信息并提供修改的功能。

以上三个组件的关系为并列(没有进行组合),但其中存在一些页面跳转的关系,后续会进行说明。

在该实例中重点进行关注和介绍的问题点为:

  • React与Spring Boot前后端的Get/Post请求交互方式如何实现?
  • ice中页面之间的跳转如何实现?并如何携带和获取参数?
  • React中props和state的使用。
  • React如何使用map渲染列表?
  • 服务端跨域请求的的设置。

项目代码:https://github.com/Yitian-Zhang/my-ice-start。下面来看具体的实例实现过程。

服务端Controller和跨域问题设置

服务端方面已经使用Spring Boot+MyBatis+MySQL搭建好项目环境,下面创建ReactUserController类提供如下的接口方法,并通过测试各接口功能正常。

/**
 * React(my-ice-start)项目接口Controller
 * @author yitian
 */
@RestController
@RequestMapping("/react-user")
public class ReactUserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/detail")
    public CommonResult getUserDetail(Long id) {
        if (id == null) {
            return CommonResult.error("用户ID不能为空");
        }
        return new CommonResult(true, 200, userService.getUserById(id));
    }

    @RequestMapping("/create")
    public CommonResult createUser(@RequestBody User user) {
        if (user == null) {
            return CommonResult.error("添加用户为空");
        }
        System.out.println(user);

        int result = userService.insertUser(user);
        boolean success = result == 1;
        String msgInfo = success ? "添加成功" : "添加失败";
        return new CommonResult(success, msgInfo, user);
    }


    @RequestMapping("/list")
    public CommonResult userList() {
        List<User> userList = userService.getUserList();
        return new CommonResult(true, "获取成功", userList);
    }


    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public CommonResult updateUser(@RequestBody User user) {
        if (user == null || user.getId() == null) {
            return CommonResult.error("待更新用户信息为空");
        }
        System.out.println(user);

        int result = userService.updateUserById(user);
        boolean success = result == 1;
        String msg = success ? "更新成功" : "更新失败";
        return new CommonResult(success, msg, userService.getUserById(user.getId()));
    }

    @RequestMapping("/delete")
    public CommonResult deleteUser(Long id) {
        if (id == null) {
            return CommonResult.error("UserId不能为空");
        }
        int result = userService.deleteUser(id);
        boolean success = result == 1;
        String msg = success ? "删除成功" : "删除失败";
        return new CommonResult(success, msg, userService.getUserList());
    }
}

此外由于Spring Boot项目为localhost:8080,而ice启动项目地址为localhost:3333,所以在前后端项目进行通信时会存在跨域问题,下面在Spring Boot项目中加入如下的Bean,来配置请求的response返回头,使其允许所有的request:

    /**
     * 解决React跨域请求时的异常
     */
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("*");
            }
        };
    }

至此该实例中的Spring Boot服务端项目的方法已经开发完成,后面重点关注ICE项目中如何进行前后端项目的交互。

创建并添加用户

首先进行createUser的页面开发,这里定义的组件如下:

import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
import PropTypes from 'prop-types';
import axios from 'axios';

@withRouter
class UserCreateForm extends Component {
  // 页面跳转静态属性
  static propTypes = {
    match: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      username: '',
      sex: 'MALE',
      note: ''
    };
  }

  // 表单变更处理函数
  handleChange = (event) => {
    const {name, value} = event.target;
    this.setState({
      [name] : value,
    })
  };

  // 创建用户信息
  handleSubmit = (event) => {
    this.submitUser();
  };

  // post请求提交更新后的user信息
  submitUser() {
    const {username, sex, note} = this.state;
    const { history } = this.props;
    console.log(username + ", " + sex + ", " + note);

    // 直接使用post请求
    axios.post('http://localhost:8080/react-user/create', {
      // id: id,
      userName: username,
      sex: sex, // 这里可以直接根据SexEnum的枚举name来进行参数传递,不需要使用枚举key
      note: note
    })
      .then(function (response) {
        console.log(response);
        alert(response.data.msgInfo);

        // 添加完成后跳转list页面
        history.push({
          pathname: '/user/list',
        });
      })
      .catch(function (error) {
        console.log(error);
      });
  };

  render() {
    const {username, sex, note} = this.state;
    return (
      <React.Fragment>
        <h1>User Detail Form</h1>
        <form>
          <table>
            <tr>
              <td>Username:</td>
              <td><input
                type="text"
                id="username"
                name="username"
                value={username}
                onChange={this.handleChange}/></td>
            </tr>
            <tr>
              <td>sex:</td>
              <td><select
                name="sex"
                value={sex}
                onChange={this.handleChange}>
                <option value="MALE">MALE</option>
                <option value="FEMALE">FEMALE</option>
              </select></td>
            </tr>
            <tr>
              <td>note:</td>
              <td><input
                type="text"
                id="note"
                name="note"
                value={note}
                onChange={this.handleChange}/></td>
            </tr>
            <tr>
              <td><input
                type="button"
                value="CreateUser"
                onClick={this.handleSubmit}/></td>
            </tr>
          </table>
        </form>
      </React.Fragment>
    )
  }
}
export default UserCreateForm;

该部分代码中有如下几个方面需要重点关注:

1. 在submitUser方法中使用axios进行post请求。

ice允许使用自己封装的request进行请求的发送(其实就是封装的axios),也允许使用ajax、jquery,axios等方式发送请求。这里使用了axios的方式进行post的请求的发送。格式如下:

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

或者使用axios(config {...}) 的格式发送post请求:

    // Send a POST request
    axios({
      method: 'post',
      url: '/user/12345',
      data: { // 这里data中的参数为requestBody参数,服务端需要使用@RequestBody注解进行获取
        firstName: 'Fred',
        lastName: 'Flintstone'
      }
    }).then(function (response) {
      console.log(response);
    }).catch(function (error) {
      console.log(error);
    });

2.  用户创建成功后如何进行的页面跳转(跳转页面代码的实现在后面)。

对于页面的跳转过程的实现,这里使用的为阿里Ice中实现组件和页面间跳转并进行参数传递中提到的,withRouter方式进行实现。

页面实现完成后,显示如下,在后续跳转页面完成后,进行完整的集成测试。

用户列表和管理

用户列表渲染

上面在创建用户完成后,页面会跳转到显示数据库中所有用户的列表页面中,该列表页面使用如下的组件进行实现:

import React, {Component} from 'react';
import axios from 'axios';
import {List} from "@alifd/next";
import UserDetailForm from "../UserDetailForm";
import {withRouter} from "react-router-dom";
import PropTypes from 'prop-types';
import './index.css';

@withRouter
class UserListTable extends Component {
  // 页面跳转静态配置
  static propTypes = {
    match: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      userList: [],
    };
  }

  componentDidMount() {
    this.getUserList();
  }

  // 获取用户列表数据
  async getUserList() {
    try {
      const response = await axios.get('http://localhost:8080/react-user/list');
      console.log(response);

      this.setState({
        userList: response.data.data,
      })
    } catch (error) {
      console.error(error);
    }
  }

  // 详情和更新页面
  handleClickDetail = (id) => {
    console.log("ListTable id: " + id);

    // 页面跳转
    const { history } = this.props;
    history.push({
      pathname: '/user/detail',
      state: { id },
    });
  };

  // 删除数据
  handleClickDelete = (id) => {
    this.deleteUser(id);
  };

  // 删除用户
  async deleteUser(id) {
    try {
      const response = await axios.get('http://localhost:8080/react-user/delete?id=' + id);
      console.log(response);
      alert(response.data.msgInfo);

      this.setState({
        userList: response.data.data,
      });

    } catch (e) {
      console.error(e);
    }
  }

  render() {
    const {userList} = this.state;

    return (
      <div>
        <h1>User List</h1>
        <table>
          <thead>
            <tr>
              <td>Id</td>
              <td>UserName</td>
              <td>Sex</td>
              <td>Note</td>
              <td>Operate</td>
            </tr>
          </thead>
          <tbody>
          {
            userList.map((row, index) => {
              const id = row.id;
              return (
                <tr key={index}>
                  <td>{row.id}</td>
                  <td>{row.userName}</td>
                  <td>{row.sex}</td>
                  <td>{row.note}</td>
                  <td>
                    <button
                      className="listButton"
                      onClick={() => this.handleClickDetail(id)}>Detail</button>
                    <button
                      className="listButton"
                      onClick={() => this.handleClickDelete(id)}>Delete</button>
                  </td>
                </tr>
              )
            })
          }
          </tbody>
        </table>
      </div>
    );
  }
}
export default UserListTable;

对于上述的代码,需要关注的重点如下:

1. 如何使用axios的GET请求来操作数据?

在使用axios进行GET请求时,与POST请求类似,有如下两种方式。第一种为直接使用封装的axios.get进行请求,格式如下:

// Make a request for a user with a given ID
axios.get('/user?ID=12345')
  .then(function (response) {
    // handle success
    console.log(response);

    // update state or do something
    this.setState({
      // ...
    })
  })
  .catch(function (error) {
    // handle error
    console.log(error);
  })
  .then(function () {
    // always executed
  });

// Optionally the request above could also be done as
axios.get('/user', {
    params: { // 这里的参数设置为URL参数(根据URL携带参数)
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })
  .then(function () {
    // always executed
  });  

// Want to use async/await? Add the `async` keyword to your outer function/method.
async function getUser() {
  try {
    const response = await axios.get('/user?ID=12345');
    console.log(response);
  } catch (error) {
    console.error(error);
  }
}

第二种为使用axios(config {...}) 的方式发送GET请求:

axios({
  method: 'get',
  url: 'http://bit.ly/2mTM3nY',
  params: {
    id: id,
  }
})
  .then(function (response) {
    console.log(response);
  });

2. 如何在页面跳转时传递需要的参数?

在页面跳转时传递参数,需要在history.push方法中进行如下设置:

    // 页面跳转
    const { history } = this.props;
    history.push({
      pathname: '/user/detail',
      state: { id },
    });

之后在用户详情页面就可以通过如下的方式获取该传递的id参数:

  componentDidMount() {
    let id = this.props.location.state.id;
    console.log("DetailForm id: " + id);
  }

 3. 如何根据userList渲染出用户的整个列表页面?

在对userList进行循环渲染整个user列表时,需要首先在constructor中对userList整个state进行初始化为数组:

  constructor(props) {
    super(props);
    this.state = {
      userList: [],
    };
  }

然后在render方法中使用map方法对数组进行遍历并渲染列表中的数据: 

          <tbody>
          {
            userList.map((row, index) => {
              const id = row.id;
              return (
                <tr key={index}>
                  <td>{row.id}</td>
                  <td>{row.userName}</td>
                  <td>{row.sex}</td>
                  <td>{row.note}</td>
                  <td>
                    <button
                      className="listButton"
                      onClick={() => this.handleClickDetail(id)}>Detail</button>
                    <button
                      className="listButton"
                      onClick={() => this.handleClickDelete(id)}>Delete</button>
                  </td>
                </tr>
              )
            })
          }
          </tbody>

完成后该页面显示如下 :

对于每个用户,其中都对应了detail和delete按钮。其中detail会跳转到UserDetail页面来显示该用户的具体信息,delete按钮则是对该用户信息进行删除。下面先看一下简单的delete方法的实现,detail的实现在下一节中进行说明。 

用户删除实现

上述列表中delete用户时,同样是使用axios.get请求来删除数据库中的用户数据,然后获取新的userList返回值并使用setState方法更新state,使该页面重新渲染。具体的代码已经在上面组件代码中给出,实现比较简单。

用户详情信息以及修改

用户详情页为userList页面中对指定user点击detail按钮而跳转得到的页面,该页面组件的实现如下:

import React, {Component} from 'react';
import axios from 'axios';
import {request} from "../../../.ice";
import {withRouter} from 'react-router-dom';
import PropTypes from 'prop-types';

@withRouter
class UserDetailForm extends Component {
  static propTypes = {
    match: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      id: '',
      username: '',
      sex: '',
      note: ''
    };
  }

  componentDidMount() {
    // 使用axios进行get和post请求
    let id = this.props.location.state.id;
    console.log("DetailForm id: " + id);
    this.getUserByAxios(id);
  }

  // 使用axios来进行get请求
  // 使用前需要安装axios:npm install axios --save,并进行import
  async getUserByAxios(id) {
    try {
      const response = await axios.get("http://localhost:8080/react-user/detail?id=" + id);
      console.log(response);
      const user = response.data.data;

      this.setState({
        id: user.id,
        username: user.userName,
        sex: user.sex,
        note: user.note
      })
    } catch (error) {
      console.error(error);
    }
  }

  // 表单变更处理函数
  handleChange = (event) => {
    const {name, value} = event.target;
    this.setState({
      [name] : value,
    })
  };

  // 更新用户信息函数
  handleSubmit = (event) => {
    this.submitUser();
  };

  // post请求提交更新后的user信息
  submitUser() {
    const {id, username, sex, note} = this.state;
    console.log(id + ", " + username + ", " + sex + ", " + note);

    axios.post('http://localhost:8080/react-user/update', {
      id: id,
      userName: username,
      sex: sex,
      note: note
    })
      .then(function (response) {
        console.log(response);
        alert(response.data.msgInfo);
        // 更新列表state
        const user = response.data.data;
        this.setState({
          id: user.id,
          username: user.userName,
          sex: sex,
          note: note
        });

      })
      .catch(function (error) {
        console.log(error);
      });
  };

  render() {
    const {id, username, sex, note} = this.state;
    return (
      <React.Fragment>
        <h1>User Detail Form</h1>
        <form>
          <table>
            <tr>
              <td>Id:</td>
              <td><input
                type="text"
                id="id"
                name="id"
                value={id}
                disabled="true"
                onChange={this.handleChange}/></td>
            </tr>
            <tr>
              <td>Username:</td>
              <td><input
                type="text"
                id="username"
                name="username"
                value={username}
                onChange={this.handleChange}/></td>
            </tr>
            <tr>
              <td>sex:</td>
              <td><select
                name="sex"
                value={sex}
                onChange={this.handleChange}>
                <option value="MALE">MALE</option>
                <option value="FEMALE">FEMALE</option>
              </select></td>
            </tr>
            <tr>
              <td>note:</td>
              <td><input
                type="text"
                id="note"
                name="note"
                value={note}
                onChange={this.handleChange}/></td>
            </tr>
            <tr>
              <td><input
                type="button"
                value="UpdateUser"
                onClick={this.handleSubmit}/></td>
            </tr>
          </table>
        </form>
      </React.Fragment>
    )
  }
}
export default UserDetailForm;

该部分代码中同时用到了axios.get请求来根据页面跳转传入的id参数,获取该用户对应的详细信息,同时使用axios.post请求在处理对用户信息的更新操作。以此实现了用户详细信息页面的展示,以及用户详情信息的更新功能。 

该页面的显示为:

Ice项目路由配置

上述代码中在进行页面跳转时,使用到了如下的请求路径:

/user/list
/user/detail

在ice项目中,需要在routes.j[t]s路由配置文件中来进行声明式路由的配置:

const routes = [
  {
    path: '/user/list',
    component: UserListTable,
  },
  {
    path: '/user/detail',
    component: UserDetailForm,
  },
  ...
];

export default routes;

以上即为该实例的全部实现。END。

发布了322 篇原创文章 · 获赞 64 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yitian_z/article/details/104639201