AJAX XML 实例

AJAX XML 实例(中文讲解)

本教程展示如何使用 AJAX 与服务器交互,处理 XML 格式的响应数据,结合 Node.js 和 Express 构建后端,MongoDB 存储数据。实例实现用户数据的查询(GET 请求)和添加(POST 请求),服务器返回 XML 响应,客户端使用 XMLHttpRequestfetch 解析 XML 数据并显示。本例简洁实用,突出 XML 的处理,适合理解 AJAX 与 XML 的交互。


1. 实例概述

  • 目标:通过 AJAX 实现用户管理功能:
  • GET 请求:查询 MongoDB 用户数据,返回 XML 格式,显示在页面。
  • POST 请求:提交新用户数据,存入 MongoDB,返回 XML 确认。
  • 技术栈
  • 客户端:HTML + JavaScript(XMLHttpRequestfetch)。
  • 服务器:Node.js + Express,生成 XML 响应。
  • 数据库:MongoDB,存储用户数据。
  • 数据格式:XML(服务器响应),JSON(POST 请求体)。

2. 环境准备

确保以下工具已安装:

  • Node.js:LTS 版本(如 v20.x),运行 node -v 检查。
  • MongoDB:本地运行或使用 MongoDB Atlas。
  • npm 依赖:Express、MongoDB 驱动和 xmlbuilder2(用于生成 XML)。

安装依赖:

mkdir ajax-xml-example
cd ajax-xml-example
npm init -y
npm install express mongodb xmlbuilder2

MongoDB 数据准备:

use myDatabase
db.users.insertMany([
  { name: "张三", age: 25, hobbies: ["读书", "旅行"] },
  { name: "李四", age: 30, hobbies: ["编程", "运动"] }
])

3. 服务器端代码(Node.js + Express + MongoDB)

创建 server.js,查询 MongoDB 数据并返回 XML 格式响应。

const express = require('express');
const { MongoClient } = require('mongodb');
const { create } = require('xmlbuilder2');
const app = express();
const uri = 'mongodb://localhost:27017/myDatabase';
const client = new MongoClient(uri);

app.use(express.json());
app.use(express.static('public'));
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST');
  next();
});

// 查询用户(返回 XML)
app.get('/users', async (req, res) => {
  try {
    await client.connect();
    const db = client.db('myDatabase');
    const collection = db.collection('users');
    const users = await collection.find({}).toArray();

    // 构建 XML
    const xml = create({ version: '1.0' })
      .ele('users')
      .dtd()
      users.forEach(user => {
        const userNode = xml.ele('user');
        userNode.ele('name').txt(user.name).up();
        userNode.ele('age').txt(user.age).up();
        const hobbiesNode = userNode.ele('hobbies');
        user.hobbies.forEach(hobby => {
          hobbiesNode.ele('hobby').txt(hobby).up();
        });
      });
    const xmlString = xml.end({ prettyPrint: true });

    res.set('Content-Type', 'text/xml');
    res.send(xmlString);
  } catch (error) {
    console.error('查询失败:', error);
    res.status(500).send('<error>服务器错误</error>');
  } finally {
    await client.close();
  }
});

// 添加用户(接收 JSON,返回 XML)
app.post('/users', async (req, res) => {
  try {
    await client.connect();
    const db = client.db('myDatabase');
    const collection = db.collection('users');
    const user = req.body;
    if (!user.name || !user.age) {
      const xml = create({ version: '1.0' })
        .ele('error')
        .txt('姓名和年龄必填')
        .end({ prettyPrint: true });
      res.status(400).set('Content-Type', 'text/xml').send(xml);
      return;
    }
    const result = await collection.insertOne(user);

    // 构建 XML 响应
    const xml = create({ version: '1.0' })
      .ele('response')
      .ele('message').txt('添加成功').up()
      .ele('id').txt(result.insertedId.toString()).up()
      .end({ prettyPrint: true });

    res.set('Content-Type', 'text/xml');
    res.send(xml);
  } catch (error) {
    console.error('添加失败:', error);
    res.status(500).send('<error>服务器错误</error>');
  } finally {
    await client.close();
  }
});

