CentOS 下使用 Pipenv + Gunicorn + Supervisor 部署 Flask 程序

当我们开发了一个简单的 Flask 程序,想把项目部署上线,我们可以选择传统的部署方式或者云部署方式把项目部署上线。在本文中,笔者将使用 阿里云轻量应用服务器 安装 CentOS 7 系统部署一个简单的 Flask 项目。

1. 购买域名、服务器、SSL 证书

要部署一个网站,首先要做的就是购买域名和服务器,市面上主要有阿里云、腾讯云、亚马逊云等云服务器供应商,你可以自由选择。除了域名和服务器外,还需要申请 SSL 证书,为开启 HTTPS 访问做准备。

1.1 域名

对于还从未购买过域名的用户,推荐使用阿里云和腾讯云购买域名,可以享受一元首购优惠。

1.2 服务器

阿里云和腾讯云针对有专门针对学生的学生机,价格非常实惠。其中阿里云是只要你的年龄 24 岁以下自动认定为大学生,可以尝试一下。

1.3 SSL 证书

SSL证书是数字证书的一种,类似于驾驶证、护照和营业执照的电子副本。因为配置在服务器上,也称为SSL服务器证书。SSL 证书就是遵守 SSL 协议,由受信任的数字证书颁发机构 CA ,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。

阿里云和腾讯云控制台都提供了 SSL 证书购买的功能,对于需要付费的 SSL 证书,你可以直接挑选一个价格合适的然后付钱即可。如果想申请免费的 SSL 证书,可以直接参考下方链接:

2. 网站备案

备案是指向主管机关报告事由存案以备查考。行政法角度看备案,实践中主要是《立法法》和《法规规章备案条例》的规定。根据中华人民共和国信息产业部第十二次部务会议审议通过的《非经营性互联网信息服务备案管理办法》精神,在中华人民共和国境内提供非经营性互联网信息服务,应当办理备案。未经备案,不得在中华人民共和国境内从事非经营性互联网信息服务。而对于没有备案的网站将予以罚款和关闭。

简单来说,购买了服务器之后,如果希望通过域名能正常访问到您的网站,就需要进行网站备案。

3. 网站域名解析

这里仅以阿里云服务器控制台为例,其它云服务器请参考官方说明文档。

首先,选择服务器控制台中的 站点设置 > 域名 菜单;然后点击 添加域名 按钮,为你的域名同时添加 'www' 及 '@' 记录。假设你购买的域名为 demo.com ,则同时添加的两条记录为:

  • '@' 记录 :demo.com
  • 'www' 记录:www.demo.com

这两个域名都能访问到你的网站首页。

图片描述

4. SSH 远程连接

通过 SSH 远程连接服务器实例,可以方便的对服务器进行管理。你可以手动输入命令生成 SSH 密钥连接服务器;也可以通过云服务器控制台自动生成密钥,然后导出密钥到本地,再使用导出的密钥连接服务器。这里推荐通过云服务器控制台生成密钥的方式。

相较于传统的用户名和密码认证方式,使用 SSH 密钥有以下优势:

  • SSH 密钥登录认证更为安全可靠,可以杜绝暴力破解威胁。
  • SSH 密钥登录方式更简便,只需在控制台和本地客户端做简单配置即可远程登录实例,再次登录时无需再输入密码。

4.1 控制台生成 SSH 密钥方式(推荐)

4.2 手动生成 SSH 密钥方式

在客户端的 Shell 中执行下面命令生成 SSH 密钥对:

$ ssh-keygen -t rsa -b 4096 -C "your_email@domain.com"

在客户端的 Shell 中执行下面命令授予 .ssh 文件夹 600 权限:

$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/authorizes_keys

在客户端的 Shell 中执行下面命令将客户端私钥拷贝到服务器:

# 执行下面的命令会被要求输入服务器对应用户的密码,密码输入正确才能成功完成拷贝
# 记得将下面命令中的 root@47.107.132.88 替换成你自己的服务器的 SSH 地址
$ ssh-copy-id -i ~/.ssh/id_rsa.pub root@47.107.132.88

