Build Node.Js web server in Docker containers: nodejs+pm2+mongodb+redis

本文详细介绍了如何在Docker环境下搭建Node.js web服务器,包括使用pm2进行进程管理,结合mongodb和redis实现数据存储。内容涵盖了从创建Node.js镜像,配置pm2日志,到建立mongodb和redis容器的全过程,并提供了具体的Dockerfile和操作步骤。
摘要由CSDN通过智能技术生成

Build Node.Js web server in Docker containers: nodejs+pm2+mongodb+redis

环境

  • 系统环境 centos 6.5 64bit
  • docker v1.7.1

下载使用镜像

[root@dss ~]# docker search node # 搜素镜像
NAME                      DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
node                      Node.js is a JavaScript-based platform for...   2462      [OK]       
strongloop/node           StrongLoop, Node.js, and tools.                 30                   [OK]
bitnami/node              Bitnami Node.js Docker Image                    16                   [OK]
siomiz/node-opencv        _/node + node-opencv                            8                    [OK]
dahlb/alpine-node         small node for gitlab ci runner                 7                    [OK]
tatsushid/tinycore-node   A small but a fully functional Node.js run...   6                    [OK]
cusspvz/node               Super small Node.js container (~20MB)...      4                    [OK]
azukiapp/node             Docker image to run Node.js by Azuki - htt...   4                    [OK]
tutum/node                Run a Tutum node inside a container             3                    [OK]
anigeo/node-forever       Daily build node.js with forever                3                    [OK]
redjack/node              Node + Nave                                     1                    [OK]
xataz/node                very light node image                           1                    [OK]
joxit/node                Slim node docker with some utils for dev        1                    [OK]
centralping/node          Bare bones CentOS 7 NodeJS container.           1                    [OK]
urbanmassage/node         Some handy (read, better) docker node images    1                    [OK]
tectoro/node-compass      Node JS minimal version with compass and b...   1                    [OK]
starefossen/ruby-node     Docker Image with Ruby and Node.js installed    1                    [OK]
atomiq/node               Use as base image for new Node.js images.       1                    [OK]
robbertkl/node            Docker container running Node.js                0                    [OK]
lynxtp/node               https://github.com/lynxtp/docker-node           0                    [OK]
codexsystems/node         Node.js for Development and Production          0                    [OK]
instructure/node          Instructure node images                         0                    [OK]
stylisted/node            Stylisted's base Docker node image with gr...   0                    [OK]
maxird/node               Node                                            0                    [OK]
c4tech/node               NodeJS images, aimed at generated single-p...   0                    [OK]

安装 Node.Js 官方镜像 [root@dss ~]# yum pull node

查看已安装镜像

[root@dss workspace]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
node                latest              47abb70784d6        8 days ago          660.6 MB

基于 Node.Js 镜像创建服务镜像

新建目录 src, 在src目录下新建 package.json:

{
    "name": "docker-node-test",
    "private": true,
    "version": "0.0.1",
    "description": "Node.js test app on node docker image",
    "author": "Shaoshuai Dong <dongsoso@hotmail.com>",
    "dependencies": {
        "express": "^4.13.4"                                
    }
}

