物联网服务器搭建及部署详细说明:掌握 Node.js、MongoDB、Socket.IO 和 JWT 的实用指南

关键知识点目录

1. 环境准备

1.1 硬件要求

1.2 软件要求

2. 搭建步骤

3. 数据处理与存储

3.1 数据存储

3.2 数据实时处理

3.2.1 安装 Socket.IO

3.2.2 修改服务器代码

4. 安全性

4.1 身份验证与授权

4.2 加密通信

4.2.1 生成自签名证书(开发环境)

4.2.2 修改服务器以支持 HTTPS

5. 数据分析和可视化

5.1 集成 Grafana

5.2.2 图表数据更新

6. 事件处理与报警

6.1 事件检测

7. 接口与集成

7.1 提供 RESTful API

7.1.1 获取所有设备数据

8. 日志与监控

8.1 日志记录

8.2 系统监控

9. 用户管理

9.1 用户角色和权限管理

9.1.1 修改用户模型

9.1.2 修改用户注册接口

9.2 权限控制

9.2.1 创建中间件进行权限检查

9.2.2 修改删除设备接口

9.3 审计和跟踪

9.3.1 修改用户注册接口

9.3.2 修改用户登录接口


物联网(IoT)正在以惊人的速度发展,越来越多的设备连接到互联网。搭建一个物联网服务器是实现设备管理、数据处理和安全性的关键。本文将详细介绍搭建物联网服务器的过程,包括所需的步骤、代码示例和相关工具。

1. 环境准备

在开始搭建物联网服务器之前,确保你的开发环境已准备好。

1.1 硬件要求

  • 服务器:可以使用物理服务器或云服务器(如AWS、Azure、阿里云)。
  • 开发设备:可以是树莓派、Arduino等物联网设备。

1.2 软件要求

  • 操作系统:Ubuntu 20.04或更高版本。
  • 数据库:MySQL或MongoDB。
  • 编程语言:Node.js、Python或Java。
  • 工具:Docker(可选)、Postman(用于测试API)。

2. 搭建步骤

2.1 安装必要的软件

首先,更新系统并安装Node.js和MongoDB。

# 更新系统
sudo apt update && sudo apt upgrade -y

# 安装 Node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs

# 安装 MongoDB
sudo apt install -y mongodb

2.2 创建项目目录

创建一个项目目录并初始化Node.js应用。

# 创建项目目录
mkdir IoTServer
cd IoTServer

# 初始化Node.js项目
npm init -y

2.3 安装依赖包

安装必要的依赖包,如Express.js(用于构建API)和Mongoose(用于MongoDB交互)。

npm install express mongoose body-parser cors

2.4 编写服务器代码

创建一个名为server.js的文件,并编写基础的Express.js服务器代码。

// server.js
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 3000;

// 连接到MongoDB数据库
mongoose.connect('mongodb://localhost:27017/iot', {
    useNewUrlParser: true,
    useUnifiedTopology: true
}).then(() => {
    console.log('MongoDB connected');
}).catch(err => {
    console.error('MongoDB connection error:', err);
});

// 中间件
app.use(cors());
app.use(bodyParser.json());

// 设备模型
const DeviceSchema = new mongoose.Schema({
    name: String,
    type: String,
    status: String
});
const Device = mongoose.model('Device', DeviceSchema);

// 设备注册接口
app.post('/api/devices', async (req, res) => {
    const device = new Device(req.body);
    await device.save();
    res.status(201).send(device);
});

// 启动服务器
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

2.5 启动服务器

运行服务器代码,确保没有错误。

node server.js

你应该会看到“Server is running on http://localhost:3000”的消息。

2.6 测试设备注册接口

使用Postman或curl测试设备注册接口。

Postman配置

  • 方法:POST
  • URLhttp://localhost:3000/api/devices
  • Body(JSON格式):
{
    "name": "温度传感器",
    "type": "传感器",
    "status": "在线"
}

点击“Send”,如果一切正常,你将收到新设备的响应。

3. 数据处理与存储

3.1 数据存储