在客户端的 Shell 中执行下面命令,进行 SSH 免密码登陆测试:

$ ssh root@47.107.132.88

在客户端的 ~/.bashrc 文件中为远程连接的命令取个别名,以后就可以方便的进行登陆了:

$ vim ~/.bashrc

在文件中找到下面这一行:

# some more ls aliases

在该行代码下面再添加一行并保存,内容如下:

alias ecs='ssh root@47.107.132.88'

在客户端的 Shell 中执行下面命令,使刚刚修改文件生效:

$ source ~/.bashrc

在客户端的 Shell 中执行下面命令,查看你已经设置的别名:

$ alias

5. 使用 MySQL 8 数据库

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择 MySQL 作为网站数据库。

5.1 安装与初始化

  1. Download MySQL Yum Repository 页面获取 MySQL 8 Community Yum 仓库文件的链接,例如:

    https://repo.mysql.com//mysql80-community-release-el7-2.noarch.rpm
  2. 通过 SSH 远程连接服务器实例,执行下面命令切换到其它拥有 root 权限的用户,阿里云服务器实例默认有一个拥有 root 权限的 admin 用户,这里以切换到 admin 用户为例子:

    $ su admin
  3. 执行下面命令,下载 MySQL 8 Community Yum 仓库文件:

    $ wget https://repo.mysql.com//mysql80-community-release-el7-2.noarch.rpm
  4. 执行下面命令,安装 MySQL 8 Community Yum 仓库文件:

    $ sudo yum localinstall mysql80-community-release-el7-2.noarch.rpm
  5. 执行下面命令,检查 MySQL 8 Community Yum 仓库文件是否正确安装 :

    $ yum repolist enabled | grep "mysql.*-community.*"
  6. 执行下面命令,安装 MySQL 8 Community :

    $ sudo yum install mysql-community-server
  7. 使用 service 命令管理 MySQL 服务:

    $ sudo service mysqld start          # 启动 MySQL 服务
    $ sudo service mysqld stop           # 停止 MySQL 服务
    $ sudo service mysqld restart        # 重启 MySQL 服务
    $ sudo service mysqld status         # 查看 MySQL 服务状态
  8. 使用 systemctl 命令管理 MySQL 服务:

    $ sudo systemctl start mysqld   # 启动 MySQL 服务
    $ sudo systemctl stop mysqld    # 停止 MySQL 服务
    $ sudo systemctl restart mysqld # 重启 MySQL 服务
    $ sudo systemctl status mysqld  # 查看 MySQL 服务状态
    $ sudo systemctl enable mysqld  # 设置 MySQL 服务开机自启动
    $ sudo systemctl disable mysqld # 关闭 MySQL 服务开机自启动
  9. 首次启动 MySQL 服务,会自动初始化数据目录、生成 SSL 证书和密钥文件、创建超级用户 ' root'@'localhost' ,超级用户的密码被设置并存储在错误日志文件中。可以使用以下命令查询临时密码:

    $ sudo grep 'temporary password' /var/log/mysqld.log
  10. 现在你可以用你查询到的临时密码连接数据库服务器了。

5.2 连接数据库服务器

  1. 输入以下命令,根据提示输入上一步获得的临时密码,连接数据库服务器:

    $ mysql -u root -p
    Enter password: (在这里输入上一步查询到的临时密码) 
  2. 连接 MySQL 服务器后,在 MySQL 命令行中为 ' root'@'localhost' 设置新密码,使临时密码失效:

    mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';

    新版 MySQL 的安全策略要求输入的密码要包含大写字母、小写字母、数字、特殊符号,推荐使用密码管理工具生成随机密码来作为你的新密码。

  3. 为了更加方便的远程连接 MySQL 服务器,接下来需要允许 MySQL 的 root 账户在其它地址登陆:

    mysql> USE mysql;
    mysql> UPDATE user SET host = '%' WHERE host = 'root';
    # 这里的 host = '%' 中的 % 表示允许在任意地址登陆,你也可以设置为指定的局域网 IP、公网 IP、域名等
  4. 接下来你就可以使用 DataGrip、Navicat 等数据库管理工具方便的管理云服务器实例上的 MySQL 了。

