用戶登錄前後端分離開發實戰案例:React,npm,webpack,ES6, Kotlin, Spring Boot, Gradle, Freemarker, Material UIKotlin 開發

用戶登錄前後端分離開發實戰案例:React,npm,webpack,ES6, Kotlin, Spring Boot, Gradle, Freemarker, Material UI

本章通過一個簡單用戶登錄模塊全棧開發案例,從前端 React工程的創建、開發,到後端 Spring Boot + Kotlin + Gradle工程的創建,使用 Spring Data JPA 來操作 MySQL數據庫, 使用Freemarker視圖引擎,從前往後完整的講解整個開發過程。

前端 React 工程開發

環境準備

本節實例工程的運行環境和技術棧相關清單如下:

運行環境準備:Node

開發工具 IDE:WebStorm

瀏覽器:Chrome

框架和組件庫:react, babel,jquery, material-ui

構建工具:webpack

我們用 webpack + es6 來結合 react 開發前端應用。本章中,我們手動使用npm來安裝各種插件,來從頭到尾自己搭建環境。當然,在實際的項目開發中,已經有大神們開發好了腳手架,例如 create-react-app(https://github.com/facebook/create-react-app),我們直接使用腳手架就可以了。爲了保持技術細節的原汁原味,我們本章先來帶領大家一步一步地來手工搭建 webpack + es6 的 React前端開發工程。

使用npm搭建React的webpack環境

本節我們來介紹如何通過 npm一步一步創建 React前端工程。我們通過Webpack打包構建React工程。

安裝配置Webpack環境

我們主要來安裝react react-dom babel等npm包,設置webpack.config.js,打包輸出bundle.js。

安裝Webpack

1.創建項目文件夾

mkdir simple-login

新建 simple-login 文件夾,在此文件夾內進行webpack本地安裝。

2.npm初始化

 $ npm init -y
Wrote to /Users/jack/spring-boot-book/chapter03/front-end/simple-login/package.json:

{
  "name": "simple-login",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

初始化,目錄下生成一個 package.json 文件,內容如上。

3.安裝 webpack

WebPack是什麼:https://github.com/webpack/webpack-cli

npm install --save-dev webpack 

安裝成功後 simple-login 目錄中會出現node_modules 目錄 。

注意:不推薦使用全局安裝npm install --global webpack

我們可以看到 .bin 目錄下面的webpack腳本:

image

打開腳本看到源碼如下:

#!/usr/bin/env node

process.exitCode = 0;

/**
 * @param {string} command process to run
 * @param {string[]} args commandline arguments
 * @returns {Promise<void>} promise
 */
const runCommand = (command, args) => {
    const cp = require("child_process");
    return new Promise((resolve, reject) => {
        const executedCommand = cp.spawn(command, args, {
            stdio: "inherit",
            shell: true
        });

        executedCommand.on("error", error => {
            reject(error);
        });

        executedCommand.on("exit", code => {
            if (code === 0) {
                resolve();
            } else {
                reject();
            }
        });
    });
};
...

通過腳本中的

#!/usr/bin/env node

我們即可知道,這是一個使用 node運行環境執行的一個 js。此時,我們 package.json 文件中在devDependencies 下面多了一行 webpack 包的依賴:

{
  "name": "simple-login",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.25.1"
  }
}
安裝依賴包

使用 npm install 命令繼續安裝 react react-dom babel 等依賴包:

npm install --save react react-dom
npm install --save-dev babel-core babel-loader babel-preset-react babel-preset-es2015

本地安裝的webpack命令爲: ./node_modules/.bin/webpack

我們可以通過打開 package.json ,在 "scripts": {} 中加入"start": "webpack" ,用 npm start 命令代替 webpack命令。

這個時候,我們的 package.json 文件內容變成了

{
  "name": "simple-login",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^8.0.4",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "webpack": "^4.25.1"
  },
  "dependencies": {
    "react": "^16.6.1",
    "react-dom": "^16.6.1"
  }
}

這裏的 babel-loader:8.0.4跟babel-core:6.26.3 版本不兼容,我們改成 "babel-loader": "^7.1.5" 。

可以看到,babel、webpack依賴被放到了 devDependencies 中,react 依賴被放到了 dependencies中。那 package.json 文件裏面的 devDependencies 和 dependencies 對象有什麼區別呢?