在上述代码中,我们已经使用MongoDB存储设备数据。你可以根据需求添加更多数据处理的功能。

3.2 数据实时处理

为了实现实时数据处理功能,我们将使用 Socket.IO 来实现设备与服务器之间的双向通信。这允许设备在数据发生变化时立即将数据发送到服务器,服务器也可以实时向设备推送消息。

3.2.1 安装 Socket.IO

如果你还没有安装 Socket.IO,可以通过以下命令进行安装:

npm install socket.io

3.2.2 修改服务器代码

在 server.js 文件中,我们已经导入了 Socket.IO。接下来,我们需要修改服务器代码以支持实时数据传输。以下是更新后的代码:

const http = require('http');
const socketIo = require('socket.io');

// 创建 HTTP 服务器
const server = http.createServer(app);
const io = socketIo(server);

// 监听连接
io.on('connection', (socket) => {
    console.log('A device connected:', socket.id);
    
    // 监听来自设备的数据
    socket.on('data', async (data) => {
        console.log('Received data:', data);
        
        // 在这里可以进行数据处理或存储
        // 例如:将数据存入数据库
        const deviceData = new Device(data);
        await deviceData.save();
        
        // 向所有连接的客户端广播接收到的数据
        io.emit('data', deviceData);
    });

    // 监听断开连接
    socket.on('disconnect', () => {
        console.log('Device disconnected:', socket.id);
    });
});

// 启动服务器
server.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

3.2.3 客户端代码示例

为了让设备能够发送数据并接收实时更新,我们需要在设备端实现Socket.IO客户端。以下是一个简单的客户端示例代码(假设设备使用 Node.js):

// device.js
const io = require('socket.io-client');
const socket = io('http://localhost:3000');

// 模拟设备数据
setInterval(() => {
    const deviceData = {
        name: '温度传感器',
        type: '传感器',
        status: '在线',
        value: Math.random() * 100 // 随机生成温度值
    };
    
    // 发送数据到服务器
    socket.emit('data', deviceData);
}, 5000); // 每5秒发送一次数据

// 监听服务器广播的数据
socket.on('data', (data) => {
    console.log('Data received from server:', data);
});

3.2.4 启动设备客户端

在设备端运行上述代码,以便模拟设备向服务器发送数据。你可以在多个终端中运行多个设备客户端,以模拟多个设备同时连接。

node device.js

这将每5秒随机发送一次温度数据到服务器,并在接收到服务器的广播消息时输出信息。

4. 安全性

安全性是物联网系统非常重要的一部分。为了确保系统的安全性,我们可以实现身份验证、加密通信和定期更新。

4.1 身份验证与授权

为确保只有授权设备能够连接到服务器,可以使用 JWT(JSON Web Token)进行身份验证。以下是如何实现基本的 JWT 身份验证。

首先,安装必要的库:

npm install jsonwebtoken bcryptjs

然后,在 server.js 中添加以下代码:

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

const SECRET_KEY = 'your_secret_key'; // 应该存储在环境变量中

// 用户模型(示例)
const UserSchema = new mongoose.Schema({
    username: String,
    password: String
});
const User = mongoose.model('User', UserSchema);

// 用户注册接口
app.post('/api/register', async (req, res) => {
    const { username, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    const user = new User({ username, password: hashedPassword });
    await user.save();
    res.status(201).send('User registered');
});

// 用户登录接口
app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;
    const user = await User.findOne({ username });
    if (!user || !await bcrypt.compare(password, user.password)) {
        return res.status(401).send('Invalid credentials');
    }
    const token = jwt.sign({ id: user._id }, SECRET_KEY, { expiresIn: '1h' });
    res.status(200).json({ token });
});

// 中间件:验证JWT
const authenticateJWT = (req, res, next) => {
    const token = req.headers['authorization'];
    if (token) {
        jwt.verify(token, SECRET_KEY, (err, user) => {
            if (err) {
                return res.sendStatus(403);
            }
            req.user = user;
            next();
        });
    } else {
        res.sendStatus(401);
    }
};