app.listen(3000, () => console.log('服务器运行在 http://localhost:3000'));
  • 说明
  • 使用 xmlbuilder2 生成 XML 响应。
  • /users (GET):返回用户列表,格式为 XML。
  • /users (POST):接收 JSON 数据,插入用户,返回 XML 确认。
  • 设置 Content-Type: text/xml
  • CORS 允许跨域请求。

XML 响应示例

  • GET /users
  <?xml version="1.0"?>
  <!DOCTYPE users>
  <users>
    <user>
      <name>张三</name>
      <age>25</age>
      <hobbies>
        <hobby>读书</hobby>
        <hobby>旅行</hobby>
      </hobbies>
    </user>
    <user>
      <name>李四</name>
      <age>30</age>
      <hobbies>
        <hobby>编程</hobby>
        <hobby>运动</hobby>
      </hobbies>
    </user>
  </users>
  • POST /users
  <?xml version="1.0"?>
  <response>
    <message>添加成功</message>
    <id>someObjectId</id>
  </response>

4. 客户端代码(HTML + AJAX)

创建 public/index.html,使用 XMLHttpRequestfetch 处理 XML 响应,显示用户数据。

<!DOCTYPE html>
<html>
<head>
  <title>AJAX XML 示例</title>
  <style>
    table { border-collapse: collapse; margin-top: 10px; }
    th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
    button, input { padding: 10px; margin: 5px; }
  </style>
</head>
<body>
  <h1>用户管理</h1>
  <div>
    <input id="name" placeholder="姓名">
    <input id="age" type="number" placeholder="年龄">
    <button onclick="addUser()">添加用户(XMLHttpRequest)</button>
    <button onclick="addUserFetch()">添加用户(Fetch)</button>
  </div>
  <button onclick="loadUsers()">加载用户</button>
  <table id="userTable">
    <thead>
      <tr><th>姓名</th><th>年龄</th><th>爱好</th></tr>
    </thead>
    <tbody id="userList"></tbody>
  </table>
  <script>
    // XMLHttpRequest 加载用户
    function loadUsers() {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', 'http://localhost:3000/users', true);
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            try {
              const xml = xhr.responseXML;
              if (!xml) throw new Error('无效 XML');
              const users = parseUsersXML(xml);
              displayUsers(users);
            } catch (error) {
              document.getElementById('userList').innerHTML = `<tr><td colspan="3">错误:${error.message}</td></tr>`;
            }
          } else {
            document.getElementById('userList').innerHTML = `<tr><td colspan="3">错误:${xhr.statusText} (${xhr.status})</td></tr>`;
          }
        }
      };
      xhr.onerror = function () {
        document.getElementById('userList').innerHTML = '<tr><td colspan="3">错误:网络错误</td></tr>';
      };
      xhr.send();
    }

    // XMLHttpRequest 添加用户
    function addUser() {
      const name = document.getElementById('name').value;
      const age = parseInt(document.getElementById('age').value);
      if (!name || !age) {
        alert('请输入姓名和年龄');
        return;
      }
      const xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://localhost:3000/users', true);
      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            try {
              const xml = xhr.responseXML;
              if (!xml) throw new Error('无效 XML');
              const message = xml.querySelector('message')?.textContent || '未知';
              alert(message);
              loadUsers();
            } catch (error) {
              alert(`错误:${error.message}`);
            }
          } else {
            alert(`添加失败:${xhr.statusText}`);
          }
        }
      };
      xhr.onerror = function () {
        alert('网络错误');
      };
      xhr.send(JSON.stringify({ name, age, hobbies: ['未知'] }));
    }

    // Fetch 添加用户
    async function addUserFetch() {
      const name = document.getElementById('name').value;
      const age = parseInt(document.getElementById('age').value);
      if (!name || !age) {
        alert('请输入姓名和年龄');
        return;
      }
      try {
        const response = await fetch('http://localhost:3000/users', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ name, age, hobbies: ['未知'] })
        });
        if (!response.ok) throw new Error(`服务器错误:${response.status}`);
        const text = await response.text();
        const parser = new DOMParser();
        const xml = parser.parseFromString(text, 'text/xml');
        if (xml.querySelector('parsererror')) throw new Error('无效 XML');
        const message = xml.querySelector('message')?.textContent || '未知';
        alert(message);
        loadUsers();
      } catch (error) {
        alert(`添加失败:${error.message}`);
      }
    }

    // 解析 XML 用户数据
    function parseUsersXML(xml) {
      const users = [];
      const userNodes = xml.getElementsByTagName('user');
      for (let userNode of userNodes) {
        const name = userNode.getElementsByTagName('name')[0]?.textContent || '';
        const age = parseInt(userNode.getElementsByTagName('age')[0]?.textContent || '0');
        const hobbyNodes = userNode.getElementsByTagName('hobby');
        const hobbies = Array.from(hobbyNodes).map(hobby => hobby.textContent);
        users.push({ name, age, hobbies });
      }
      return users;
    }

    // 显示用户列表
    function displayUsers(users) {
      const userList = document.getElementById('userList');
      userList.innerHTML = '';
      if (users.length === 0) {
        userList.innerHTML = '<tr><td colspan="3">无用户数据</td></tr>';
        return;
      }
      users.forEach(user => {
        const row = document.createElement('tr');
        row.innerHTML = `
          <td>${user.name}</td>
          <td>${user.age}</td>
          <td>${user.hobbies.join(', ')}</td>
        `;
        userList.appendChild(row);
      });
    }
  </script>