devDependencies和dependencies的區別

我們在使用npm install 安裝模塊或插件的時候,有兩種命令把他們寫入到 package.json 文件裏面去,比如:

--save-dev
--save

在 package.json 文件裏面提現出來的區別就是,使用 --save-dev 安裝的 插件,被寫入到 devDependencies 對象裏面去,而使用 --save 安裝的插件,則被寫入到 dependencies 對象裏面去。

devDependencies 裏面的插件只用於開發環境,不用於生產環境。而 dependencies 是需要發佈到生產環境的。

配置webpack

創建項目文件,最終結構如下:

image

文件說明如下:

  • app/index.js 入口文件
  • dist 用於盛放webpack打包輸出的bundle.js
  • webpack.config.js 用於配置webpack環境。

編寫webpack.config.js配置文件

const path = require('path');
module.exports = {
    entry: "./app/index.js",  //入口文件
    output: {
        path: path.join(__dirname, "/dist/"),    // 所有輸出文件的目標路徑,絕對路徑!
        filename: "bundle.js"
    },
    module: {
        rules: [
            {
                test: /\.js$/,                //babel-loader將其他文件解析爲js文件
                exclude: /node_modules/,
                loader: "babel-loader",
                options: {
                    presets: ["es2015", "react"]  //babel-loader需要解析的文件
                }
            }
        ]
    }
};

webpack 開箱即用,可以無需使用任何配置文件。然而,webpack 會假定項目的入口起點爲 src/index,然後會在 dist/main.js 輸出結果,並且在生產環境開啓壓縮和優化。

通常,你的項目還需要繼續擴展此能力,爲此你可以在項目根目錄下創建一個 webpack.config.js 文件,webpack 會自動使用它。

更多關於 webpack的配置說明參考:https://webpack.github.io/

package-lock.json的作用

我們有看到上面的目錄中,多了一個package-lock.json文件。這個文件是幹嘛用的呢?其實用一句話來概括很簡單,就是鎖定安裝時的包的版本號,並且需要上傳到git,以保證其他人在npm install時大家的依賴能保證一致。

根據官方文檔,這個package-lock.json 是在 npm install時候生成一份文件,用以記錄當前狀態下實際安裝的各個npm package的具體來源和版本號。它有什麼用呢?因爲npm是一個用於管理package之間依賴關係的管理器,它允許開發者在pacakge.json中間標出自己項目對npm各庫包的依賴。你可以選擇以如下方式來標明自己所需要庫包的版本。

這裏舉個例子:

"dependencies": {
 "@types/node": "^8.0.33",
}

這裏面的向上標號^是定義了向後兼容依賴,指如果 types/node的版本是超過8.0.33,並在大版本號(8)上相同,就允許下載最新版本的 types/node庫包,例如實際上可能運行npm install時候下載的具體版本是8.0.35。

編寫React組件 App.js

代碼如下:

var React = require('react');

export default class App extends React.Component {

    render() { // Every react component has a render method.

        let now = new Date();
        let datetimeString =`${now.toDateString()}  ${now.toTimeString()}`;

        return ( // Every render method returns jsx. Jsx looks like HTML, but it's actually javascript and functions a lot like xml, with self closing tags requiring the `/` within the tag in order to work propperly
            <h1>
                Hello World, Now Time is {datetimeString}
            </h1>
        );
    }
}

其中,var React = require('react') 是引入 react 包。我們的App類繼承自React.Component。每個 React Component 都必須要有一個 render() 函數,該函數返回一個 JSX 對象。

在render() 函數中,我們實現了一個簡單的 App 組件:給世界問好,並展示當前的時間。

編寫index.js和index.html文件

我們在 index.js 中引入我們上面的 App組件,代碼如下:

import App from "./components/App";
var ReactDOM = require('react-dom');

ReactDOM.render(
    <App/>,
    document.getElementById('App')
);

其中,元素 id = 'App' 是我們下面在index.html 中指定的 div 。

在 index.html中引用 webpack 打包生成的bundle.js, 代碼如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Hello React!</title>
</head>
<body>
<div id="App"></div>
<script src="dist/bundle.js"></script>
<!--引用webpack打包輸出的bundle.js-->
</body>
</html>
執行webpack命令