// 保护的设备注册接口
app.post('/api/devices', authenticateJWT, async (req, res) => {
    const device = new Device(req.body);
    await device.save();
    res.status(201).send(device);
});

4.2 加密通信

为了确保数据在传输过程中的安全性,我们将使用 HTTPS。以下是如何设置 HTTPS 的步骤。

首先,您需要 SSL 证书。可以使用自签名证书来进行开发和测试,或者使用 Let's Encrypt 获取免费的证书。

4.2.1 生成自签名证书(开发环境)
# 生成 SSL 证书和密钥
openssl req -nodes -new -x509 -keyout server.key -out server.cert
4.2.2 修改服务器以支持 HTTPS

在 server.js 中,我们将使用HTTPS模块来创建服务器。

const https = require('https');
const fs = require('fs');

// 读取证书和密钥
const privateKey = fs.readFileSync('server.key', 'utf8');
const certificate = fs.readFileSync('server.cert', 'utf8');

const credentials = { key: privateKey, cert: certificate };

// 创建 HTTPS 服务器
const httpsServer = https.createServer(credentials, app);

// 启动 HTTPS 服务器
httpsServer.listen(PORT, () => {
    console.log(`Server is running on https://localhost:${PORT}`);
});

5. 数据分析和可视化

在物联网应用中,数据分析和可视化可以帮助用户理解和利用数据。我们可以使用第三方库或工具来实现数据分析和可视化,以下是一些常用的工具:

  • Grafana:用于监控和可视化时序数据。
  • Chart.js:在前端展示实时数据图表。
  • D3.js:用于复杂的数据可视化。
5.1 集成 Grafana

Grafana 是一个开源的分析和监控平台,可以通过连接到数据库(如 MongoDB)来可视化数据。

  1. 安装 Grafana

    sudo apt install -y grafana
    
  2. 启动 Grafana

    sudo systemctl start grafana-server
    sudo systemctl enable grafana-server
    
  3. 访问 Grafana:在浏览器中访问 http://localhost:3000,默认用户名和密码均为 admin

  4. 添加数据源:在 Grafana 中添加 MongoDB 数据源,并配置它以连接到你的 MongoDB 实例。

  5. 创建仪表盘:通过查询 MongoDB 数据,创建可视化面板,展示传感器数据。

5.2 使用 Chart.js

如果你希望在前端应用中可视化数据,可以使用 Chart.js。以下是一个简单的示例,展示如何使用 Chart.js 来展示实时数据。

在前端应用中,我们将创建一个实时更新的图表,展示从服务器接收到的温度数据。

5.2.1 创建HTML结构

在你的HTML文件中,添加一个<canvas>元素来显示图表。以下是一个完整的HTML示例:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>实时温度数据可视化</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <h1>实时温度数据</h1>
    <canvas id="myChart" width="400" height="200"></canvas>
    
    <script src="/socket.io/socket.io.js"></script>
    <script>
        const socket = io('http://localhost:3000'); // 连接到服务器
        const ctx = document.getElementById('myChart').getContext('2d');
        
        const labels = []; // 存储时间标签
        const dataPoints = []; // 存储实时数据

        const myChart = new Chart(ctx, {
            type: 'line',
            data: {
                labels: labels,
                datasets: [{
                    label: '温度数据',
                    data: dataPoints,
                    borderColor: 'rgba(75, 192, 192, 1)',
                    borderWidth: 1,
                    fill: false
                }]
            },
            options: {
                scales: {
                    x: {
                        type: 'linear',
                        position: 'bottom'
                    }
                }
            }
        });

        // 监听服务器发送的数据
        socket.on('data', (data) => {
            // 假设数据格式为 { value: 温度值, timestamp: 时间戳 }
            const timestamp = new Date().toLocaleTimeString();
            labels.push(timestamp);
            dataPoints.push(data.value);

            // 更新图表
            myChart.update();
        });
    </script>
</body>
</html>

5.2.2 图表数据更新