</body>
</html>
  • 说明
  • 使用 <table> 显示用户数据,解析 XML 响应。
  • loadUsers():通过 xhr.responseXMLDOMParser 解析 XML,提取用户数据。
  • addUser()addUserFetch():发送 JSON 数据,接收 XML 响应,显示结果。
  • parseUsersXML():解析 XML,转换为 JavaScript 对象。
  • 处理错误(如无效 XML、网络问题)。

5. 运行和测试

  1. 启动服务器
   node server.js
  1. 访问客户端
  • 打开浏览器,访问 http://localhost:3000
  • 点击“加载用户”,显示用户表格:
    姓名 年龄 爱好 张三 25 读书, 旅行 李四 30 编程, 运动
  • 输入姓名和年龄,点击“添加用户(XMLHttpRequest)”或“添加用户(Fetch)”,添加用户并刷新表格。
  1. 响应解析
  • XMLHttpRequest:直接使用 xhr.responseXML 获取 DOM 对象。
  • fetch:通过 response.text() 获取 XML 字符串,使用 DOMParser 解析。

6. XML 在实例中的作用

  • 服务器:使用 xmlbuilder2 生成 XML 响应,设置 Content-Type: text/xml
  • 客户端
  • XMLHttpRequest:通过 xhr.responseXML 获取解析后的 DOM 对象。
  • fetch:获取文本后使用 DOMParser 解析为 DOM。
  • 使用 DOM 方法(如 getElementsByTagName)提取数据。
  • 与 JSON 对比
  • XML 更结构化,适合复杂嵌套数据,但解析较繁琐。
  • JSON 更轻量,现代主流,解析更简单(response.json())。