我們使用 npm start 來執行 webpack打包動作,我們看到在dist目錄一下生成了一個bundle.js文件。然後,直接打開index.html 看到頁面效果:

Hello World, Now Time is Sat Nov 10 2018 12:49:10 GMT+0800 (中國標準時間)

使用 React前端組件庫 Material-UI

簡介

React Material-UI (https://github.com/mui-org/material-ui)是一組實現了谷歌 Material Design 設計語言的 React 組件。它在 GitHub 上的 Star 數> 4w,fork>8k,可能是最受歡迎的 React 組件庫了,目前最新版本是 v3.4.0。

安裝

下面我們來一步一步安裝Material-UI——這個世界上最受歡迎的React UI框架。Material-UI 可作爲 npm 包使用。

安裝核心依賴

npm install @material-ui/core

等待依賴安裝完畢,我們可以看到,此時我們的package.json文件內容新增了 "@material-ui/core": "^3.4.0" 的依賴:

"dependencies": {
  "@material-ui/core": "^3.4.0",
  "react": "^16.6.1",
  "react-dom": "^16.6.1"
}

爲了使用預構建的SVG Material icons,例如在組件演示中找到的那些, 須先安裝 @material-ui/icons包:

npm install @material-ui/icons

詳細使用參考:https://material-ui.com/getting-started/installation/

開發一個簡單的登陸表單

下面我們就來使用Material UI組件庫,來開發一個簡單的登陸表單頁面。這個表單頁面的最終效果如下圖:

image

使用 Card 佈局
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
使用表單 FormControl
import FormControl from '@material-ui/core/FormControl';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
使用 Button 按鈕
import Button from '@material-ui/core/Button';
使用 prop-types

我們使用 prop-types 第三方庫對組件的props中的變量進行類型檢測。安裝命令:

$ npm install prop-types

最終,登陸頁面的 js組件類的代碼如下:

class LoginForm extends React.Component {

    render() {
        const {classes} = this.props;

        return (
            <div className={classes.loginForm}>
                <Card className={classes.cardHeight}>
                    <CardContent>

                        <FormControl className={classes.formControl}>
                            <InputLabel>用戶名</InputLabel>
                            <Input id="username"/>
                        </FormControl>

                        <FormControl className={classes.formControl}>
                            <InputLabel>密碼</InputLabel>
                            <Input id="password" type='password'/>
                        </FormControl>

                        <div className={classes.inlineButton}>
                            <Button
                                variant="contained"
                                color="primary"
                                className={classes.button}>
                                登陸
                            </Button>
                            <Button variant="contained" className={classes.button}>
                                重置
                            </Button>
                        </div>

                    </CardContent>
                </Card>
            </div>
        )
    }
}
登陸按鈕事件處理

登陸按鈕的前端代碼如下:

<Button
    onClick={this.handleClick}
    variant="contained"
    color="primary"
    className={classes.button}>
    登陸
</Button>

其中,onClick 事件綁定當前LoginForm 類的handleClick 函數,代碼如下:

handleClick(event) {
    console.log(event.currentTarget);
    const username = document.getElementById('username').value
    const password = document.getElementById('password').value
    console.log({
        username: username,
        password: password
    })
};

這樣我們可以在登陸頁面,輸入用戶名、密碼:

image

點擊“登陸”,可以看到控制檯的輸出:

image

簡單前端表單校驗

通常,我們會在前端頁面對用戶輸入做一些合理性校驗。例如,我們添加對用戶名長度>3的校驗。首先,監聽用戶的表單輸入函數是 onChange, 用戶名錶單的 JSX代碼如下:

<FormControl className={classes.formControl}>
    <InputLabel>用戶名</InputLabel>
    <Input id="username" onChange={this.handleUsernameChange} autoFocus={true}/>
    <FormHelperText id="component-helper-text">{this.state.helperText}</FormHelperText>
</FormControl>

使用 FormHelperText 組件來提示用戶輸入的校驗結果。顯示的 helperText 存儲在 state 中。

其中,handleUsernameChange函數的代碼如下:

handleUsernameChange(event) {
    console.log(event.currentTarget);
    if (event.currentTarget.value.length < 3) {
        this.setState({ // 更新 helperText 提示文本
            helperText: '用戶名長度不得小於3'
        })
    } else {
        this.setState({
            helperText: ''
        })
    }
};

爲了能夠在JSX代碼中,可以直接使用onChange={this.handleUsernameChange}這樣的語法:

<Input id="username" onChange={this.handleUsernameChange} autoFocus={true}/>

同時能夠在handleUsernameChange()函數中使用 this.setState({...}) , 我們需要在構造函數提前綁定 this:

constructor(props) {
    super(props);
    this.state = {
        helperText: ''
    };

    // 這邊綁定是必要的,這樣 `this` (代表 LoginForm) 才能在回調函數中使用,例如:this.setState
    this.handleClick = this.handleClick.bind(this);
    this.handleUsernameChange = this.handleUsernameChange.bind(this);
}

這樣,我們在輸入用戶名的過程中,會看到實時提示:

image

表單提交函數編寫

這裏我們使用熟悉的 jquery的 ajax 來進行登陸表單的提交。首先,安裝 jquery依賴如下:

$ npm install jquery --save

安裝完畢,我們可以在 package.json 中多了 "jquery": "^3.3.1" 。

下面,我們來使用 ajax 寫登陸 Post 請求。

引入 jquery

首先,我們在LoginForm.js文件頭部 import jquery,代碼如下:

import $ from 'jquery'
登陸 Post代碼

下面就是寫一個普通的 ajax POST請求的代碼。

handleClick(event) {
    console.log(event.currentTarget);
    const username = document.getElementById('username').value
    const password = document.getElementById('password').value
    const data = {
        username: username,
        password: password
    };

    console.log(data);

    $.ajax({
        url: '/login.json',
        data: data,
        type: 'POST',
        success: (res) => {
            console.log(data)
        },
        error: (err) => {
            console.log(err)
        }
    });
};

在瀏覽器 console中測試運行,我們可以看到 POST請求已經成功的發出了:

image

只不過,我們還沒有後端的 HTTP接口/login.json 來接收這個請求。所以,我們看到的是404 Not Found。這種軟件開發的方法,我們可以稱之爲“前端驅動後端開發”。

前端核心組件 LoginForm.js的完整源代碼如下:

import React from 'react';
import PropTypes from 'prop-types';
import {withStyles} from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import FormHelperText from "@material-ui/core/es/FormHelperText/FormHelperText";
import $ from 'jquery'

const styles = theme => ({
    container: {
        display: 'flex',
        flexWrap: 'wrap',
    },
    button: {
        margin: theme.spacing.unit,
    },
    formControl: {
        margin: theme.spacing.unit,
        display: 'flex',
    },
    loginForm: {
        textAlign: 'center',
    },
    inlineButton: {
        display: 'inline-flex'
    },
    cardHeight: {
        height: '360px'
    }
});

class LoginForm extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            helperText: ''
        };

        // 這邊綁定是必要的,這樣 `this` (代表 LoginForm) 才能在回調函數中使用,例如:this.setState
        this.handleClick = this.handleClick.bind(this);
        this.handleUsernameChange = this.handleUsernameChange.bind(this);
    }

    handleClick(event) {
        console.log(event.currentTarget);
        const username = document.getElementById('username').value
        const password = document.getElementById('password').value
        const data = {
            username: username,
            password: password
        };

        console.log(data);

        $.ajax({
            url: '/login.json',
            data: data,
            type: 'POST',
            success: (res) => {
                console.log(data)
            },
            error: (err) => {
                console.log(err)
            }
        });
    };

    handleUsernameChange(event) {
        console.log(event.currentTarget);
        if (event.currentTarget.value.length < 3) {
            this.setState({
                helperText: '用戶名長度不得小於3'
            })
        } else {
            this.setState({
                helperText: ''
            })
        }
    };

    render() {
        const {classes} = this.props;

        return (
            <div className={classes.loginForm}>
                <Card className={classes.cardHeight}>
                    <CardContent>

                        <FormControl className={classes.formControl}>
                            <InputLabel>用戶名</InputLabel>
                            <Input id="username" onChange={this.handleUsernameChange} autoFocus={true}/>
                            <FormHelperText id="component-helper-text">{this.state.helperText}</FormHelperText>
                        </FormControl>

                        <FormControl className={classes.formControl}>
                            <InputLabel>密碼</InputLabel>
                            <Input id="password" type='password'/>
                        </FormControl>

                        <div className={classes.inlineButton}>
                            <Button
                                onClick={this.handleClick}
                                variant="contained"
                                color="primary"
                                className={classes.button}>
                                登陸
                            </Button>
                            <Button variant="contained" className={classes.button}>
                                重置
                            </Button>
                        </div>

                    </CardContent>
                </Card>
            </div>
        )
    }
}

