阿里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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章