zoukankan      html  css  js  c++  java
  • Ajax九

    Ajax 前后端交互例子

    前面的章节,我们主要从什么是 Ajax、为什么要用 Ajax、Ajax 是如何实现的以及如何封装一个 Ajax来对 Ajax 做一个多方位的学习。从前面章节的学习中,相信同学们对 Ajax 都会有一个比较完整的概念了。那么,接下来的这个章节,我们着重列举一个示例,来讲述 Ajax 是如何进行前后端交互的。

    本章须知

    • 本章节会给出前后端简单代码,弱化容错性等增强性需求,重点描述前后端交互的过程和效果。
    • 本章节前端使用前面章节封装的 Ajax, 因此本章节代码不涉及封装 Ajax 的相关代码,需要了解的同学可以翻看前面章节。
    • 本章节会给出业务相关的前端代码, 前端使用 HTML、Css、和JavaScript,使用 moment.js 库进行时间的格式化。
    • 本章节提供 node 后端代码,使用框架为 Express。node 端会使用 MySQL包。
    • 本章节也提供相应的 Java 后端代码。
    • 本章节 Java 服务端使用 servlet 提供服务。引入了 fastjson 包进行 JSON 的一些列序列化和反序列化的操作;使用 mysql-connector-java 来进行 java 端数据库的连接和操作。

    1. 业务需求

    构建一个简单的课程录入和查询页面。

    录入的课程涉及 4 个字段,分别为 课程名( name )、老师( teacher )、开始时间( start_time )、结束时间( end_time )。

    1.1 具体需求

    1. 一个表格,展示目前所有的课程;
    2. 一个表单,可以添加要录入的课程;
    3. 录入课程数据后,刷新表格展示最新结果。

    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>
    
    

    如上所示,我们首先定义好页面的结构和样式。可以清晰看出。页面主要分为两块,上面一块展示的是所有课程的结果,并且是表格呈现的,这里的 table 标签之所以没有嵌套,是因为我们会在后面 JavaScript 部分进行插入。下面一块则是录入课程的模块,分别有 课程名称、老师、开始时间和结束时间 4 个 input 标签。

    4. 拉取服务端的数据

    首先我们先不去管如何向后端添加一条数据,我们来做一个简单的数据查询。

    那么, 前后端分别要做什么?

    简单来说,前后端按顺序应该是这样的:

    1. 前端通过 Ajax 发送一个查询请求;
    2. 后端接收到请求,处理请求,包括 MySQL 查询等,最后返回结果;
    3. 前端收到请求,进行界面上的 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'); // 获取页面的table
        
        // table 的项, 这里初始化会添加表格头
        let tableItems = [
        `<tr>
            <th>课程名称</th>
            <th>老师</th>
            <th>开始时间</th>
            <th>结束时间</th>
        </tr>`];
        
        // 对数据做一个遍历, 处理为 HTML 字符串并添加到对应的 tableItems 的项中。
        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(''); // 数组转为字符串, 添加到 table 中
        })
    }
    

    表格更新函数接收一个数组数据,并且会遍历这个数组,分别把每一项处理为 HTML 字符串并添加到 tableItems 中。最后会把 tableItems 这个数组 通过 .join('') 的方式转化及拼接为一个 HTML 字符串,并添加到 table 中。

    4.3 请求数据并更新表格

    有了上面操作,一个负责获取数据,一个负责更新表格,那么我们可以合二为一,创造一个函数来控制流程。

    const updateTableProcess = () => {
        return getValues().then(res => {
            const {code, data} = res
            if (code === 0) { // 返回结果 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(); // express 路由
    
    const app = express();
    
    // 使用 bodyParser 中间件
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    registerRouter();  // 路由注册执行
    
    app.use(router); // 使用 router
    
    /**
     * 构建返回结果
     * @param {*} code 
     * @param {*} data 
     * @param {*} error 
     */
    const buildResponse = (code, data = {}, error = null) => {
        return {
            code,
            data,
            error
        }
    }
    
    // 创建 mysql 链接, 本地数据库为 mk
    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}) // mysql 查询结果包装后进行返回
                res.send(responseData); // send 结果
            });
        });
        
        // other router ..
    }
    

    如上所示,我们引入了 Express 框架、bodyParser 以及 mysql 相关的库。其中, bodyParser 可以解析请求中 body 的内容。 不过我们的重点应该是最下面的函数 registerRouter,这个函数是用来注册我们所有的路由的。我们之后的路由也都会写在这个函数里面。

    好了,回归正题。为了使服务端响应前端的请求,我们在上面的代码中注册了一个路由:

     router.get("/course/get", callback)
    

    如果前端发送请求到 “/course/get” ,那服务端会触发回调函数 callback,对应到上面代码中,我们可以看到:

    1. 内部会执行一个查询所有课程的 sql 语句;
    2. 将查询后的数据进行包装,变为 {code: 0, data: xxx, error: xxxx} 这样的格式;
    3. 返回数据。

    相同的,我们也可以使用 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 {
    
        // JDBC 驱动名
        static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
        // 数据库 URL
        static final String DB_URL = "jdbc:mysql://localhost:3306/mk?useUnicode=true&useJDBCCompliantTimezoneShift
    " +
                "=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 {
    
        }
    
        // 获取课程使用 GET, 会进入 doGet
        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>(); // 返回结果, Map 类型
            try{
                // 注册 JDBC 驱动
                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");
                    
                    // 分别将 mysql 查询结果 put 到 map中
                    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部分, Map 类型
                data.put("items", Courses); // data 添加 items, items 就是我们要的课程列表数据
    
                // 构建输出数据
                resMap = buildResponse(0, data, null);
                // 完成后关闭
                rs.close();
                stmt.close();
                conn.close();
            }catch(SQLException se){
                // 处理 JDBC 错误
                se.printStackTrace();
            }catch(Exception e){
                // 处理 Class.forName 错误
                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);// 将 Map 类型的结果序列化为 String
            out.println(responseData); // 返回结果
        }
    }
    
    

    这里主要使用的是 servlet 的方式来为前端提供服务,对于请求课程列表来说,使用到 GET 方法,因此本例子中会进入到 doGet 方法中。另外,所用到的技术在章节须知中也有讲到,这里就不再累赘。实现的代码虽然和 node 端有所差别,但是思想都是一样的。无非也是使用 MySQL 的查询结果, 拼装成前端所需要的数据。并进行返回。

    4.5 效果

    好了,前后端都有相应的部署后,我们可以来看看效果了:

    图片描述

    我们可以看到,刷新页面的时候,前端会通过 Ajax 发出 get 请求,服务端返回数据后,前端便在页面上绘制出表格。

    5. 向服务端添加数据

    向服务端添加数据,我们会采用 POST 请求。思路也非常简单,大致过程如下:

    1. 在页面的表单上填写数据;
    2. 填写完毕,点击提交按钮。这个时候发送 Ajax 请求;
    3. 服务端接收请求,执行 sql 语句进行数据插入;
    4. 插入成功,返回成功响应;
    5. 前端接收到服务端响应,重新更新页面上的表格。

    咱一步一步来,首先我们需要有些函数和事件,来处理提交按钮的点击事件,并发送请求。

    5.1 数据录入、发送请求及响应

    const submitBtn = document.getElementById('submitBtn'); // 提交按钮
    
    /**
    * 提交数据函数, 返回的是一个 promise
    */
    const submitValues = (values) => {
        return Ajax({
            method: 'post',
            url: '/course/post',
            data: values
        })
    }
    
    // 提交按钮绑定点击事件处理函数
    submitBtn.addEventListener('click', () => {
        const inputItems = ['name', 'teacher', 'startTime', 'endTime'] // 表单项几个 key
        const values = {} // 初始化返回数据
        inputItems.forEach(key => {
            // 遍历 inputItems,获取对应的 input 上的 value
            // 如果 value 有值, 则添加相应的项
            const value = document.getElementById(key).value;
            if (!value) return;
            values[key] = value
        })
        
        // 最后发送提交请求
        submitValues(values).then(data => {
            updateTableProcess(); // 成功后重新更新表格
        })
    })
    

    这段代码并显然没有考虑多重情况,比如填写的项是否可以为空,或者填写的值的格式控制,不过这都不重要。我们这里默认全部填写,并且是正确的情况。着重关注发送请求并带上 data 到后端的过程。

    5.2 服务端写入 MySQL 并返回成功

    假如我们已经录入了数据并点击了提交按钮,那此时服务端也需要有相应的路由来响应这个请求。简单的步骤,我们需要分三步走:

    1. 获取请求中的 data 数据,解构出我们要录入的每个字段。
    2. 执行 sql 语句, 录入新数据。
    3. 录入成功,返回成功结果。

    对照着这几个步骤,我们来看看下面的代码:

    5.2.1 使用 node

    // 添加课程
    router.post("/course/post", function(req, res) {
        const {name, teacher, startTime, endTime} = req.body // 解构出每个字段
        
        // sql 语句, “?” 会对应后面 values
        const sql = `INSERT INTO course(name, teacher, start_time, end_time) VALUES (?,?,?,?);` 
        const values = [name, teacher, startTime,endTime] // 插入的所有值
        
        // 执行 sql 语句
        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 {
        // 解析 body 中的参数,结果将会添加到 data 上
        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()); // 将解析后的结果转变为 JSONObject 对象
        Connection conn = null;
        PreparedStatement stmt = null;
        
        // 设置编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/json; charset=utf-8");
        
        // 输出对象
        PrintWriter out = response.getWriter();
        
        // 返回结果, Map 类型
        Map<String,Object> resMap = new HashMap<String,Object>();
        try{
            // 注册 JDBC 驱动
            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){
            // 处理 JDBC 错误
            se.printStackTrace();
        }catch(Exception e){
            // 处理 Class.forName 错误
            e.printStackTrace();
        }finally{
            // 关闭资源
            try{
                if(stmt!=null) stmt.close();
            }catch(SQLException se2){
            }
            try{
                if(conn!=null) conn.close();
            }catch(SQLException se){
                se.printStackTrace();
            }
        }
    }
    

    这里 Java 部分只给出 doPost 部分,这里有个地方需要注意,在 doPost 函数中,我们在一开始需要对 body 进行解析,获取我们需要的参数。其他的思路也是一样的,在 sql 语句成功执行之后,我们包装好结果进行返回。

    5.3 效果

    图片描述

    可以看到,当我们点击提交的时候,前端发送 POST 请求,触发服务端插入新数据。当前端接收到服务端的响应之后,又发送了 GET 请求,获取最新的课程列表数据,并绘制到网页上。

    6. 结语

    讲到这里,本章节也就到了尾声。

    本章节旨在通过一个课程录入的例子,并使用 GET 和 POST 两种请求方式来诠释一个前后端交互的完整过程。希望同学们在这个过程中,能够学习到 Ajax 在实际交互中的一个简单的应用,包括:

    • 了解如何响应事件,比如点击事件;
    • 了解如何发送一个请求;
    • 了解如何简单使用 MySQL;
    • 了解后端如何返回响应内容;
    • 如何前端如何处理响应内容并更新页面。

    在实际的开发工作中, Ajax 好比前后端之间的一个桥梁,我们可以通过这个桥梁进行通信。然而,如何通信,何时通信,更需要我们有一个良好的程序控制。

    总而言之,希望同学们能够多用、善用 Ajax,并且让它在你们的程序中发光发亮。

  • 相关阅读:
    迈步从头越
    C 语言中用bsearch()实现查找操作
    ASP.NET 远程调试
    JsonHelper
    json数据中包含html代码的解决方法
    js对url的常见操作
    jquery中ajax中get和post的用法
    图片和文件合成为图片的方法(黑客)
    数据库分库知识
    长链接转短链接的方法(百度、新浪、腾讯)
  • 原文地址:https://www.cnblogs.com/liunaiming/p/13060506.html
Copyright © 2011-2022 走看看