LoginForm.propTypes = {
    classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(LoginForm);  

下面,我們就來開始後端工程的開發。

後端 Web 工程開發

本節主要介紹簡單用戶登錄模塊的後端工程的開發。

環境準備

本節實例工程的運行環境和技術棧相關清單如下:

運行環境:JDK 8

編程語言:Java、Kotlin

Web 開發框架:Spring MVC,Spring Boot

ORM框架:Spring Data JPA

數據庫:MySQL,客戶端工具 mysql workbench

視圖模板引擎:Freemarker

開發 IDE: IDEA

創建 Spring Boot工程

接下來,我們創建一個使用 Kotlin編程語言,Gradle 來構建項目的 Spring Boot工程。瀏覽器訪問:https://start.spring.io/ 創建工程如下圖所示:

image

選擇 Gradle Project,Kotlin編程語言,選擇 Spring Boot 2.1.0 版本,然後在項目基本信息中,分別填入 Group、Artifact,起步依賴選擇:Web,MySQL,JPA,Freemarker。 點擊“Generate Project”,下載自動生成的樣板工程,解壓,導入到 IDEA中。

構建項目

打開 IDEA,點擊 Open

image

選擇剛纔自動生成的樣板工程的根目錄

image

點擊“Open”,進入到 Import Project from Gradle界面:

image

如上圖勾選,其中 Gradle 安裝包的根目錄是:/Users/jack/soft/gradle-5.0-rc-1(這個需要根據自己的機器上的目錄自己指定)。點擊“OK”,進入到IDEA項目主界面,耐心等待項目構建完成,我們將看到如下的項目目錄結構:

image

修改 maven 中央倉庫地址

國外的 maven中央倉庫國內訪問起來比較慢,改用阿里雲提供的中央倉庫鏡像。在build.gradle中添加阿里雲倉庫鏡像的地址如下:

repositories {
    maven { url 'https://maven.aliyun.com/repository/central' }
    mavenCentral()
}
build.gradle 配置文件

我們可以看到在工程的依賴:

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
    implementation('org.springframework.boot:spring-boot-starter-freemarker')
    implementation('org.springframework.boot:spring-boot-starter-web')
    implementation('com.fasterxml.jackson.module:jackson-module-kotlin')
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    runtimeOnly('mysql:mysql-connector-java')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}
Spring Boot工程的入口類