这个示例中,我们使用 Socket.IO 实时接收数据,并将其添加到图表中。我们假设服务器发送的数据的格式如下:

{
    "value": 25.5 // 温度值
}

在客户端代码中,我们通过监听 socket.on('data', ...) 来接收服务器发送的实时数据,并将数据点和时间标签添加到 Chart.js 图表的数据集中。调用 myChart.update() 方法来更新图表。

6. 事件处理与报警

为了确保系统能够及时处理异常事件并进行报警,我们可以实现一个简单的事件处理系统。

6.1 事件检测

在服务端,我们可以设置一个阈值,并在接收到数据后检查数据是否超出这个阈值。

// 设定温度阈值
const TEMPERATURE_THRESHOLD = 75;

// 监听来自设备的数据
socket.on('data', async (data) => {
    console.log('Received data:', data);

    // 检查温度是否超出阈值
    if (data.value > TEMPERATURE_THRESHOLD) {
        console.log('Warning: Temperature exceeds threshold!', data.value);
        // 发送报警通知
        io.emit('alert', { message: 'Temperature exceeds threshold!', value: data.value });
    }

    // 存储数据
    const deviceData = new Device(data);
    await deviceData.save();

    // 向所有连接的客户端广播接收到的数据
    io.emit('data', deviceData);
});
6.2 报警系统

在客户端,我们也可以监听 alert 事件,并根据需要做出响应。

// 监听报警事件
socket.on('alert', (alert) => {
    alertUser(alert.message, alert.value);
});

// 简单的报警通知函数
function alertUser(message, value) {
    alert(`报警: ${message} 当前温度: ${value}`);
}

7. 接口与集成

为了让第三方系统能够集成到我们的物联网平台,我们需要提供丰富的 API 接口。

7.1 提供 RESTful API

我们已经实现了一些基础的 API 接口,接下来我们将添加获取所有设备、更新设备状态和删除设备的接口。

7.1.1 获取所有设备数据

添加一个 API 接口以获取所有已注册设备的数据。我们将在 server.js 中添加如下代码:

// 获取所有设备数据
app.get('/api/devices', authenticateJWT, async (req, res) => {
    try {
        const devices = await Device.find();
        res.status(200).json(devices);
    } catch (error) {
        res.status(500).json({ message: 'Error fetching devices', error });
    }
});
7.1.2 更新设备状态

添加一个 API 接口以更新特定设备的状态。我们将使用设备的 ID 作为参数:

// 更新设备状态
app.put('/api/devices/:id', authenticateJWT, async (req, res) => {
    const { id } = req.params;
    const { status } = req.body;

    try {
        const device = await Device.findByIdAndUpdate(id, { status }, { new: true });
        if (!device) {
            return res.status(404).json({ message: 'Device not found' });
        }
        res.status(200).json(device);
    } catch (error) {
        res.status(500).json({ message: 'Error updating device', error });
    }
});
7.1.3 删除设备

添加一个 API 接口以删除特定设备。我们同样使用设备的 ID 作为参数:

// 删除设备
app.delete('/api/devices/:id', authenticateJWT, async (req, res) => {
    const { id } = req.params;

    try {
        const device = await Device.findByIdAndDelete(id);
        if (!device) {
            return res.status(404).json({ message: 'Device not found' });
        }
        res.status(204).send(); // 返回 204 No Content
    } catch (error) {
        res.status(500).json({ message: 'Error deleting device', error });
    }
});

8. 日志与监控

为了确保系统的稳定性和安全性,我们需要实现日志记录和系统监控。可以使用 morgan 中间件来记录 HTTP 请求,并使用其他工具来监控系统性能。

8.1 日志记录

首先,安装 morgan

npm install morgan

然后在 server.js 中添加以下代码,以便记录每个请求的信息:

const morgan = require('morgan');

// 使用 morgan 记录 HTTP 请求日志
app.use(morgan('combined'));

这将记录所有请求的详细信息,包括请求方法、URL、状态码和请求时间。

8.2 系统监控