7. 注意事项与最佳实践

  1. XML 解析
  • 检查 xhr.responseXMLxml.querySelector('parsererror') 确保有效 XML:
    javascript if (!xml || xml.querySelector('parsererror')) throw new Error('无效 XML');
  1. 跨域处理
  • 本例使用 CORS(Access-Control-Allow-Origin: *)。
  • 生产环境限制域名:
    javascript res.header('Access-Control-Allow-Origin', 'http://trusted-domain.com');
  1. 错误处理
  • XMLHttpRequest:检查 xhr.statusonerror
  • fetch:检查 response.oktry-catch
  • 服务器验证输入,防止无效数据。
  1. 超时设置
  • XMLHttpRequest
    javascript xhr.timeout = 5000; xhr.ontimeout = () => alert('请求超时');
  • fetch
    javascript const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); fetch('http://localhost:3000/users', { signal: controller.signal });
  1. 推荐 JSON
  • XML 解析复杂,现代开发推荐 JSON(fetch + response.json())。
  • XML 适用于遗留系统或特定需求(如 SOAP)。
  1. 调试
  • 使用浏览器开发者工具(Network 面板)检查 XML 响应。
  • 验证 XML 格式(在线 XML 验证工具)。

8. 总结

  • 实例功能:通过 AJAX 查询和添加 MongoDB 用户,处理 XML 响应,渲染表格。
  • XML 角色:服务器返回 XML,客户端通过 responseXMLDOMParser 解析。
  • 技术要点
  • XMLHttpRequest:使用 onreadystatechangeresponseXML
  • fetch:结合 DOMParser 解析 XML。
  • 最佳实践:检查 XML 有效性、处理错误、设置超时,考虑 JSON 替代。

如果你需要其他格式(如 JSON+XML 混合)、更复杂功能(如分页、搜索),或想深入某部分,请告诉我,我会提供详细代码或指导!

const express = require('express');
const { MongoClient } = require('mongodb');
const { create } = require('xmlbuilder2');
const app = express();
const uri = 'mongodb://localhost:27017/myDatabase';
const client = new MongoClient(uri);

app.use(express.json());
app.use(express.static('public'));
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST');
  next();
});

app.get('/users', async (req, res) => {
  try {
    await client.connect();
    const db = client.db('myDatabase');
    const collection = db.collection('users');
    const users = await collection.find({}).toArray();

    const xml = create({ version: '1.0' })
      .ele('users')
      .dtd()
      users.forEach(user => {
        const userNode = xml.ele('user');
        userNode.ele('name').txt(user.name).up();
        userNode.ele('age').txt(user.age).up();
        const hobbiesNode = userNode.ele('hobbies');
        user.hobbies.forEach(hobby => {
          hobbiesNode.ele('hobby').txt(hobby).up();
        });
      });
    const xmlString = xml.end({ prettyPrint: true });

    res.set('Content-Type', 'text/xml');
    res.send(xmlString);
  } catch (error) {
    console.error('查询失败:', error);
    res.status(500).send('<error>服务器错误</error>');
  } finally {
    await client.close();
  }
});

app.post('/users', async (req, res) => {
  try {
    await client.connect();
    const db = client.db('myDatabase');
    const collection = db.collection('users');
    const user = req.body;
    if (!user.name || !user.age) {
      const xml = create({ version: '1.0' })
        .ele('error')
        .txt('姓名和年龄必填')
        .end({ prettyPrint: true });
      res.status(400).set('Content-Type', 'text/xml').send(xml);
      return;
    }
    const result = await collection.insertOne(user);

    const xml = create({ version: '1.0' })
      .ele('response')
      .ele('message').txt('添加成功').up()
      .ele('id').txt(result.insertedId.toString()).up()
      .end({ prettyPrint: true });

    res.set('Content-Type', 'text/xml');
    res.send(xml);
  } catch (error) {
    console.error('添加失败:', error);
    res.status(500).send('<error>服务器错误</error>');
  } finally {
    await client.close();
  }
});

app.listen(3000, () => console.log('服务器运行在 http://localhost:3000'));
<!DOCTYPE html>
<html>
<head>
  <title>AJAX XML 示例</title>
  <style>
    table { border-collapse: collapse; margin-top: 10px; }
    th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
    button, input { padding: 10px; margin: 5px; }
  </style>