我們可以看到,在 Spring Boot工程中,使用@SpringBootApplication註解標註 main 程序。

package com.easy.springboot.simpleloginbackend

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class SimpleLoginBackEndApplication

fun main(args: Array<String>) {
    runApplication<SimpleLoginBackEndApplication>(*args)
}

這個時候,我們直接運行這個 main函數,我們將會在控制檯看到如下的報錯提示:

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

Action:

Consider the following:
    If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

日誌告訴我們,Spring Boot應用在啓動過程中,自動初始化 DataSource 配置的時候失敗。因爲我們還沒有告訴程序,我們的數據庫連接的信息。

配置數據庫源

首先在數據庫中,創建 schema :

CREATE SCHEMA `simple_login` DEFAULT CHARACTER SET utf8 ;

然後,在 application.properties 中配置 datasource 如下:

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/simple_login?autoReconnect=true&useUnicode=true&createDatabaseIfNotExist=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root1234

再次啓動 main 程序,我們可以看到啓動成功了:

...
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
c.e.s.s.SimpleLoginBackEndApplicationKt  : Started SimpleLoginBackEndApplicationKt in 8.981 seconds (JVM running for 10.534)

這個時候,我們訪問 http://127.0.0.1:8080/ ,會看到一個默認錯誤頁面

