[
  {
    "path": "README.md",
    "content": "# Python3 开发以及部署 RESTful API项目（Python3 + Django2.0 + Django REST FrameWork + Centos7 + uWsgi + Nginx）\n文档分为两个部分，分别从开发和部署两个方面先介绍项目流程，然后会说明项目中最常遇到的问题以及解决方案。如果项目中有什么处理不正确的地方，也希望大家给予指正、交流。\n\n1. 开发环境采用Python3.6.3版本，项目采用Django2.0，Django REST FrameWork3.7.7去搭建。\n\n2. 部署的时候，系统版本为Centos7，uWsgi版本使用本文发布时最新的2.0.15，Nginx版本1.13.7\n\n## 第一部分 开发流程以及问题说明\n由于项目本身基于 Token 的身份验证，另外有些接口不只是处理一个表里的数据，直接通过ORM不太好映射，所以并没有完全按照Django REST FrameWork进行开发，并且只采用GET和POST方法。\n\n### 一、开发步骤\n首先搭建开发环境并且创建一个新的项目。\n#### 1. 安装Python3：如果使用的是 Mac OS X ，系统可能已经预装了 Python 。我们可以通过homebrew安装Python3。\n``` shell\n$ brew install python3\n```\n安装了Python3之后，会有pip3，使用pip install XXX 新安装的库会放在这个目录下面python2.7/site-packages使用pip3 install XXX 新安装的库会放在这个目录下面python3.6/site-packages如果使用python3执行程序，那么就不能import python2.7/site-packages中的库\n#### 2. 安装Django和Django REST FrameWork：可以下载django最新版本,若想指定版本,请在命令后面加上版本号。\n``` shell {.line-numbers}\n$ pip3 install django\n$ pip3 install djangorestframework\n```\n``` shell {.line-numbers}\n$ pip3 install django==版本号\n$ pip3 install djangorestframework==版本号\n```\n#### 3. 创建一个新的项目。\n``` shell {.line-numbers}\n$ cd ~\n$ django-admin.py startproject attendances\n$ cd attendances\n$ python manage.py startapp user apps/user\n```\n#### 4. 将新user应用和rest_framework应用添加到配置文件attendances/settings.py文件的INSTALLED_APPS。\n``` python {.line-numbers}\nINSTALLED_APPS = (\n    ...\n    'rest_framework',\n    'apps.user',\n)\n```\n#### 5. 写接口代码前的一点准备。\n做完以上步骤，我们就把项目开发需要的环境搭建完成了，接下来就可以在我们的项目中添加接口了。\n\n另外还有一点要说明的是，为了给我们的api增加版本控制，我们的文件目录和路由是这样处理的，这样我们就可以通过添加新的版本文件并且修改配置，同时运行多个版本的接口：\n\n**文件目录结构**\n<ul style='list-style:none;'>\n<li style='color:red;'>> apis</li>\n<li>\n    <ul style='list-style:none;'>\n        <li style='color:red;'>> api_v1</li>\n        <li>\n            <ul style='list-style:none'>\n            <li style='color:red;'>> user</li>\n            <li>> base</li>\n            <li>> enum</li>\n            <li>> setting</li>\n            <li style='color:red;'>> urls</li>\n            </ul>\n        </li>\n    </ul>\n</li>\n<li>> apps</li>\n<li>> attendances</li>\n<li>\n    <ul style='list-style:none'>\n    <li>> setting</li>\n    <li style='color:red;'>> urls</li>\n    <li>> wsgi</li>\n    </ul>\n</li>\n<li>> static</li>\n<li>> attendances.xml</li>\n<li>> manage.py</li>\n<li>> requirements.txt</li>\n<li>> test.py</li>\n</ul>\n\n**路由**\n``` python {.line-numbers}\n# 项目路由\nurlpatterns = [\n    url('admin/', admin.site.urls),\n    url(r'^api/v1/', include('apis.api_v1.urls', namespace='api-v1')),\n]\n```\n``` python {.line-numbers}\n# apis下面的路由\nfrom apis.api_v1.user import views as api_user\n\nurlpatterns = [\n    # user\n    url(r'^user/$', api_user.user, name='user'),\n]\n\napp_name = 'api-v1'\n```\n**调用地址：** https://{Domain}/api/v1/user\n\n**请求方法：** GET-获取用户信息 POST-修改用户信息\n\n> 文件说明：apis存放的是api接口文件，app存放的是应用或者可以说是模块，attendances是项目文件存放一些项目配置，static静态文件，attendances.xml是uWsgi的配置文件，requirements.txt是一些项目相关的依赖，test.py是为了测试uWsgi是否配置成功的文件。\n#### 6. 创建一个可以使用的模型以及Serializer类、Django视图。\n**在apps/user/models.py创建User模型**\n``` python {.line-numbers}\n\"\"\"\n用户模块\n\"\"\"\nfrom django.db import models, transaction\n\nclass User(models.Model):\n    \"\"\"\n    用户信息表\n    \"\"\"\n    user_id = models.AutoField(primary_key=True, verbose_name='用户id')\n    user_guid = models.CharField(max_length=150, verbose_name='用户guid')\n    user_name = models.CharField(\n        max_length=100, blank=True, null=True, verbose_name='用户名')\n    real_name = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='真实姓名')\n    avatar = models.CharField(\n        max_length=250, blank=True, null=True, verbose_name='头像')\n    mobile = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='手机')\n    balance = models.DecimalField(\n        max_digits=8, decimal_places=2, blank=True, null=True, verbose_name='账户余额')\n    available_balance = models.DecimalField(\n        max_digits=8, decimal_places=2, blank=True, null=True, verbose_name='可用金额')\n    frozen_balance = models.DecimalField(\n        max_digits=8, decimal_places=2, blank=True, null=True, verbose_name='冻结金额')\n    all_balance = models.DecimalField(\n        max_digits=8, decimal_places=2, blank=True, null=True, verbose_name='累计金额')\n    wx_open_id = models.CharField(max_length=150, verbose_name='微信OpenID')\n    wx_union_id = models.CharField(\n        max_length=150, blank=True, null=True, verbose_name='微信UnionID')\n    create_date = models.FloatField(blank=True, null=True, verbose_name='创建时间')\n    last_login_date = models.FloatField(\n        blank=True, null=True, verbose_name='最后登录时间')\n    ip_address = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='IP地址')\n    gender = models.IntegerField(blank=True, null=True, verbose_name='性别')\n    province = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='省份')\n    city = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='城市')\n    session_key = models.CharField(\n        max_length=150, blank=True, null=True, verbose_name='会话秘钥')\n    is_notify = models.IntegerField(blank=True, null=True, verbose_name='是否开启打卡通知')\n\n    @classmethod\n    def update_user_balance(cls, user_id, amount):\n        # 手动让select for update和update语句发生在一个完整的事务里面\n        with transaction.atomic():\n            user = (\n                cls.objects\n                .select_for_update()\n                .get(user_id=user_id)\n            )\n            user.available_balance += amount \n            user.balance = user.available_balance + user.frozen_balance\n            if amount > 0:\n                user.all_balance += amount\n            user.save()\n        return user\n\n    class Meta:\n        managed = False\n        db_table = 'user'\n        ordering = ['-create_date']\n        verbose_name = '用户'\n        verbose_name_plural = '用户'\n```\n**在apis/api_v1/user/serializers.py创建Serializer类**\n``` python {.line-numbers}\n\"\"\"\n用户模块\n\"\"\"\nfrom rest_framework import serializers\nfrom apps.user.models import User\nfrom apis.api_v1.enum import ErrorCode\nfrom apis.api_v1.base import BaseApi\n\n\nclass GetUserSerializer(serializers.Serializer):\n    \"\"\"\n    获取用户信息\n    \"\"\"\n    token = serializers.CharField(max_length=150)\n    user_id = serializers.IntegerField()\n\n    def get_user(self, validated_data):\n        result = dict()\n        base_api = BaseApi()\n        # 获取用户\n        try:\n            user = User.objects.get(user_id=validated_data[\"user_id\"])\n        except User.DoesNotExist:\n            result[\"error_code\"] = ErrorCode.用户不存在.value\n            result[\"error\"] = \"用户不存在\"\n            return result\n        # 认证\n        if not base_api.authenticate_user(validated_data[\"token\"], user.user_guid):\n            result[\"error_code\"] = ErrorCode.认证错误.value\n            result[\"error\"] = \"认证错误\"\n            return result\n        result[\"nick_name\"] = user.user_name\n        result[\"avatar\"] = user.avatar\n        result[\"error_code\"] = ErrorCode.正确.value\n        result[\"error\"] = \"\"\n        return result\n\n```\n**在apis/api_v1/user/view.py创建视图**\n``` python {.line-numbers}\n\"\"\"\n用户模块\n\"\"\"\nimport json\nfrom rest_framework.decorators import api_view\nfrom rest_framework.response import Response\nfrom rest_framework import status\nfrom apis.api_v1.user.serializers import *\nfrom apis.api_v1.enum import ErrorCode\n\n\n@api_view(['GET', 'POST'])\ndef user(request):\n    \"\"\"\n    GET:获取用户信息\n    POST:更新用户信息\n    \"\"\"\n    if request.method == 'GET':\n        param = dict()\n        param[\"token\"] = request.GET.get(\"token\", None)\n        param[\"user_id\"] = request.GET.get(\"user_id\", 0)\n        serializer = GetUserSerializer(data=param)\n        if serializer.is_valid():\n            result = serializer.get_user(serializer.validated_data)\n            return Response(result, status=status.HTTP_201_CREATED)\n        return Response(dict(error_code=ErrorCode.参数错误.value, error=json.dumps(serializer.errors, ensure_ascii=False)), status=status.HTTP_400_BAD_REQUEST)\n    # elif request.method == 'POST':\n    #     serializer = PostUserSerializer(data=request.data)\n    #     if serializer.is_valid():\n    #         result = serializer.update_user(serializer.validated_data)\n    #         return Response(result, status=status.HTTP_201_CREATED)\n    #     return Response(dict(error_code=ErrorCode.参数错误.value, error=json.dumps(serializer.errors, ensure_ascii=False)), status=status.HTTP_400_BAD_REQUEST)\n```\n到目前为止，我们已经完成了一个简单接口的创建，接下来可以对项目进行测试\n#### 4. 测试我们的Web API。首先启动一个开发服务器，之后在浏览器里输入地址访问我们的接口。\n启动开发服务器\n``` shell {.line-numbers}\npython manage.py runserver\n\nValidating models...\n\n0 errors found\nDjango version 2.0, using settings 'attendances.settings'\nDevelopment server is running at http://127.0.0.1:8000/\nQuit the server with CONTROL-C.\n```\n在浏览器里输入接口地址，获取到接口数据：http://127.0.0.1:8000/api/v1/user/?token=21cd4161-2a78-451e-8979-5fbd8538935e&user_id=4\n``` python {.line-numbers}\nGET /api/v1/user/?token=21cd4161-2a78-451e-8979-5fbd8538935e&user_id=4\n```\n``` python {.line-numbers}\nHTTP 201 Created\nAllow: POST, GET, OPTIONS\nContent-Type: application/json\nVary: Accept\n\n{\n    \"nick_name\": \"Band\",\n    \"avatar\": \"http://wx.qlogo.cn/mmopen/vi_32/aSKcBBPpibyKNicHNTMM0qJVh8Kjgiak2AHWr8MHM4WgMEm7GFhsf8OYrySdbvAMvTsw3mo8ibKicsnfN5pRjl1p8HQ/0\",\n    \"error_code\": 0,\n    \"error\": \"\"\n}\n```\n### 二、相关问题说明\n#### 1. 在配置文件里记得修改成本地时区以及将语言设置成中文。\n``` python {.line-numbers}\nLANGUAGE_CODE = 'zh-Hans'\nTIME_ZONE = 'Asia/Shanghai'\n```\n#### 2. 在安装python3之后，使用pip安装依赖的时候记得使用pip3,或者改掉python和pip的。\n``` shell {.line-numbers}\n$ alias python='/usr/bin/python3'\n```\n\b为了不混淆项目，并且大家在项目中遇到的问题可能各不一样，暂时不列出更多的问题，如果有疑问可以\b留言。\n## 第二部分 部署流程以及问题说明\n部署的时候建议大家安装docker容器，如果你没有太多的时间了解docker，可以先不用安装，直接按下面的流程去部署，部署的方式都是一样的，只是运行的地方不一样而已。这里就不列出docker的安装以及使用了。\n### 一、部署步骤\n#### 1. 安装python3\n首先安装依赖包。libxml模块是为了让uwsig支持使用“-x\"选项，能通过xml文件启动项目\n``` shell {.line-numbers}\n$ yum gcc-c++\n$ yum install wget openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel\n$ yum install libxml*\n```\n下载python3的压缩包之后，解压并安装到/usr/local/python3/路径下，ln命令是为了方便在终端中直接使用python3和pip3命令。\n``` shell {.line-numbers}\n$ wget https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tgz\n$ tar -zxvf Python-3.6.3.tar.gz\n$ ./configure --prefix=/usr/local/python3\n$ make -j2\n$ make install -j2\n$ ln -s /usr/local/python3/bin/python3.5 /usr/bin/python3\n$ ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3\n```\n#### 2. 安装uWsgi\n``` shell {.line-numbers}\n$ pip3 install uwsgi\n```\n为了在终端中使用uwsgi命令，执行以下命令\n``` shell {.line-numbers}\n$ ln -s /usr/local/python3/bin/uwsgi /usr/bin/uwsgi3\n```\n编写一个简单的wsgi应用测试uwsgi是否能正常使用,创建一个test.py文件。\n``` python {.line-numbers}\n# test.py\ndef application(env, start_response):\n    start_response('200 OK', [('Content-Type','text/html')])\n    return [b\"Hello World\"] # python3\n```\n运行uwsgi,http :8000表示使用http协议，端口号为8000，wigi-file则表示要运行的wsgi应用程序文件。\n``` shell {.line-numbers}\n# test.py\n$ uwsgi --http :8000 --wsgi-file test.py\n```\nuwsgi运行后打开浏览器，访问http://127.0.0.1:8000/ ，或者是相应服务器地址的8000端口，就可以看到hello world 页面了。\n#### 3. 安装Django以及项目依赖，通过uWsgi测试项目是否能正常运行\n安装依赖\n``` shell {.line-numbers}\n$ pip3 install django\n$ pip3 install djangorestframework\n$ pip3 install pycrypto\n```\n\bDjango通过uWsgi测试，如果能正常浏览则运行成功，静态文件无法访问的话，我们这里先忽略，后面会通过Nginx配置静态资源访问\n#### 4. 安装MySQL,也可以直接使用SQLite或PostgreSQL、Oracle数据库，Django支持多种数据库，根据配置安装不同的驱动，本项目采用MySQL，数据表结构以及链接配置请参考文档\n``` shell {.line-numbers}\n$ pip3 install mysql\n$ pip3 install mysql-server\n$ pip3 install mysql-devel\n```\nMySQL的安装可以参考文档：[MySQL 安装 | 菜鸟教程](http://www.runoob.com/mysql/mysql-install.html)\n#### 5. 连接uwsgi与Django\n该步骤只是检测Django项目能否在uwsgi下运行，下面我们将配置xml的启动配置文件\n``` shell {.line-numbers}\n$ uwsgi3 --http :8000 --module attendances.wsgi\n```\nxml的启动配置文件\n``` python {.line-numbers}\n<uwsgi>\n    <socket>127.0.0.1:8000</socket><!-- 内部端口，自定义 -->\n        <chdir>/root/web/attendances/attendances</chdir><!-- 项目路径 -->\n            <module>attendances.wsgi</module>\n                <processes>4</processes> <!-- 进程数 --> \n    <daemonize>uwsgi.log</daemonize><!-- 日志文件 -->\n</uwsgi>\n```\n进入项目执行以下命令\n``` shell {.line-numbers}\n$ uwsgi3 -x attendances.xml\n```\n#### 6. 安装nginx\n``` shell {.line-numbers}\n$ wget http://nginx.org/download/nginx-1.13.7.tar.gz\n$ tar -zxvf nginx-1.13.7.tar.gz\n$ ./configure\n$ make\n$ make install\n$ nginx\n```\n通过链接查看nginx是否启动成功:http://192.168.2.110\n#### 6. 通过配置nginx.conf文件连接Django、uWsgi与Nginx\n在/etc/nginx/nginx.conf修改nginx.conf\n``` shell {.line-numbers}\n    server {\n        listen       80 default_server;#暴露给外部访问的端口\n        listen       [::]:80 default_server;\n        server_name  127.0.0.1;\n\t    index  index.py index.html;\n        root         /root/web/attendances/attendances;\n\n        location / {\n            include uwsgi_params;\n            uwsgi_pass 127.0.0.1:8000;#外部访问80就转发到内部8000\n        }\n\n    \tlocation /static/ {\n            alias /root/web/attendances/attendances/static/;#项目静态路径设置\n    \t}\n    }\n```\n保存nginx.conf执行nginx -t命令先检查配置文件是否有错，没有错就执行以下命令：nginx启动nginx，可以通过链接查看nginx是否启动成功,之前启动过的话重启nginx.\n重启nginx\n``` shell {.line-numbers}\n$ nginx -t\n$ nginx -s reload\n```\n以上步骤都没有出错的话，打开你的浏览器，输入以下链接，记得关闭系统防火墙或者开放8000端口\nhttp://192.168.2.110/api/v1/user/?token=21cd4161-2a78-451e-8979-5fbd8538935e&user_id=4 （请将该ip替换成你的服务器ip）\n网站访问成功！\n### 二、相关问题说明\n#### 1. Nginx和Django静态文件处理\nDjango项目可以正常打开，但是静态文件引用路径还有问题，在Django开发时Django自己可以正确处理静态文件的路径，但是部署后Nginx去无法找到静态文件路径。\n\n检查Nginx配置文件夹sites-enabled里的nginx-pro文件，确保里面默认的try_files要删掉或者注释掉，否则Nginx会因此检查静态文件是否存在。\n\n将Django的静态文件集中起来，Django为此有专门的工具\n\n现在Django的Settings文件中加上StATIC_ROOT，把静态文件都集中到这个路径下\n\n``` shell\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static/\")\n```\n执行命令\n\n``` shell {.line-numbers}\n$ python3 ./manage.py collectstatic\n```\n这样所有Django前后台的静态文件都会集中到项目文件夹pro下static中，另外nginx-pro其中一个配置location /static即可让Nginx来处理静态内容。\n#### 2. Nginx权限问题(Nginx 403 forbidden)\n如果nginx用户没有web目录的权限，则会导致该错误。解决办法：修改web目录的读写权限。或者是把nginx的启动用户改成目录的所属用户，重起一下就能解决，在nginx.conf头部加入一行：user  root;因为项目是在root用户下建立的，通过这个用户去访问资源就可以访问。\n``` shell\nchmod -R 766 /web\n```\n#### 3. Nginx转发出现错误(502 BAD GATEWAY)\n\n``` shell\n$ setsebool -P httpd_can_network_connect 1\n```\n> 如果访问不了可以查看Nginx下的error.log文件，查看具体的问题，再找解决方法。\n## 分享一些中文文档资料\n> 有关Python、Django以及Django REST FrameWork的学习可以参考官方文档，以及以下中文文档，在网上找了很久，感觉以下这几个中文文档还是写的很不错的。\n\n[**Python：** 廖雪峰老师Python教程，基于最新的Python 3版本](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000)\n\n[**Django：**  The Django Book 2.0--中文版](http://docs.30c.org/djangobook2/index.html)\n\n[**Django REST FrameWork：** Django REST FrameWork中文文档目录](http://www.chenxm.cc/post/299.html)\n"
  },
  {
    "path": "apis/api_v1/base.py",
    "content": "import hashlib\nfrom apis.api_v1.setting import IS_TEST, APP_VERIFY_CODE\n\n\nclass BaseApi(object):\n    \n    def authenticate(self, token):\n        \"\"\"\n        认证\n        \"\"\"\n        if IS_TEST:\n            return True\n        elif not IS_TEST and self.md5(APP_VERIFY_CODE) == token:\n            return True\n        return False\n\n    def authenticate_user(self, token, guid):\n        \"\"\"\n        登录用户认证\n        \"\"\"\n        if IS_TEST:\n            return True\n        elif not IS_TEST and self.md5(APP_VERIFY_CODE + guid) == token:\n            return True\n        return False\n\n    def md5(self, str):\n        \"\"\"\n        MD5加密\n        \"\"\"\n        m = hashlib.md5()\n        m.update(str.encode(\"utf8\"))\n        return m.hexdigest()\n\n    def is_in_enum(self, eunm, key=None, value=None):\n        \"\"\"\n        判断是否为枚举值的元素\n        \"\"\"\n        for name, member in eunm.__members__.items():\n            if key is not None and name == key or value is not None and value == member.value:\n                return True\n        return False\n"
  },
  {
    "path": "apis/api_v1/enum.py",
    "content": "from enum import Enum, unique\n\n\n@unique\nclass ErrorCode(Enum):\n    # base\n    正确 = 0\n    参数错误 = 1\n    请求方法错误 = 2\n    认证错误 = 3\n    # user\n    微信登录凭证错误 = 1000\n    用户不存在 = 1001\n    获取微信用户信息错误 = 1002\n    账户余额不足 = 1003\n    推送状态不正确 = 1004\n"
  },
  {
    "path": "apis/api_v1/setting.py",
    "content": "# 是否测试环境，需要验证认证\nIS_TEST = True\n"
  },
  {
    "path": "apis/api_v1/urls.py",
    "content": "from django.conf.urls import url\nfrom apis.api_v1.user import views as api_user\nfrom apis.api_v1.scene import views as api_scene\nfrom apis.api_v1.joined_scene import views as api_joined_scene\nfrom apis.api_v1.my import views as api_my\nfrom apis.api_v1.other import views as api_other\n\nurlpatterns = [\n    # user\n    url(r'^user/$', api_user.user, name='user'),\n]\n\napp_name = 'api-v1'\n"
  },
  {
    "path": "apis/api_v1/user/serializers.py",
    "content": "from rest_framework import serializers\nfrom apps.user.models import User\nfrom apis.api_v1.enum import ErrorCode\nfrom apis.api_v1.base import BaseApi\n\n\nclass GetUserSerializer(serializers.Serializer):\n    \"\"\"\n    获取用户信息\n    \"\"\"\n    token = serializers.CharField(max_length=150)\n    user_id = serializers.IntegerField()\n\n    def get_user(self, validated_data):\n        result = dict()\n        base_api = BaseApi()\n        # 获取用户\n        try:\n            user = User.objects.get(user_id=validated_data[\"user_id\"])\n        except User.DoesNotExist:\n            result[\"error_code\"] = ErrorCode.用户不存在.value\n            result[\"error\"] = \"用户不存在\"\n            return result\n        # 认证\n        if not base_api.authenticate_user(validated_data[\"token\"], user.user_guid):\n            result[\"error_code\"] = ErrorCode.认证错误.value\n            result[\"error\"] = \"认证错误\"\n            return result\n        result[\"nick_name\"] = user.user_name\n        result[\"avatar\"] = user.avatar\n        result[\"error_code\"] = ErrorCode.正确.value\n        result[\"error\"] = \"\"\n        return result\n\n\n"
  },
  {
    "path": "apis/api_v1/user/views.py",
    "content": "import json\nfrom rest_framework.decorators import api_view\nfrom rest_framework.response import Response\nfrom rest_framework import status\nfrom apis.api_v1.user.serializers import *\nfrom apis.api_v1.enum import ErrorCode\n\n\n@api_view(['GET', 'POST'])\ndef user(request):\n    \"\"\"\n    GET:获取用户信息\n    POST:更新用户信息\n    \"\"\"\n    if request.method == 'GET':\n        param = dict()\n        param[\"token\"] = request.GET.get(\"token\", None)\n        param[\"user_id\"] = request.GET.get(\"user_id\", 0)\n        serializer = GetUserSerializer(data=param)\n        if serializer.is_valid():\n            result = serializer.get_user(serializer.validated_data)\n            return Response(result, status=status.HTTP_201_CREATED)\n        return Response(dict(error_code=ErrorCode.参数错误.value, error=json.dumps(serializer.errors, ensure_ascii=False)), status=status.HTTP_400_BAD_REQUEST)\n    # elif request.method == 'POST':\n    #     serializer = PostUserSerializer(data=request.data)\n    #     if serializer.is_valid():\n    #         result = serializer.update_user(serializer.validated_data)\n    #         return Response(result, status=status.HTTP_201_CREATED)\n    #     return Response(dict(error_code=ErrorCode.参数错误.value, error=json.dumps(serializer.errors, ensure_ascii=False)), status=status.HTTP_400_BAD_REQUEST)\n"
  },
  {
    "path": "apps/user/__init__.py",
    "content": "from os import path\nfrom django.apps import AppConfig\n\nVERBOSE_APP_NAME = \"用户管理\"\n\n\ndef get_current_app_name(file):\n    path_array = path.dirname(file).replace('\\\\', '/').split('/')\n    return path_array[-2] + '.' + path_array[-1]\n\n\nclass AppVerboseNameConfig(AppConfig):\n    name = get_current_app_name(__file__)\n    verbose_name = VERBOSE_APP_NAME\n\n\ndefault_app_config = get_current_app_name(\n    __file__) + '.__init__.AppVerboseNameConfig'\n"
  },
  {
    "path": "apps/user/admin.py",
    "content": "\"\"\"\n用户模块\n\"\"\"\nfrom django.contrib import admin\nfrom apps.user.models import User\n\nclass UserAdmin(admin.ModelAdmin):\n    \"\"\"\n    用户表\n    \"\"\"\n    list_display = ('user_id', 'user_name', 'real_name', 'avatar', 'mobile',\n                    'balance', 'available_balance', 'frozen_balance')\n    search_fields = ('user_name', 'mobile')\n\nadmin.site.register(User, UserAdmin)\n"
  },
  {
    "path": "apps/user/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass UserConfig(AppConfig):\n    name = 'user'\n"
  },
  {
    "path": "apps/user/migrations/0001_initial.py",
    "content": "# Generated by Django 2.0 on 2018-01-08 15:34\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='User',\n            fields=[\n                ('user_id', models.AutoField(primary_key=True, serialize=False, verbose_name='用户id')),\n                ('user_guid', models.CharField(max_length=32, verbose_name='用户guid')),\n                ('user_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='用户名')),\n                ('real_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='真实姓名')),\n                ('avatar', models.CharField(blank=True, max_length=250, null=True, verbose_name='头像')),\n                ('mobile', models.CharField(blank=True, max_length=50, null=True, verbose_name='手机')),\n                ('balance', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='账户余额')),\n                ('available_balance', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='可用金额')),\n                ('frozen_balance', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='冻结金额')),\n                ('all_balance', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='累计金额')),\n                ('wx_open_id', models.CharField(max_length=150, verbose_name='微信OpenID')),\n                ('wx_union_id', models.CharField(blank=True, max_length=150, null=True, verbose_name='微信UnionID')),\n                ('create_date', models.FloatField(blank=True, null=True, verbose_name='创建时间')),\n                ('last_login_date', models.FloatField(blank=True, null=True, verbose_name='最后登录时间')),\n                ('ip_address', models.CharField(blank=True, max_length=50, null=True, verbose_name='IP地址')),\n            ],\n            options={\n                'verbose_name': '用户',\n                'verbose_name_plural': '用户',\n                'db_table': 'user',\n                'ordering': ['-create_date'],\n                'managed': False,\n            },\n        ),\n    ]\n"
  },
  {
    "path": "apps/user/models.py",
    "content": "\"\"\"\n用户模块\n\"\"\"\nfrom django.db import models, transaction\n\nclass User(models.Model):\n    \"\"\"\n    用户信息表\n    \"\"\"\n    user_id = models.AutoField(primary_key=True, verbose_name='用户id')\n    user_guid = models.CharField(max_length=150, verbose_name='用户guid')\n    user_name = models.CharField(\n        max_length=100, blank=True, null=True, verbose_name='用户名')\n    real_name = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='真实姓名')\n    avatar = models.CharField(\n        max_length=250, blank=True, null=True, verbose_name='头像')\n    mobile = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='手机')\n    balance = models.DecimalField(\n        max_digits=8, decimal_places=2, blank=True, null=True, verbose_name='账户余额')\n    available_balance = models.DecimalField(\n        max_digits=8, decimal_places=2, blank=True, null=True, verbose_name='可用金额')\n    frozen_balance = models.DecimalField(\n        max_digits=8, decimal_places=2, blank=True, null=True, verbose_name='冻结金额')\n    all_balance = models.DecimalField(\n        max_digits=8, decimal_places=2, blank=True, null=True, verbose_name='累计金额')\n    wx_open_id = models.CharField(max_length=150, verbose_name='微信OpenID')\n    wx_union_id = models.CharField(\n        max_length=150, blank=True, null=True, verbose_name='微信UnionID')\n    create_date = models.FloatField(blank=True, null=True, verbose_name='创建时间')\n    last_login_date = models.FloatField(\n        blank=True, null=True, verbose_name='最后登录时间')\n    ip_address = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='IP地址')\n    gender = models.IntegerField(blank=True, null=True, verbose_name='性别')\n    province = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='省份')\n    city = models.CharField(\n        max_length=50, blank=True, null=True, verbose_name='城市')\n    session_key = models.CharField(\n        max_length=150, blank=True, null=True, verbose_name='会话秘钥')\n    is_notify = models.IntegerField(blank=True, null=True, verbose_name='是否开启打卡通知')\n\n    @classmethod\n    def update_user_balance(cls, user_id, amount):\n        # 手动让select for update和update语句发生在一个完整的事务里面\n        with transaction.atomic():\n            user = (\n                cls.objects\n                .select_for_update()\n                .get(user_id=user_id)\n            )\n            user.available_balance += amount \n            user.balance = user.available_balance + user.frozen_balance\n            if amount > 0:\n                user.all_balance += amount\n            user.save()\n        return user\n\n    class Meta:\n        managed = False\n        db_table = 'user'\n        ordering = ['-create_date']\n        verbose_name = '用户'\n        verbose_name_plural = '用户'\n"
  },
  {
    "path": "apps/user/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/user/urls.py",
    "content": "from django.conf.urls import url\nfrom apps.user import views\n\nurlpatterns = [\n    url(r'^user-add/(?P<id>\\d+)/$', views.add, name='user-add'),\n]\n\n\napp_name = 'user'\n"
  },
  {
    "path": "apps/user/views.py",
    "content": "\"\"\"\n用户表\n\"\"\"\nfrom django.shortcuts import render_to_response\nfrom apps.user.models import User\n\n\ndef add(request, id):\n    \"\"\"\n    add\n    \"\"\"\n    uid = id\n    return render_to_response('user/user_add.html', locals())\n"
  },
  {
    "path": "attendances/settings.py",
    "content": "\"\"\"\nDjango settings for attendances project.\n\nGenerated by 'django-admin startproject' using Django 2.0.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/2.0/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/2.0/ref/settings/\n\"\"\"\n\nimport os\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = '+6$=@6_9ts&1jzyd^fh99k6f_$)5&xixcqv-z8hg1(!sd&9a^3'\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = True\n\nALLOWED_HOSTS = ['127.0.0.1','192.168.2.110']\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    'suit',\n    'rest_framework',\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'apps.user',\n]\n\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\nROOT_URLCONF = 'attendances.urls'\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [\n            os.path.join(BASE_DIR, 'templates')\n        ],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = 'attendances.wsgi.application'\n\n\n# Database\n# https://docs.djangoproject.com/en/2.0/ref/settings/#databases\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.mysql',\n        'NAME': 'attendances',\n        'USER': 'root',\n        'PASSWORD': '123',\n        'HOST': '192.168.2.110',\n        'PORT': '3306',\n    }\n    # 'default': {\n    #     'ENGINE': 'django.db.backends.mysql',\n    #     'NAME': 'attendances',\n    #     'USER': 'root',\n    #     'PASSWORD': 'admin!@#$1234',\n    #     'HOST': '127.0.0.1',\n    #     'PORT': '3306',\n    # }\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/2.0/topics/i18n/\n\nLANGUAGE_CODE = 'zh-Hans'\n\nTIME_ZONE = 'Asia/Shanghai'\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/2.0/howto/static-files/\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static\")\n\nSTATIC_URL = '/static/'\n\nSTATICFILES_DIRS = (\n    os.path.join(BASE_DIR, \"static\"),\n)\n"
  },
  {
    "path": "attendances/urls.py",
    "content": "\"\"\"attendances URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/2.0/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  path('', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.urls import include, path\n    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))\n\"\"\"\nfrom django.conf.urls import url, include\nfrom django.contrib import admin\n\nurlpatterns = [\n    url('admin/', admin.site.urls),\n    url(r'^user/', include('apps.user.urls', namespace='user')),\n    url(r'^api/v1/', include('apis.api_v1.urls', namespace='api-v1')),\n]\n"
  },
  {
    "path": "attendances/wsgi.py",
    "content": "\"\"\"\nWSGI config for attendances project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/\n\"\"\"\n\nimport os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"attendances.settings\")\n\napplication = get_wsgi_application()\n"
  },
  {
    "path": "attendances.xml",
    "content": "<uwsgi>\n    <socket>127.0.0.1:8000</socket><!-- 内部端口，自定义 -->\n        <chdir>/root/web/attendances/attendances</chdir><!-- 项目路径 -->\n            <module>attendances.wsgi</module>\n                <processes>4</processes> <!-- 进程数 --> \n    <daemonize>uwsgi.log</daemonize><!-- 日志文件 -->\n</uwsgi>"
  },
  {
    "path": "manage.py",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"attendances.settings\")\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError as exc:\n        raise ImportError(\n            \"Couldn't import Django. Are you sure it's installed and \"\n            \"available on your PYTHONPATH environment variable? Did you \"\n            \"forget to activate a virtual environment?\"\n        ) from exc\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "requirements.txt",
    "content": "Django==2.0\ndjangorestframework==3.7.7\npycrypto==2.6.1\nuwsgi\nrequest"
  },
  {
    "path": "test.py",
    "content": "def application(env, start_response):\n    start_response('200 OK', [('Content-Type','text/html')])\n    return [b\"Hello World\"] # python3"
  }
]