</head>
<body>
  <h1>用户管理</h1>
  <div>
    <input id="name" placeholder="姓名">
    <input id="age" type="number" placeholder="年龄">
    <button onclick="addUser()">添加用户(XMLHttpRequest)</button>
    <button onclick="addUserFetch()">添加用户(Fetch)</button>
  </div>
  <button onclick="loadUsers()">加载用户</button>
  <table id="userTable">
    <thead>
      <tr><th>姓名</th><th>年龄</th><th>爱好</th></tr>
    </thead>
    <tbody id="userList"></tbody>
  </table>
  <script>
    function loadUsers() {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', 'http://localhost:3000/users', true);
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            try {
              const xml = xhr.responseXML;
              if (!xml) throw new Error('无效 XML');
              const users = parseUsersXML(xml);
              displayUsers(users);
            } catch (error) {
              document.getElementById('userList').innerHTML = `<tr><td colspan="3">错误:${error.message}</td></tr>`;
            }
          } else {
            document.getElementById('userList').innerHTML = `<tr><td colspan="3">错误:${xhr.statusText} (${xhr.status})</td></tr>`;
          }
        }
      };
      xhr.onerror = function () {
        document.getElementById('userList').innerHTML = '<tr><td colspan="3">错误:网络错误</td></tr>';
      };
      xhr.send();
    }

    function addUser() {
      const name = document.getElementById('name').value;
      const age = parseInt(document.getElementById('age').value);
      if (!name || !age) {
        alert('请输入姓名和年龄');
        return;
      }
      const xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://localhost:3000/users', true);
      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            try {
              const xml = xhr.responseXML;
              if (!xml) throw new Error('无效 XML');
              const message = xml.querySelector('message')?.textContent || '未知';
              alert(message);
              loadUsers();
            } catch (error) {
              alert(`错误:${error.message}`);
            }
          } else {
            alert(`添加失败:${xhr.statusText}`);
          }
        }
      };
      xhr.onerror = function () {
        alert('网络错误');
      };
      xhr.send(JSON.stringify({ name, age, hobbies: ['未知'] }));
    }

    async function addUserFetch() {
      const name = document.getElementById('name').value;
      const age = parseInt(document.getElementById('age').value);
      if (!name || !age) {
        alert('请输入姓名和年龄');
        return;
      }
      try {
        const response = await fetch('http://localhost:3000/users', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ name, age, hobbies: ['未知'] })
        });
        if (!response.ok) throw new Error(`服务器错误:${response.status}`);
        const text = await response.text();
        const parser = new DOMParser();
        const xml = parser.parseFromString(text, 'text/xml');
        if (xml.querySelector('parsererror')) throw new Error('无效 XML');
        const message = xml.querySelector('message')?.textContent || '未知';
        alert(message);
        loadUsers();
      } catch (error) {
        alert(`添加失败:${error.message}`);
      }
    }

    function parseUsersXML(xml) {
      const users = [];
      const userNodes = xml.getElementsByTagName('user');
      for (let userNode of userNodes) {
        const name = userNode.getElementsByTagName('name')[0]?.textContent || '';
        const age = parseInt(userNode.getElementsByTagName('age')[0]?.textContent || '0');
        const hobbyNodes = userNode.getElementsByTagName('hobby');
        const hobbies = Array.from(hobbyNodes).map(hobby => hobby.textContent);
        users.push({ name, age, hobbies });
      }
      return users;
    }

    function displayUsers(users) {
      const userList = document.getElementById('userList');
      userList.innerHTML = '';
      if (users.length === 0) {
        userList.innerHTML = '<tr><td colspan="3">无用户数据</td></tr>';
        return;
      }
      users.forEach(user => {
        const row = document.createElement('tr');
        row.innerHTML = `
          <td>${user.name}</td>
          <td>${user.age}</td>
          <td>${user.hobbies.join(', ')}</td>
        `;
        userList.appendChild(row);
      });
    }
  </script>
</body>
</html>

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注