image

因爲,此時我們的代碼中還沒有對請求處理的 Controller。

寫一個 Rest 接口Hello World

下面我們就來寫一個 Rest 接口。代碼如下:

package com.easy.springboot.simpleloginbackend.controller

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HelloWorldController {

    @GetMapping(value = ["/hello"])
    fun hello(): String {
        return "Hello World!"
    }

}

重啓應用,再次訪問 http://127.0.0.1:8080/ , 頁面輸出:Hello World!

如果想更改服務端口,只需要在 application.properties 中添加如下配置:

server.port=9000
編寫登陸 POST 接口

我們先簡單返回一個結果示例:

package com.easy.springboot.simpleloginbackend.controller

import com.easy.springboot.simpleloginbackend.result.Result
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
class LoginController {

    @PostMapping(value = ["/login.json"])
    fun login(@RequestParam("username") username: String, @RequestParam("password") password: String): Result<String> {
        return Result(data = "username=${username},password=${password}", success = true, msg = "")
    }

}

其中,Result類的代碼是:

package com.easy.springboot.simpleloginbackend.result

class Result<T>(
        var data: T? = null,
        var success: Boolean = false,
        var msg: String = ""
)
測試 POST 接口

爲了方便地進行測試,我們添加Spring Boot Actuator依賴到工程中:

dependencies {
    ...
    implementation('org.springframework.boot:spring-boot-starter-actuator')
    ...
}

重新啓動應用,我們將會在底部工具欄中看到端點請求映射:

image

單擊 /login.json [POST], 選擇 Open in HTTP Request Editor, 在 POST 後面加上參數

POST http://127.0.0.1:9000/login.json?username=123&password=123

如下圖, 點擊綠色執行按鈕

image

可以得到輸出:

POST http://127.0.0.1:9000/login.json?username=123&password=123

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 10 Nov 2018 16:19:10 GMT

{
  "data": "username=123,password=123",
  "success": true,
  "msg": ""
}

Response code: 200; Time: 23ms; Content length: 60 bytes

好了,現在我們的前端表單頁面有了,後端的 /login.json 接口也好了。怎樣集成呢?且看下文分解。

前後端集成聯調

本節我們來把上面的前端 js、html頁面集成到後端的 Spring Boot應用中來。

把前端代碼放到後端工程中

我們後端視圖引擎使用的是 Freemarker。默認的視圖文件在 src/main/resources/templates 目錄下。

我們先手動把前端工程中的index.html、 bundle.js 分別放到 src/main/resources 相應的目錄下面,如下圖

image

視圖文件默認後綴

然後,爲了方便起見,我們把 Freemarker 的默認文件後綴名改成 .html, 這個配置在 application.properties中:

spring.freemarker.suffix=.html
編寫請求轉發路由

編寫一個控制器,把來自前端的請求 "", "/", "/index.html", "/index.htm" 路由到後端的視圖index.html上。代碼如下:

package com.easy.springboot.simpleloginbackend.controller

import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping

@Controller
class IndexController {

    @GetMapping(value = ["", "/", "/index.html", "/index.htm"])
    fun index(): String {
        return "index"
    }

}

前後端聯調測試

重啓應用,訪問 http://127.0.0.1:9000/ , 我們會看到登陸表單頁面。輸入用戶名、密碼,點擊登錄

image

觀察瀏覽器的控制檯,我們可以看到請求成功信息:

image

請求響應值:

{"data":"username=jack,password=123456","success":true,"msg":""}

有了上面的前後端完整的開發流程作爲基礎,我們就可以連接數據庫,判斷用戶名、密碼是否存在;也可以在前端做出登錄成功、失敗的跳轉提示等處理了。我們會在後面的章節中逐步介紹。

本章小結

本章通過一個簡單的用戶登錄表單的前端 React開發、後端 Spring Boot + Kotlin開發的完整實例,給大家講解了前後端分離開發的簡單過程。當然,在實際的項目開發中,我們有一系列的自動化腳手架、構建工具插件等,我們會在其他章節中逐步介紹。

前端工程代碼地址:https://github.com/EasySpringBoot/front-end-simple-login

後端工程代碼地址:https://github.com/EasySpringBoot/simple-login-back-end

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