对于系统监控,可以使用工具如 Prometheus 和 Grafana 进行监控。通过收集和可视化系统指标(如 CPU 使用率、内存使用量等),您可以更好地理解和管理系统的性能。

# 安装 Prometheus
sudo apt install prometheus

然后配置 Prometheus 以监控您的 Node.js 应用。可以使用 prom-client 库在应用中导出指标。

npm install prom-client

在 server.js 中添加以下代码:

const client = require('prom-client');

// 创建一个 Registry 用于保存指标
const register = new client.Registry();

// 创建一个 Gauge 指标来监控请求数量
const httpRequestDurationMicroseconds = new client.Histogram({
    name: 'http_request_duration_seconds',
    help: 'Duration of HTTP requests in seconds',
    labelNames: ['method', 'route', 'code'],
    registers: [register],
});

// 中间件记录请求时间
app.use((req, res, next) => {
    const end = httpRequestDurationMicroseconds.startTimer();
    res.on('finish', () => {
        end({ method: req.method, route: req.path, code: res.statusCode });
    });
    next();
});

// 提供一个端点用于导出指标
app.get('/metrics', async (req, res) => {
    res.set('Content-Type', register.contentType);
    res.end(await register.metrics());
});

9. 用户管理

用户管理是物联网系统的重要组成部分,确保只有授权用户可以访问系统的特定功能。我们将实现用户角色和权限管理,并记录用户操作以便审计和跟踪。

9.1 用户角色和权限管理

在我们的物联网系统中,用户可以具有不同的角色(例如,管理员、普通用户),并且每个角色可以拥有不同的权限。我们可以通过在用户模型中添加角色字段来实现这一点。

9.1.1 修改用户模型

首先,修改我们的用户模型以支持角色:

const UserSchema = new mongoose.Schema({
    username: String,
    password: String,
    role: {
        type: String,
        enum: ['admin', 'user'],
        default: 'user'
    }
});
9.1.2 修改用户注册接口

确保在用户注册时能够指定角色(可选):

// 用户注册接口
app.post('/api/register', async (req, res) => {
    const { username, password, role } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    const user = new User({ username, password: hashedPassword, role });
    await user.save();
    res.status(201).send('User registered');
});
9.2 权限控制

我们需要在 API 中实施权限控制,以确保只有特定角色的用户能够访问某些功能。例如,只有管理员可以删除设备。

9.2.1 创建中间件进行权限检查

我们可以创建一个中间件来检查用户的角色:

// 权限检查中间件
const authorizeRoles = (...allowedRoles) => {
    return (req, res, next) => {
        const { role } = req.user; // 从请求中获取用户角色
        if (!allowedRoles.includes(role)) {
            return res.status(403).json({ message: 'Access forbidden: insufficient rights' });
        }
        next();
    };
};
9.2.2 修改删除设备接口

使用角色检查中间件来保护删除设备的接口:

// 删除设备
app.delete('/api/devices/:id', authenticateJWT, authorizeRoles('admin'), async (req, res) => {
    const { id } = req.params;

    try {
        const device = await Device.findByIdAndDelete(id);
        if (!device) {
            return res.status(404).json({ message: 'Device not found' });
        }
        res.status(204).send(); // 返回 204 No Content
    } catch (error) {
        res.status(500).json({ message: 'Error deleting device', error });
    }
});

9.3 审计和跟踪

为了记录用户的操作,我们可以在每个敏感操作的 API 接口中添加日志记录。

9.3.1 修改用户注册接口

在用户注册接口中,我们已经记录了日志。以下是完整的用户注册接口代码:

// 用户注册接口
app.post('/api/register', async (req, res) => {
    const { username, password, role } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    const user = new User({ username, password: hashedPassword, role });
    await user.save();
    
    // 记录日志
    const log = new Log({ userId: user._id, action: `Registered user ${username}` });
    await log.save();

    res.status(201).send('User registered');
});
9.3.2 修改用户登录接口

在用户登录接口中,我们可以记录用户的登录操作:

// 用户登录接口
app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;
    const user = await User.findOne({ username });
    if (!user || !await bcrypt.compare(password, user.password)) {
        return res.status(401).send('Invalid credentials');
    }
    const token = jwt.sign({ id: user._id, role: user.role }, SECRET_KEY, { expiresIn: '1h' });

    // 记录登录日志
    const log = new Log({ userId: user._id, action: `User ${username} logged in` });
    await log.save();

    res.status(200).json({ token });
});
9.3.3 修改更新设备状态接口

在设备状态更新接口中记录日志:

// 更新设备状态
app.put('/api/devices/:id', authenticateJWT, async (req, res) => {
    const { id } = req.params;
    const { status } = req.body;

    try {
        const device = await Device.findByIdAndUpdate(id, { status }, { new: true });
        if (!device) {
            return res.status(404).json({ message: 'Device not found' });
        }

        // 记录日志
        const log = new Log({ userId: req.user.id, action: `Updated device ${id} status to ${status}` });
        await log.save();

        res.status(200).json(device);
    } catch (error) {
        res.status(500).json({ message: 'Error updating device', error });
    }
});
9.3.4 修改获取所有设备数据接口

在获取所有设备数据的接口中记录日志:

// 获取所有设备数据
app.get('/api/devices', authenticateJWT, async (req, res) => {
    try {
        const devices = await Device.find();
        
        // 记录日志
        const log = new Log({ userId: req.user.id, action: `Fetched all devices` });
        await log.save();

        res.status(200).json(devices);
    } catch (error) {
        res.status(500).json({ message: 'Error fetching devices', error });
    }
});

9.4 查看操作日志

为了方便审计和追踪,您可能需要一个接口来查看操作日志。我们可以添加一个新的 API 接口来获取所有日志记录:

// 获取所有日志记录
app.get('/api/logs', authenticateJWT, authorizeRoles('admin'), async (req, res) => {
    try {
        const logs = await Log.find().populate('userId', 'username'); // Populate userId to get username
        res.status(200).json(logs);
    } catch (error) {
        res.status(500).json({ message: 'Error fetching logs', error });
    }
});

10. 结论

通过上述步骤,我们已经搭建了一个完整的物联网服务器,涵盖了以下关键模块:

  • 设备管理:实现了设备的注册、更新、删除和查询功能,支持大规模设备的管理。这为物联网应用提供了基础设施,使得各种设备可以被有效地管理和控制。

  • 数据处理与存储:通过使用 MongoDB 等数据库高效存储设备数据,并实现实时数据处理功能,使得系统能够及时响应设备状态变化,支持边缘计算和云端计算的结合。

  • 安全性:使用 JWT 实现身份验证和权限管理,确保只有授权用户和设备能够访问系统,降低了安全隐患。此外,通过 HTTPS 加密通信,保护了数据在传输过程中的安全。

  • 可扩展性与高可用性:设计了可横向扩展的架构,保证随着设备数量的增加,系统能够稳定运行。同时,通过负载均衡和容错机制提高了系统的可用性。

  • 数据分析与可视化:集成了 Grafana 和 Chart.js 等工具,提供了丰富的数据可视化功能,使用户能够实时监控和分析数据,帮助用户做出更好的决策。

  • 事件处理与报警:实现了实时事件检测和报警系统,确保用户能够及时获知异常情况,提高了系统的可靠性。

  • 接口与集成:提供了丰富的 RESTful API 接口,使得第三方系统能够方便地与物联网平台进行集成,增强了系统的互操作性。

  • 日志与监控:通过记录用户操作日志和系统性能指标,提供了审计和监控功能,有助于系统的维护和安全审查。

  • 用户管理:实现了用户角色和权限管理,确保系统的安全性和可控性,并通过日志系统实现了用户操作的审计和跟踪。

10.1 参考资料

在搭建物联网服务器的过程中,可以参考以下资料和文档:


非常感谢您阅读到这里!您的关注和支持是我不断前进的动力。搭建物联网服务器是一个充满挑战的旅程,但通过本指南,希望您能掌握关键步骤,构建出功能强大的物联网平台

——by 极客小张

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客小张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值