在src目录下新建index.js:

 const express = require('express');

 // Creates an Express application
 let app = express();

 // Assigns settings
 app.set('port', 8080);

 // Routes HTTP GET requests 
 app.get('/', (req, res) => {
     res.send('Hello world --- from nodejs docker image\n');
 });                                                          

 // Binds and listens
 app.listen(app.get('port'), () => {
     console.info("Listen port %s for app", app.get("port"));

在src同级目录下新建文件 Dockerfile:

From node:6.3

ADD src/ /src

RUN cd /src; npm install

EXPOSE  8080

CMD ["node", "/src/index.js"]

创建镜像 docker build --tag="node/app-test" ./, docker build --help 查看详细参数

创建成功会提示 Successfully built 2199b2aa9cd1

查看创建的镜像

[root@dss test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
node/app-test       latest              2199b2aa9cd1        7 seconds ago       663.6 MB
node                6.3                 47abb70784d6        8 days ago          660.6 MB
node                latest              47abb70784d6        8 days ago          660.6 MB

运行刚刚创建的镜像, docker run --help 查看详细参数:

[root@dss test]# docker run -d -p 8081:8080 node/app-test 
873ac198368799f9bf56262e097f55e66241f47cedd336349236b43e9001539d

查看运行中的容器,是否存在:

[root@dss test]# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                    NAMES
873ac1983687        node/app-test       "node /src/index.js"   53 seconds ago      Up 52 seconds       0.0.0.0:8081->8080/tcp   desperate_swartz 

测试 Node.Js 服务:

[root@dss ~]# curl localhost:8081 -i
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 41
ETag: W/"29-onjHxbOtTbPH0+vlfe2YtA"
Date: Fri, 15 Jul 2016 10:49:54 GMT
Connection: keep-alive

Hello world --- from nodejs docker image

基于 Node.Js 镜像使用 pm2

使用 pm2

创建 Dockerfile:

From node:6.3

RUN npm install pm2 -g --registry=https://registry.npm.taobao.org
RUN mkdir -p /usr/src/node-app
WORKDIR /usr/src/node-app

COPY src/. /usr/src/node-app/

RUN npm install --registry=https://registry.npm.taobao.org

EXPOSE  8080

CMD ["npm", "start"]                                             

先看看目录结构吧

[root@dss workspace]# tree test/
test/
├── Dockerfile
└── src
    ├── index.js
    ├── package.json
    └── pm2.json

package.json:

{                                                           
    "name": "docker-node-test",                             
    "private": true,                                        
    "version": "0.0.1",                                     
    "description": "Node.js test app on node docker image", 
    "author": "Shaoshuai Dong <dongsoso@hotmail.com>",      
    "scripts": {                                            
        "start": "pm2 startOrGracefulReload ./pm2.json --no-daemon"     
    },                                                      
    "dependencies": {                                       
        "express": "^4.13.4"                                
    }                                                       
}                                                           

index.js

const express = require('express');

// Creates an Express application
let app = express();

// Assigns settings
app.set('port', 8080);

// Routes HTTP GET requests                                       
app.get('/', (req, res) => {
    res.send('Hello world --- from nodejs docker image\n');
});

// Binds and listens
app.listen(app.get('port'), () => {
    console.info("Listen port %s for app", app.get("port"));
});

pm2.json:

{                                         
    "apps": [
        {
            "name": "app-test",
            "script"      : "index.js",
            "args"        : [],
            "watch"       : false,
            "merge_logs"  : true,
            "max_memory_restart": "100M",
            "env": {
                "NODE_ENV": "local"
            }
        }
    ]
}

创建镜像 docker build --tag="node/app-test" ./

查看创建的镜像 docker images:

[root@dss test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
node/app-test       latest              1a3325c28c8c        14 minutes ago      678 MB
node                latest              47abb70784d6        10 days ago         660.6 MB
node                6.3                 47abb70784d6        10 days ago         660.6 MB

运行 docker run -d -p 8081:8080 node/app-test

查看运行中的容器

[root@dss test]# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                    NAMES
9e49f7c5270a        node/app-test       "pm2 start index.js    7 minutes ago       Up 7 minutes        0.0.0.0:8081->8080/tcp   gloomy_sammet

测试

[root@dss test]# curl -i localhost:8081
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 41
ETag: W/"29-onjHxbOtTbPH0+vlfe2YtA"
Date: Sun, 17 Jul 2016 09:12:48 GMT
Connection: keep-alive

Hello world --- from nodejs docker image

在容器内运行命令(docker exec --help 查看更多用法)

[root@dss test]# docker exec -i 9e49f7c5270a pm2 list
┌──────────┬────┬──────┬─────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory      │ watching │
├──────────┼────┼──────┼─────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ app-test │ 0fork17  │ online │ 09m23.730 MB   │ disabled │
└──────────┴────┴──────┴─────┴────────┴─────────┴────────┴─────────────┴──────────┘

将 pm2、log4js 日志记录到宿主机文件

这里介绍将 pm2 日志和程序日志记录在宿主机文件之中, 目标是:

  • pm2 out logs => /data/logs/app-test-out.log
  • pm2 error logs => /data/logs/app-test-err.log
  • logs by log4js => /data/logs/hello.log
./
├── Dockerfile
└── src
    ├── index.js
    ├── package.json
    └── pm2.json

Dockerfile

From node:6.3

RUN npm install pm2 -g --registry=https://registry.npm.taobao.org
RUN mkdir -p /usr/src/node-app
WORKDIR /usr/src/node-app

COPY src/. /usr/src/node-app/

RUN npm install --registry=https://registry.npm.taobao.org

EXPOSE  8080

CMD ["npm", "start"]

index.js

const express = require('express');
const log4js = require('log4js');

// Creates an Express application
let app = express();

// Init logger
log4js.configure({
    appenders: [{
        type: 'dateFile',
        pattern: '-yyyyMMddhh.log',
        filename: '/data/logs/hello.log',
        category: 'hello'
    }],
    levels: {
        action: 'INFO',                                            
        alarm_api: 'INFO'
    }
});

global.logger = log4js.getLogger('hello');

// Assigns settings
app.set('port', 8080);

// Routes HTTP GET requests 
app.get('/', (req, res) => {
    console.info('On request: hello world');
    global.logger.info(req.method, req.path);
    res.send('Hello world --- from nodejs docker image\n');
});

// Binds and listens
app.listen(app.get('port'), () => {
    console.info('Listen port %s for app', app.get('port'));
});

package.json

{
    "name": "docker-node-test",
    "private": true,
    "version": "0.0.1",
    "description": "Node.js test app on node docker image",
    "author": "Shaoshuai Dong <dongsoso@hotmail.com>",
    "scripts": {
        "start": "pm2 startOrGracefulReload ./pm2.json --no-daemon"
    },
    "dependencies": {
        "express": "^4.13.4",
        "log4js": "^0.6.33"                                        
    }
}

pm2.json

{
    "apps": [
        {
            "name": "app-test",
            "script": "index.js",
            "args": [],
            "watch": false,
            "merge_logs": true,
            "error_file": "/data/logs/app-test-err.log",
            "out_file": "/data/logs/app-test-out.log",   
            "log_date_format": "YYYY-MM-DD HH:mm:ss.SSS",
            "max_memory_restart": "100M",
            "env": {
                "NODE_ENV": "local"                          
            }
        }
    ]
}

创建镜像 docker build --tag="node/app-test:0.1" ./

运行容器 docker run -p 8081:8080 -v /data/logs:/data/logs --name hello node/app-test:0.1

使用 -v 参数在容器上挂载宿主机上的指定目录

查看容器和测试:

[root@dss test]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
63eb2a3aea8b        node/app-test:0.1   "npm start"         17 seconds ago      Up 16 seconds       0.0.0.0:8081->8080/tcp   hello
[root@dss test]# curl localhost:8081
Hello world --- from nodejs docker image
[root@dss test]# 

在宿主机上查看日志

[root@dss test]# ls /data/logs/
app-test-err.log app-test-out.log hello.log
[root@dss test]# cat /data/logs/app-test-out.log 
2016-07-18 05:56:56.406: Listen port 8080 for app
2016-07-18 05:57:21.289: On request: hello world
[root@dss test]# cat /data/logs/hello.log 
[2016-07-18 05:57:21.290] [INFO] hello - GET /

命令 docker inspect hello 可以看到容器的 volume 配置

...
 "Volumes": {
     "/data/logs": "/data/logs"
 },
 "VolumesRW": {
     "/data/logs": true
 },
...

Data volumes 有如下特性:

  • 当容器被创建时 volumes 会被初始化
  • Data volumes 可以在容器间共享复用
  • 对 data volume 的修改直接生效
  • 更新镜像时, 原来 data volume 的修改不会被影响
  • 即使容器被删除, 对应的 data volumes 依然会存在

使用 Volume 可以将容器与容器产生的数据分离,容器产生的数据可以持久化。

Docker volumes 使用 -v 指定和 在 Dockerfile 指定 VOLUME 的区别:

  • -v /data/logs:/data/logs: 将宿主机的 /data/logs 目录挂载到容器的 /data/logs 目录(如果目录不存在会被创建), 宿主机和容器共享该目录,二者对该目录下的修改相互受影响。
  • Dockerfile 指定 VOLUME /data/logs: 在宿主机创建一个目录(默认是 /var/lib/docker/volumes/)并挂载到容器的 /data/logs 目录(如果目录不存在会被创建), 宿主机和容> 器共享该目录,二者对该目录下的修改相互受影响。
  • -v /data/logs: 同上, 在宿主机创建一个目录(默认是 /var/lib/docker/volumes/)并挂载到容器的 /data/logs 目录(如果目录不存在会被创建), 宿主机和容器共享该目录,二者对该目录下的修改相互受影响。

Docker volumes 默认是 read-write 模式,也可以设置为 read-only 模式。

应用实践 nodejs&mongodb&redis

目标

  • container: nodejs
    • pm2
    • logs on host
  • container: mongodb
  • container: redis

基于 centos 镜像创建 mongodb 镜像

新建 Dockerfile

# Mongodb image from centos:6
# run -v /data/db:/data/db -p 27017:27017

From centos:6
MAINTAINER Shaoshuai Dong <dongsoso@hotmail.com>

# install mongodb v3.2
COPY mongodb.repo /etc/yum.repos.d/

RUN yum install -y mongodb-org

# volume
VOLUME ["/data/db"]

# expose port
EXPOSE 27017

# command
CMD ["mongod"]                                      

mongodb.repo

[MongoDB]
name=MongoDB Repository
baseurl=http://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.2/x86_64/
gpgcheck=0
enabled=1                                                                      

创建镜像 docker build --tag="centos/mongo:0.1" ./
运行容器 docker run -d -p 27017:27017 -v /data/db:/data/db --name mongo centos/mongo:0.1

查看容器运行状态

[root@dss mongodb]# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                              NAMES
c9ddd14992e6        centos/mongo:0.1    "mongod"               5 seconds ago       Up 4 seconds        0.0.0.0:27017->27017/tcp           mongo 

测试远程连接 mongodb

[dongshaoshuai~/test] ]$mongo --port 27017 --host x.x.x.x
^[[AMongoDB shell version: 3.0.6
connecting to: x.x.x.x:27017/test

也可以通过 Robomongo 远程连接(如果mongodb 版本 >= v3,Robomongo 的版本需 > v0.8.5)

基于 centos 镜像创建 redis 镜像

Dockerfile

From centos:6
MAINTAINER Shaoshuai Dong <dongsoso@hotmail.com>

# install redis v3.2.1
RUN yum install -y gcc-c++
RUN yum install -y tcl
RUN yum install -y wget
RUN wget http://download.redis.io/releases/redis-3.2.1.tar.gz
RUN tar -xzvf redis-3.2.1.tar.gz
RUN mv redis-3.2.1 /usr/local/redis
RUN cd /usr/local/redis; make; make install

COPY redis.conf /etc/redis.conf

EXPOSE 6379

CMD ["/usr/local/bin/redis-server", "/etc/redis.conf"]

redis.conf: wget http://download.redis.io/redis-stable/redis.confprotected-mode yes 改为 protected-mode no

创建镜像 docker build --tag="centos/redis:0.1" ./
运行容器 docker run -d -p 6379:6379 --name redis centos/redis:0.1

查看运行容器

[root@dss redis]# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                      NAMES
5aa2aa34764e        centos/redis:0.1    "/usr/local/bin/redi   6 seconds ago       Up 3 seconds        0.0.0.0:6379->6379/tcp     redis
c9ddd14992e6        centos/mongo:0.1    "mongod"               2 hours ago         Up 2 hours          0.0.0.0:27017->27017/tcp   mongo

基于 centos 镜像创建 nodejs 服务镜像

目录结构

├── app
│   ├── index.js
│   ├── package.json
│   └── pm2.json
└── Dockerfile

Dockerfile

# run -v /data/logs:/data/logs --link mongo:mongo                          

From centos:6
MAINTAINER Shaoshuai Dong <dongsoso@hotmail.com>

# install nodejs v6
RUN curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -
RUN yum -y install nodejs

# install pm2
RUN npm install pm2 -g --registry=https://registry.npm.taobao.org

# make dir
RUN mkdir -p /usr/src/myapp

# envs
ENV NODE_ENV=local

# copy files
COPY app/. /usr/src/myapp/

# work dir
WORKDIR /usr/src/myapp

# install dependencies
RUN npm install --registry=https://registry.npm.taobao.org

# expose port
EXPOSE  8080

# cmd
CMD ["npm", "start"]

package.json

{
    "name": "docker-node-test",
    "private": true,
    "version": "0.0.1",
    "description": "Node.js test app on node docker image",
    "author": "Shaoshuai Dong <dongsoso@hotmail.com>",
    "scripts": {
        "start": "pm2 startOrGracefulReload ./pm2.json --no-daemon"
    },
    "dependencies": {
        "express": "^4.13.4",
        "log4js": "^0.6.33",                                         
        "mongoose": "^4.0.8",
        "express-session": "^1.13.0",
        "connect-redis": "^3.1.0"
    }
}

pm2.json

{
    "apps": [
        {
            "name": "app",
            "script": "index.js",
            "args": [],
            "watch": false,
            "merge_logs": true,
            "error_file": "/data/logs/app-err.log",      
            "out_file": "/data/logs/app-out.log",
            "log_date_format": "YYYY-MM-DD HH:mm:ss.SSS",
            "max_memory_restart": "100M",
            "env": {
                "NODE_ENV": "local"
            }
        }
    ]
}

一个简单的服务,仅作为演示 index.js

const express = require('express');                                                  
const log4js = require('log4js');
const mongoose = require('mongoose');
const cookieParser = require("cookie-parser");
const session = require('express-session');
const redisStore = require('connect-redis')(session);

// Creates an Express application
let app = express();

// NODE_ENV
const ENV = app.get('env');

// Init logger
log4js.configure({
    appenders: [{
        type: 'dateFile',
        pattern: '-yyyyMMddhh.log',
        filename: '/data/logs/app.log',
        category: 'app'
    }],
    levels: {
        app: 'INFO',
    }
});

global.logger = log4js.getLogger('app');

// Assigns settings
app.set('port', 8080);

// Middlewares
app.use(cookieParser('tadf@a213!sdfa6'));
app.use(session({
    secret: 'tadf@a213!sdfa6',
    resave: false,
    saveUninitialized: false,
    store: new redisStore({
        logErrors: true,
        // host: 'redis', // Can be solved by docker-compose
        host: process.env.REDIS_PORT_6379_TCP_ADDR,
        port: 6379,
        ttl: 60 // expiration in 60s
    })
}));

// MongoDB connection: 'mongo' resolves to a docker container
let mongoUrl = 'mongodb://mongo:27017/test';
mongoose.connect(mongoUrl);
let Monkey = mongoose.model('Monkey', { name: String });

/**
 * Routes HTTP GET requests 
 */

app.all('*', (req, res, next) => {
    global.logger.info(req.method, req.path);

    if (req.path === '/login') {
        next();
        return;
    }

    if (!req.session || !req.session.isLoggedIn) {
        // TODO: redirect to login page
        res.send('Please login first');
        return;
    }
    next();
});

app.get('/login', (req, res) => {
    let userName = 'admin';
    let password = 'admin';

    if (req.query.name === userName && req.query.password === password) {
        req.session.isLoggedIn = true;
        res.json({
            RetCode: 0
        });
        return;
    }

    res.json({
        RetCode: 5,
        ErrMsg: 'Username or password wrong'
    });
});

app.get('/hello', (req, res) => {
    console.info('On request: hello world');
    res.send('Hello world --- from nodejs docker image\n'); 
});

app.get('/mongo/insert', (req, res) => {
    let newData = { name: req.query.name };
    let newMonkey = new Monkey(newData);

    newMonkey.save(err => {
        if (err) {
            global.logger.error('Insert data failed: ', err, newData); 
            res.json({
                RetCode: 100,
                ErrMsg: 'Insert data failed'
            });
            return;
        }

        global.logger.info('Insert data success:', newData);
        res.json({
            RetCode: 0
        });
    });
});

app.get('/mongo/find', (req, res) => {
    Monkey.find((err, monkeys) => {
        if (err) {
            global.logger.error('Find data failed: ', err); 
            res.json({
                RetCode: 200,
                ErrMsg: 'Find data failed'
            });
            return;
        }

        global.logger.info('Find data success:', monkeys);
        res.json({
            RetCode: 0,
            Data: monkeys
        });
    });
});

// Binds and listens
app.listen(app.get('port'), () => {
    console.info('Listen port %s for app', app.get('port'));
    console.info('NODE_ENV', ENV);
});                                                             

创建镜像 docker build --tag="centos/node-app:0.1" ./

运行容器 docker run -d -p 8181:8080 -v /data/logs:/data/logs --link mongo:mongo --link redis:redis --name app centos/node-app:0.1

查看服务状态

[root@dss app]# docker exec app pm2 list
┌──────────┬────┬──────┬─────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App nameid │ mode │ pid │ status │ restart │ uptime │ memory      │ watching │
├──────────┼────┼──────┼─────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ app      │ 0  │ fork │ 25  │ online │ 021s    │ 47.879 MB   │ disabled │
└──────────┴────┴──────┴─────┴────────┴─────────┴────────┴─────────────┴──────────┘
 Use `pm2 show <id|name>` to get more details about an app

测试

未登录, 在浏览器访问 yourhost:8181/hello

Please log in first

登录, yourhost:8181/login?name=admin&password=admin

{
    "RetCode": 0
}

登陆后,测试 yourhost:8181/hello

Hello world --- from nodejs docker image

测试 yourhost:8181/mongo/find

{"RetCode":0,"Data":[{"_id":"578ded8321be5919003c0dd8","name":"mmmm","__v":0},{"_id":"578df6f638a6a11b008a9bd0","name":"mmmmmm","__v":0}]}

查看日志

[root@dss logs]# cat /data/logs/app.log 
[2016-07-20 02:31:40.097] [INFO] app - GET /hello
[2016-07-20 02:31:45.088] [INFO] app - GET /login
[2016-07-20 02:32:12.375] [INFO] app - GET /hello
[2016-07-20 02:32:16.476] [INFO] app - GET /mongo/find
[2016-07-20 02:32:16.477] [INFO] app - Find data success: [ { _id: 578ded8321be5919003c0dd8, name: 'mmmm', __v: 0 },
  { _id: 578df6f638a6a11b008a9bd0, name: 'mmmmmm', __v: 0 } ]

其他

修改镜像、容器存储目录

docker 镜像、容器默认存储在 /var/lib/docker,对于云主机通常系统盘不大,最好存储在数据盘。

通过 --graph 参数可以指定存储位置。修改 /etc/sysconfig/docker 文件:

# 数据盘
other_args="--graph=/data/docker"

停止 docker 服务 service docker stop

copy 数据至数据盘 cp -r /var/lib/docker /data/docker/

移除、备份原数据 mv /var/lib/docker /data/bak/docker.bak

重新启动 docker 服务 service docker start

测试镜像和容器是否正常 docker images, docker ps -a

原文

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值