6. 编译安装 Python 3

Cent OS 预装了一个 Python 2,并且系统很多组件都依赖于 Python 2 ,笔者在安装和使用 Python 3 时就因为这些依赖情况遇到了很多问题,最后总结下来,正确的安装和使用 Python 3 的过程如下:

  1. 远程连接云服务器实例,在本示例中将使用 root 用户通过编译安装方式全局安装 Python 3,你也可以选择单独为某个用户安装 Python 3 ,步骤上大同小异,详细编译安装文档参考 Using Python on Unix platforms
  2. 使用 Yum 安装编译安装 Python 3 时依赖的包:

    $ yum -y groupinstall "Development tools"
    $ yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel
  3. 下载 Python 3.6.7 版本的安装包,其它版本的请自行去 Download Python | Python.org 获取链接:

    $ wget https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tgz
  4. 在当前用户目录解压下载的 Python 安装包:

    $ tar -zxvf Python-3.6.7.tgz
  5. 进入已解压的 Python 安装文件根目录:

    $ cd Python-3.6.7
  6. 通过编译配置指定 Python 的安装位置:

    $ ./configure --prefix=/usr/local/python3
  7. 使用 make 命令开始编译安装 Python:

    $ make && make install
  8. 为了和系统自带的 pythonpip 命令区分开来,给刚刚安装的 Python 建立软链接,并为其设置命令别名。分别取名为 python3pip3

    $ ln -s /usr/local/python3/bin/python3.6 /usr/bin/python3
    $ ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3
  9. 测试 Python 3 是否正确安装,输入 python3 命令:

    $ python3
    Python 3.6.7 (default, Feb  4 2019, 19:05:27)
    [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>>
  10. 测试 Pip 3 是否正确安装,输入 pip3 命令:

    $ pip3 -V
    pip 10.0.0 from /usr/local/python3/lib/python3.6/site-packages/pip (python 3.6)
  11. 更新 Pip :

    $ pip3 install --upgrade pip

7. 使用 Pipenv 管理 Python 虚拟环境

Pipenv 是 Pipfile 主要倡导者、requests 作者 Kenneth Reitz 写的一个命令行工具,主要包含了 Pipfile、pip、click、requests 和 virtualenv ,使用 Pipenv 可以方便的管理 Python 虚拟环境、管理依赖文件。Pipfile 和 Pipenv本来都是Kenneth Reitz 的个人项目,后来贡献给了 pypa 组织。Pipfile 是社区拟定的依赖管理文件,用于替代过于简陋的 requirements.txt 文件。

  1. 执行下面命令,安装 Pipenv :

    $ pip3 install pipenv
  2. 执行下面命令,为 Pipenv 可执行文件设置软链接,之后可以通过 pipenv 命令来使用 Pipenv :

    $ ln -s /usr/local/python3/bin/pipenv /usr/bin/pipenv
  3. 切换到一个拥有 root 权限的用户,这里以 admin 用户为例:

    $ su admin
  4. 在用户目录下为你的项目创建一个目录,并进入项目目录,项目名称以 FlaskApp 为例:

    $ cd ~
    $ mkdir FlaskApp
    $ cd FlaskApp
  5. 执行下面命令,为项目创建 Python 虚拟环境,默认将虚拟环境保存在 ~/.local/share/virtualenvs

    $ pipenv install

    如果想把虚拟环境保存至项目根目录,需要设置环境变量 PIPENV_VENV_IN_PROJECT=1 ,再执行创建命令:

    $ export PIPENV_VENV_IN_PROJECT=1
    $ pipenv install
  6. 虚拟环境创建完成后,执行下面命令为虚拟环境安装 Flask 包:

    $ pipenv install flask
  7. 在项目根目录编写一个简单的 Flask Demo 进行测试:

    # 新建并打开一个名为 app.py 的文件
    $ vim app.py

    输入下面的代码并保存:

    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def hello_flask():
        return 'Hello Flask!'
    
  8. 使用 pipenv run 调用虚拟环境中的 Python 执行 flask run 命令可以运行编写的代码:

    $ pipenv run flask run

    也可以使用 pipenv shell 命令进入虚拟环境,然后再在虚拟环境执行 flask run 命令运行程序:

    $ pipenv shell
    (venv)$ flask run
  9. Flask 默认运行的地址和端口为 http://127.0.0.1:5000 ,云服务器实例不包含桌面环境的话,你很难去浏览这个页面。你可以设置 flask 运行的地址和端口,然后尝试从外网访问该页面。先执行下面命令,让 flask 允许外网访问,并且监听 80 端口:

    $ pipenv run flask run --host 0.0.0.0 --port 80

    然后你可以通过你的服务器公网 IP 或 域名 直接访问到该页面。

8. 使用 Gunicorn 运行程序

flask run 命令启动的开发服务器是由 Werkzeug 提供的。细分的话, Werkzeug 提供的这个开发服务器应该被称为 WSGI 服务器,而不是单纯意义上的 Web 服务器。在生产环境中,我们需要一个更强健、性能更高的 WSGI 服务器。这些 WSGI 服务器也被称为独立 WSGI 容器,因为它们可以承载我们编写的 WSGI 程序,然后处理 HTTP 请求和响应。这通常有很多选择,比如 Gunicorn 。 Gunicorn 是 Green Unicorn 的简写,意为绿色独角兽,是一款专为 UNIX 设计的 Python WSGI HTTP 服务器。是一个Pre-fork 工人模型。Gunicorn 服务器广泛兼容各种 web 框架,实现简单,节省服务器资源,速度相当快。

  1. 安装 Gunicorn :

    $ pipenv install gunicorn
  2. 使用 Gunicorn 运行一个 WSGI 程序:

    $ pipenv run gunicorn --workers=4 --bind=0.0.0.0:8000 app:app
    # --workers = 4 表示使用 4 worker 进程运行程序,建议 worker 数量为 ( CPU 核心数 × 2 ) + 1
    # Gunicorn 默认只允许从本地 8000 端口访问,--bind=0.0.0.0:8000 表示允许使用 8000 端口从外部访问
    # app:app 冒号前面的 app 表示 app.py 文件,冒号后面的 app 表示 flask 程序的名称

    也可以把 --workers 简写为 -w--bind 简写为 -b ,如下:

    # 没有 -b 或者 --bind 参数,默认监听 127.0.0.1:8000
    $ pipenv run gunicorn -w 4 app:app
    
    # 指定 -b 0.0.0.0:8000 监听 8000 端口的外部请求
    $ pipenv run gunicorn -w 4 -b 0.0.0.0:8000 app:app

9. 使用 Nginx 提供反向代理

像 Gunicorn 这类 WSGI 服务器内置的 Web 服务器还不够强健,虽然程序可以正常运行,但是更流行的部署方式是使用一个常规的 Web 服务器运行在前端,为 WSGI 提供反向代理。比较流行的开源 Web 服务器有 Nginx 、Apache 等,这里选择使用和 Gunicorn 集成良好的 Nginx 。

  1. 访问 nginx packages 获取对应版本 Nginx 的 Yum 仓库的链接,例如:

    http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
  2. 下载 Nginx Yum 仓库文件:

    $ wget http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
  3. 安装 Nginx Yum 仓库文件:

    $ sudo yum localinstall nginx-release-centos-7-0.el7.ngx.noarch.rpm
  4. 安装 Nginx :

    $ sudo yum install nginx
  5. 进入 Nginx 配置文件目录:

    $ cd /etc/nginx/
  6. 创建 cert 目录,并上传你的 SSL 证书到该目录:

    $ mkdir cert

    上传 SSL 证书到 cert 目录你可以使用 scp 命令,或者使用 FileZilla 等 SFTP 软件,我上传的文件如下:

    $ cd cert
    $ ls
    ssl.key  ssl.pem
  7. 进入 /etc/nginx/conf.d/ 目录编辑默认的配置文件 default.conf

    $ cd /etc/nginx/conf.d/
    $ vim default.conf

    删除文件中原有的全部内容,新增下面内容并保存:

    # 监听 http 请求,强制跳转到 https
    server {
        listen      80;
        
        # 这里的 your.domain.com 换成你购买的域名
        server_name your.domain.com;
        
        # 这里的 your.domain.com 换成你购买的域名
        return 301 https://your.domain.com$request_uri;
    }
    
    # 监听 https 请求
    server {
        listen      443;
        
        # 这里的 your.domain.com 换成你购买的域名
        server_name your.domain.com;
        
        access_log  /var/log/nginx/host.access.log;
        error_log   /var/log/nginx/host.error.log;
    
        ssl on;
        
        # 这部分的 ssl.pem ssl.key 换成你上传的与其对应的文件
        ssl_certificate             cert/ssl.pem;
        ssl_certificate_key         cert/ssl.key;
    
        ssl_session_cache           shared:SSL:1m;
        ssl_session_timeout         5m;
    
        ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers                 ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_prefer_server_ciphers   on;
    
        location / {
            # 转发请求给 Gunicorn
            proxy_pass      http://127.0.0.1:8000;
            proxy_redirect  off;
            
            # 为了能正常运行,重写请求头
            proxy_set_header    Host                $host;
            proxy_set_header    X-Real-IP           $remote_addr;
            proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
            proxy_set_header    X-Forwarded-Proto   $scheme;
        }
        
        # 处理静态文件夹中的静态文件
        location /static {
            alias   /home/admin/FlaskApp/static/;
            
            # 设置静态文件缓存过期时间为 30 天
            expires  30d;
        }
    }
  8. 测试配置正确性:

    $ sudo nginx -t

    如果出现的提示中没有报错,则可以启动 nginx 了。

  9. 启动 nginx :

    $ sudo nginx

    现在,你可以使用 Gunicorn 不指定 --bind 参数运行 Flask 程序,然后尝试从外网通过 HTTPS 访问,判断 nginx 反向代理是否设置成功。

  10. 使用 nginx 命令管理 Nginx :

    $ sudo nginx            # 启动 Nginx 服务
    $ sudo nginx -s stop    # 关闭 Nginx 服务
    $ sudo nginx -s reload  # 重载 Nginx 服务
    $ sudo nginx -s reopen  # 重启 Nginx 服务
    $ sudo nginx -s quit    # 退出 Nginx 服务
  11. 使用 service 命令管理 Nginx 服务:

    $ sudo service nginx start          # 启动 Nginx 服务
    $ sudo service nginx stop           # 停止 Nginx 服务
    $ sudo service nginx restart        # 重启 Nginx 服务
    $ sudo service nginx status         # 查看 Nginx 服务状态
  12. 使用 systemctl 命令管理 Nginx 服务:

    $ sudo systemctl start nginx   # 启动 Nginx 服务
    $ sudo systemctl stop nginx    # 停止 Nginx 服务
    $ sudo systemctl restart nginx # 重启 Nginx 服务
    $ sudo systemctl status nginx  # 查看 Nginx 服务状态
    $ sudo systemctl enable nginx  # 设置 Nginx 服务开机自启动
    $ sudo systemctl disable nginx # 关闭 Nginx 服务开机自启动
  13. 如果 Nginx 已经启动却又被启动了一次,可能会报错。比如:找不到 nginx.pid 文件、提示 XX 端口已经被使用等等...,解决办法如下:

    # 杀掉占用 80 端口的进程
    $ sudo fuser -k 80/tcp
    
    # 杀掉占用 443 端口的进程
    $ sudo fuser -k 443/tcp
    
    # 使用默认配置文件重新启动 Nginx
    $ sudo nginx -c /etc/nginx/nginx.conf

10. 使用 Supervisor 管理进程

Supervisor 是用 Python 开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台 daemon ,并监控进程状态,异常退出时能自动重启。它是通过 fork/exec 的方式把这些被管理的进程当作 Supervisor 的子进程来启动,这样只要在 Supervisor 的配置文件中把要管理的进程的可执行文件的路径写进去即可。也实现当子进程挂掉的时候,父进程可以准确获取子进程挂掉的信息的,可以选择是否自己启动和报警。Supervisor 还提供了一个功能,可以为 supervisord 或者每个子进程设置一个非 root 的用户,这个用户就可以管理它对应的进程。

  1. 安装 Supervisor :

    $ sudo yum install supervisor
  2. 检查 Supervisor 配置文件:

    $ vim /etc/supervisord.conf

    找到最后一行,检查是否是如下内容:

    [include]
    files = supervisord.d/*.ini

    如果不是,则修改文件使其跟上面内容一致。

  3. 进入 /etc/supervisord.d/ 目录, 为项目创建一个 Supervisor 配置文件:

    $ cd /etc/supervisord.d/
    $ vi FlaskApp.ini

    配置文件内容为:

    [program:app]
    ; 下面命令中的 app:app 请修改为你实际部署时的项目名称
    command=pipenv run gunicorn -w 4 app:app
    
    ; 下面的路径请修改为你创建的项目的根目录
    directory=/home/admin/FlaskApp
    
    autostart=true
    autorestart=true
    stopsignal=QUIT
    stopasgroup=true
    killasgroup=true
    
    ; 下面的用户请修改为创建该项目的用户
    user=admin
    
    redirect_stderr=true
    
    ; log 文件的路径你可以重新自定义
    stdout_logfile=/home/admin/FlaskApp/log/supervisor.log
    
    ; 解决编码问题
    [supervisord]
    environment=LC_ALL='en_US.UTF-8',LANG='en_US.UTF-8'
  4. 启动 Supervisor :

    $ supervisord -c /etc/supervisord.conf
  5. 使用 service 命令管理 Supervisor 服务:

    $ sudo service supervisord start          # 启动 Supervisor 服务
    $ sudo service supervisord stop           # 停止 Supervisor 服务
    $ sudo service supervisord restart        # 重启 Supervisor 服务
    $ sudo service supervisord status         # 查看 Supervisor 服务状态
  6. 使用 systemctl 命令管理 Supervisor 服务:

    $ sudo systemctl start supervisord   # 启动 Supervisor 服务
    $ sudo systemctl stop supervisord    # 停止 Supervisor 服务
    $ sudo systemctl restart supervisord # 重启 Supervisor 服务
    $ sudo systemctl status supervisord  # 查看 Supervisor 服务状态
    $ sudo systemctl enable supervisord  # 设置 Supervisor 服务开机自启动
    $ sudo systemctl disable supervisord # 关闭 Supervisor 服务开机自启动
  7. 进入 Supervisor 控制台,管理后台进程:

    $ sudo supervisorctl
    app                              RUNNING   pid 2696, uptime 23:46:00
    supervisor > help # 输入 help 命令,查看 supervisor 支持的命令
    
    default commands (type help <topic>):
    =====================================
    add    clear  fg        open  quit    remove  restart   start   stop  update
    avail  exit   maintail  pid   reload  reread  shutdown  status  tail  version

    使用 status 命令,查看正在运行的后台进程:

    supervisor> status
    app                              RUNNING   pid 2696, uptime 23:49:37

    使用 stop 命令,结束指定的进程:

    supervisor> stop app
    app: stopped

    使用 start 命令,启动指定的进程:

    supervisor> start app
    app: started
  8. 测试,你可以先使用 Supervisor 运行进程,再通过外网访问页面,检查是否正常访问;再结束进程,看看页面是否显示 502 Bad Gateway
阅读 1.8k更新于 2月7日
推荐阅读
HelloJason
用户专栏

关于计算机、程序开发、电脑技巧的一些文章。

8 人关注
8 篇文章
专栏主页
目录