1. 业务需求
录入的课程涉及 4 个字段,分别为 课程名( name )、老师( teacher )、开始时间( start_time )、结束时间( end_time )。
1.1 具体需求
- 一个表格,展示目前所有的课程;
- 一个表单,可以添加要录入的课程;
- 录入课程数据后,刷新表格展示最新结果。
2. Mysql 准备
2.1 创建表格
CREATE TABLE `course` (
`id` int(11) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`teacher` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`start_time` date NOT NULL,
`end_time` date NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
2.2 插入数据
INSERT INTO `course`(`id`, `name`, `teacher`, `start_time`, `end_time`) VALUES (00000000001, 'vue源码', '陈小小', '2020-03-15', '2020-03-31');
这条数据是初始化数据,主要是让我们能够一开始就在页面的表格上看到数据而已。不插入也可以。只是查询结果就是空。
如果你使用了可视化数据库管理工具,比如 Navicat,那么此时,你会看到这样一个表格:
3. 页面结构
<!DOCTYPE html><html lang="en">
<head>
<meta charset="utf-8" />
<title>ajax example</title>
</head>
<style>
table {
border-collapse: collapse;
text-align: center;
width: 800px;
}
table td,
table th {
border: 1px solid #b8cfe9;
color: #666;
height: 30px;
}
table tr:nth-child(odd) {
background: #fff;
}
table tr:nth-child(even) {
background: rgb(246, 255, 255);
}
input {
outline-style: none;
border: 1px solid #ccc;
border-radius: 3px;
padding: 5px 10px;
width: 200px;
font-size: 14px;
}
button {
border: 0;
background-color: rgb(87, 177, 236);
color: #fff;
padding: 10px;
border-radius: 5px;
margin-top: 20px;
}
</style>
<body>
<div id="container">
<div class="query">
<h3>课程列表</h3>
<table id="courseTable"></table>
</div>
<div class="create">
<h3>添加课程</h3>
<div>
<label for="name">课程名称:</label><br />
<input type="text" id="name" name="name" /><br />
<label for="teacher">老师:</label><br />
<input type="text" id="teacher" name="teacher" /><br />
<label for="startTime">开始时间:</label><br />
<input type="date" id="startTime" name="startTime" /><br />
<label for="endTime">结束时间:</label><br />
<input type="date" id="endTime" name="endTime" /><br />
<button id="submitBtn">点击提交</button>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="/__build__/example.js"></script>
</body></html>
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
如上所示,我们首先定义好页面的结构和样式。可以清晰看出。页面主要分为两块,上面一块展示的是所有课程的结果,并且是表格呈现的,这里的 table 标签之所以没有嵌套,是因为我们会在后面 JavaScript 部分进行插入。下面一块则是录入课程的模块,分别有 课程名称、老师、开始时间和结束时间 4 个 input 标签。
4. 拉取服务端的数据
首先我们先不去管如何向后端添加一条数据,我们来做一个简单的数据查询。
- 前端通过 Ajax 发送一个查询请求;
- 后端接收到请求,处理请求,包括 MySQL 查询等,最后返回结果;
- 前端收到请求,进行界面上的 table 更新。
Talk is cheap,接下来我们来实现一下。
4.1 获取服务端课程数据
const getValues = () => {
return Ajax({
method: 'get',
url: '/course/get'
})}
这个方法我们返回Ajax请求对象,实际上是一个 promise,当服务端返回数据的时候,我们可以在后续的 ·then()
中进行表格更新。
这里要注意的是,由于 Ajax()
返回的是 promise, 所以 getValues()
返回的将会是个 promise。
4.2 表格更新
const updateTable = (data = []) => {
const table = document.querySelector('#courseTable');
let tableItems = [
`<tr>
<th>课程名称</th>
<th>老师</th>
<th>开始时间</th>
<th>结束时间</th>
</tr>`];
data.forEach(item => {
const {name, teacher, startTime, endTime} = item
tableItems.push(
`<tr>
<td>${name}</td>
<td>${teacher}</td>
<td>${moment(startTime).format('YYYY-MM-DD')}</td>
<td>${moment(endTime).format('YYYY-MM-DD')}</td>
</tr>`
);
table.innerHTML = tableItems.join('');
})}
- 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
表格更新函数接收一个数组数据,并且会遍历这个数组,分别把每一项处理为 HTML 字符串并添加到 tableItems 中。最后会把 tableItems 这个数组 通过 .join('')
的方式转化及拼接为一个 HTML 字符串,并添加到 table 中。
4.3 请求数据并更新表格
有了上面操作,一个负责获取数据,一个负责更新表格,那么我们可以合二为一,创造一个函数来控制流程。
const updateTableProcess = () => {
return getValues().then(res => {
const {code, data} = res if (code === 0) {
const {items} = data
updateTable(items)
}
}).catch(e => {
alert('获取数据失败啦')
})}updateTableProcess();
4.4 响应前端的请求
有求也要有应。服务端也需要在前端发出请求的时候做出相应的响应。
4.4.1 使用 node
const express = require("express");const mysql = require('mysql');const bodyParser = require("body-parser");const router = express.Router(); const app = express();app.use(bodyParser.json());app.use(bodyParser.urlencoded({ extended: true }));registerRouter(); app.use(router); const buildResponse = (code, data = {}, error = null) => {
return {
code,
data,
error }}const connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'ok36369ok',
database : 'mk'});connection.connect();const port = process.env.PORT || 8080;module.exports = app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`);});function registerRouter() {
router.get("/course/get", function(req, res) {
connection.query('SELECT id, name, teacher, start_time as startTime, end_time as endTime from course', function (error, results, fields) {
if (error) throw error;
const responseData = buildResponse(0, {items: results})
res.send(responseData);
});
});
}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
如上所示,我们引入了 Express 框架、bodyParser 以及 mysql 相关的库。其中, bodyParser 可以解析请求中 body 的内容。 不过我们的重点应该是最下面的函数 registerRouter,这个函数是用来注册我们所有的路由的。我们之后的路由也都会写在这个函数里面。
好了,回归正题。为了使服务端响应前端的请求,我们在上面的代码中注册了一个路由:
router.get("/course/get", callback)
如果前端发送请求到 “/course/get” ,那服务端会触发回调函数 callback,对应到上面代码中,我们可以看到:
- 内部会执行一个查询所有课程的 sql 语句;
- 将查询后的数据进行包装,变为 {code: 0, data: xxx, error: xxxx} 这样的格式;
- 返回数据。
相同的,我们也可以使用 Java 来实现后端的逻辑。
4.4.2 使用 Java
package com.demo;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import Javax.servlet.ServletException;import Javax.servlet.annotation.WebServlet;import Javax.servlet.http.HttpServlet;import Javax.servlet.http.HttpServletRequest;import Javax.servlet.http.HttpServletResponse;import Java.io.IOException;import Java.io.PrintWriter;import Java.sql.*;import Java.util.ArrayList;import Java.util.HashMap;import Java.util.List;import Java.util.Map;@WebServlet("/course/get")public class HelloWorld extends HttpServlet {
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/mk?useUnicode=true&useJDBCCompliantTimezoneShift\n" +
"=true&useLegacyDatetimeCode=false&serverTimezone=UTC";
static final String USER = "root";
static final String PW = "ok36369ok";
private Map buildResponse(int code, Object data, String error) {
Map<String, Object> res = new HashMap<String, Object>();
res.put("code", code);
res.put("data", data);
res.put("error", error);
return res;
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Connection conn = null;
Statement stmt = null;
request.setCharacterEncoding("utf-8");
response.setContentType("text/json; charset=utf-8");
PrintWriter out = response.getWriter();
Map<String,Object> resMap = new HashMap<String,Object>();
try{
Class.forName(JDBC_DRIVER);
System.out.println("连接数据库...");
conn = DriverManager.getConnection(DB_URL,USER,PW);
System.out.println(" 实例化Statement对象...");
stmt = conn.createStatement();
String sql;
sql = "SELECT * FROM course";
ResultSet rs = stmt.executeQuery(sql);
List<Map> Courses = new ArrayList<Map>();
while(rs.next()){
Map<String,Object> map = new HashMap<String, Object>();
int id = rs.getInt("id");
String name = rs.getString("name");
String teacher = rs.getString("teacher");
Date startTime = rs.getDate("start_time");
Date endTime = rs.getDate("end_time");
map.put("id", id);
map.put("name", name);
map.put("teacher", teacher);
map.put("startTime", startTime);
map.put("endTime", endTime);
Courses.add(map);
}
Map<String, List> data = new HashMap<String, List>();
data.put("items", Courses);
resMap = buildResponse(0, data, null);
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(stmt!=null) stmt.close();
}catch(SQLException se2){
}
try{
if(conn!=null) conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
String responseData = JSON.toJSONString(resMap);
out.println(responseData);
}}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
这里主要使用的是 servlet 的方式来为前端提供服务,对于请求课程列表来说,使用到 GET 方法,因此本例子中会进入到 doGet 方法中。另外,所用到的技术在章节须知中也有讲到,这里就不再累赘。实现的代码虽然和 node 端有所差别,但是思想都是一样的。无非也是使用 MySQL 的查询结果, 拼装成前端所需要的数据。并进行返回。
4.5 效果
好了,前后端都有相应的部署后,我们可以来看看效果了:
我们可以看到,刷新页面的时候,前端会通过 Ajax 发出 get 请求,服务端返回数据后,前端便在页面上绘制出表格。
5. 向服务端添加数据
向服务端添加数据,我们会采用 POST 请求。思路也非常简单,大致过程如下:
- 在页面的表单上填写数据;
- 填写完毕,点击提交按钮。这个时候发送 Ajax 请求;
- 服务端接收请求,执行 sql 语句进行数据插入;
- 插入成功,返回成功响应;
- 前端接收到服务端响应,重新更新页面上的表格。
咱一步一步来,首先我们需要有些函数和事件,来处理提交按钮的点击事件,并发送请求。
5.1 数据录入、发送请求及响应
const submitBtn = document.getElementById('submitBtn'); const submitValues = (values) => {
return Ajax({
method: 'post',
url: '/course/post',
data: values })}submitBtn.addEventListener('click', () => {
const inputItems = ['name', 'teacher', 'startTime', 'endTime']
const values = {}
inputItems.forEach(key => {
const value = document.getElementById(key).value;
if (!value) return;
values[key] = value })
submitValues(values).then(data => {
updateTableProcess();
})})
- 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
这段代码并显然没有考虑多重情况,比如填写的项是否可以为空,或者填写的值的格式控制,不过这都不重要。我们这里默认全部填写,并且是正确的情况。着重关注发送请求并带上 data 到后端的过程。
5.2 服务端写入 MySQL 并返回成功
假如我们已经录入了数据并点击了提交按钮,那此时服务端也需要有相应的路由来响应这个请求。简单的步骤,我们需要分三步走:
- 获取请求中的 data 数据,解构出我们要录入的每个字段。
- 执行 sql 语句, 录入新数据。
- 录入成功,返回成功结果。
5.2.1 使用 node
router.post("/course/post", function(req, res) {
const {name, teacher, startTime, endTime} = req.body
const sql = `INSERT INTO course(name, teacher, start_time, end_time) VALUES (?,?,?,?);`
const values = [name, teacher, startTime,endTime]
connection.query(sql, values,function (error, results, fields) {
if (error) throw error;
const responseData = buildResponse(0, null)
res.send(responseData);
});});
在服务端的响应代码中,我们可以看到。当服务端接收到请求的时候,会执行 SQL 语句,把前端发送的请求中的数据添加到 MySQL 中,然后返回成功的结果。
5.2.2 使用 Java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
StringBuffer data = new StringBuffer();
String line = null;
BufferedReader reader = null;
try {
reader = request.getReader();
while (null != (line = reader.readLine()))
data.append(line);
} catch (IOException e) {
} finally {
}
JSONObject body= JSON.parseObject(data.toString());
Connection conn = null;
PreparedStatement stmt = null;
request.setCharacterEncoding("utf-8");
response.setContentType("text/json; charset=utf-8");
PrintWriter out = response.getWriter();
Map<String,Object> resMap = new HashMap<String,Object>();
try{
Class.forName(JDBC_DRIVER);
System.out.println("连接数据库...");
conn = DriverManager.getConnection(DB_URL,USER,PW);
String sql;
sql = "INSERT INTO course(name, teacher, start_time, end_time) VALUES (?,?,?,?);";
stmt = conn.prepareStatement(sql);
stmt.setString(1, body.getString("name"));
stmt.setString(2, body.getString("teacher"));
stmt.setString(3, body.getString("startTime"));
stmt.setString(4, body.getString("endTime"));
stmt.executeUpdate();
stmt.close();
conn.close();
resMap = buildResponse(0, null, null);
String responseData = JSON.toJSONString(resMap);
out.println(responseData);
}catch(SQLException se){
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(stmt!=null) stmt.close();
}catch(SQLException se2){
}
try{
if(conn!=null) conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}}
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
这里 Java 部分只给出 doPost 部分,这里有个地方需要注意,在 doPost 函数中,我们在一开始需要对 body 进行解析,获取我们需要的参数。其他的思路也是一样的,在 sql 语句成功执行之后,我们包装好结果进行返回。
5.3 效果
可以看到,当我们点击提交的时候,前端发送 POST 请求,触发服务端插入新数据。当前端接收到服务端的响应之后,又发送了 GET 请求,获取最新的课程列表数据,并绘制到网页上。
6. 结语
本章节旨在通过一个课程录入的例子,并使用 GET 和 POST 两种请求方式来诠释一个前后端交互的完整过程。希望同学们在这个过程中,能够学习到 Ajax 在实际交互中的一个简单的应用,包括:
- 了解如何响应事件,比如点击事件;
- 了解如何发送一个请求;
- 了解如何简单使用 MySQL;
- 了解后端如何返回响应内容;
- 如何前端如何处理响应内容并更新页面。
在实际的开发工作中, Ajax 好比前后端之间的一个桥梁,我们可以通过这个桥梁进行通信。然而,如何通信,何时通信,更需要我们有一个良好的程序控制。
总而言之,希望同学们能够多用、善用 Ajax,并且让它在你们的程序中发光发亮。