wushaodong 3 年 前
コミット
16923ff107
100 ファイル変更3411 行追加0 行削除
  1. 17 0
      .gitignore
  2. 89 0
      apps/Alipay.py
  3. 205 0
      apps/WeChatResponse.py
  4. 0 0
      apps/WechatApplet/__init__.py
  5. 26 0
      apps/WechatApplet/filters.py
  6. 0 0
      apps/WechatApplet/migrations/__init__.py
  7. 352 0
      apps/WechatApplet/models.py
  8. 30 0
      apps/WechatApplet/serializers.py
  9. 18 0
      apps/WechatApplet/urls.py
  10. 226 0
      apps/WechatApplet/views.py
  11. 0 0
      apps/WechatTp/__init__.py
  12. 0 0
      apps/WechatTp/migrations/__init__.py
  13. 92 0
      apps/WechatTp/models.py
  14. 19 0
      apps/WechatTp/serializers.py
  15. 12 0
      apps/WechatTp/urls.py
  16. 29 0
      apps/WechatTp/views.py
  17. 0 0
      apps/__init__.py
  18. 1 0
      apps/account/__init__.py
  19. 78 0
      apps/account/consts.py
  20. 18 0
      apps/account/filters.py
  21. 0 0
      apps/account/migrations/__init__.py
  22. 184 0
      apps/account/models.py
  23. 202 0
      apps/account/serializers.py
  24. 17 0
      apps/account/urls.py
  25. 153 0
      apps/account/views.py
  26. 0 0
      apps/api/__init__.py
  27. 10 0
      apps/api/urls.py
  28. 154 0
      apps/api/views.py
  29. 53 0
      apps/base.py
  30. 0 0
      apps/dashboard/__init__.py
  31. 11 0
      apps/dashboard/views.py
  32. 0 0
      apps/log/__init__.py
  33. 20 0
      apps/log/filters.py
  34. 0 0
      apps/log/migrations/__init__.py
  35. 54 0
      apps/log/models.py
  36. 9 0
      apps/option/filters.py
  37. 0 0
      apps/option/migrations/__init__.py
  38. 93 0
      apps/option/models.py
  39. 44 0
      apps/option/serializers.py
  40. 15 0
      apps/option/urls.py
  41. 129 0
      apps/option/views.py
  42. 1 0
      apps/order/__init__.py
  43. 26 0
      apps/order/filters.py
  44. 1 0
      apps/order/migrations/__init__.py
  45. 158 0
      apps/order/models.py
  46. 54 0
      apps/order/serializers.py
  47. 14 0
      apps/order/urls.py
  48. 127 0
      apps/order/views.py
  49. 184 0
      apps/serializer_errors.py
  50. 21 0
      manage.py
  51. 0 0
      nostone_loan/__init__.py
  52. 262 0
      nostone_loan/settings.py
  53. 36 0
      nostone_loan/urls.py
  54. 12 0
      nostone_loan/wsgi.ini
  55. 16 0
      nostone_loan/wsgi.py
  56. 17 0
      requirements
  57. BIN
      static/imgs/aliPay.png
  58. BIN
      static/imgs/wechart.png
  59. 95 0
      uis/layuiadmin/config.js
  60. 0 0
      uis/layuiadmin/layui/css/layui.css
  61. 1 0
      uis/layuiadmin/layui/css/layui.mobile.css
  62. 1 0
      uis/layuiadmin/layui/css/modules/code.css
  63. 0 0
      uis/layuiadmin/layui/css/modules/laydate/default/laydate.css
  64. BIN
      uis/layuiadmin/layui/css/modules/layer/default/icon-ext.png
  65. BIN
      uis/layuiadmin/layui/css/modules/layer/default/icon.png
  66. 0 0
      uis/layuiadmin/layui/css/modules/layer/default/layer.css
  67. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-0.gif
  68. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-1.gif
  69. BIN
      uis/layuiadmin/layui/css/modules/layer/default/loading-2.gif
  70. BIN
      uis/layuiadmin/layui/font/iconfont.eot
  71. 25 0
      uis/layuiadmin/layui/font/iconfont.svg
  72. BIN
      uis/layuiadmin/layui/font/iconfont.ttf
  73. BIN
      uis/layuiadmin/layui/font/iconfont.woff
  74. BIN
      uis/layuiadmin/layui/font/iconfont.woff2
  75. BIN
      uis/layuiadmin/layui/images/face/0.gif
  76. BIN
      uis/layuiadmin/layui/images/face/1.gif
  77. BIN
      uis/layuiadmin/layui/images/face/10.gif
  78. BIN
      uis/layuiadmin/layui/images/face/11.gif
  79. BIN
      uis/layuiadmin/layui/images/face/12.gif
  80. BIN
      uis/layuiadmin/layui/images/face/13.gif
  81. BIN
      uis/layuiadmin/layui/images/face/14.gif
  82. BIN
      uis/layuiadmin/layui/images/face/15.gif
  83. BIN
      uis/layuiadmin/layui/images/face/16.gif
  84. BIN
      uis/layuiadmin/layui/images/face/17.gif
  85. BIN
      uis/layuiadmin/layui/images/face/18.gif
  86. BIN
      uis/layuiadmin/layui/images/face/19.gif
  87. BIN
      uis/layuiadmin/layui/images/face/2.gif
  88. BIN
      uis/layuiadmin/layui/images/face/20.gif
  89. BIN
      uis/layuiadmin/layui/images/face/21.gif
  90. BIN
      uis/layuiadmin/layui/images/face/22.gif
  91. BIN
      uis/layuiadmin/layui/images/face/23.gif
  92. BIN
      uis/layuiadmin/layui/images/face/24.gif
  93. BIN
      uis/layuiadmin/layui/images/face/25.gif
  94. BIN
      uis/layuiadmin/layui/images/face/26.gif
  95. BIN
      uis/layuiadmin/layui/images/face/27.gif
  96. BIN
      uis/layuiadmin/layui/images/face/28.gif
  97. BIN
      uis/layuiadmin/layui/images/face/29.gif
  98. BIN
      uis/layuiadmin/layui/images/face/3.gif
  99. BIN
      uis/layuiadmin/layui/images/face/30.gif
  100. BIN
      uis/layuiadmin/layui/images/face/31.gif

+ 17 - 0
.gitignore

@@ -0,0 +1,17 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+**/migrations/*
+!**/migrations/__init__.py
+
+*.py[cod]
+local_settings.*
+*.bat
+*.txt
+*.zip
+*.whl
+venv
+.idea
+python*
+uis/up*
+.history
+build.py

+ 89 - 0
apps/Alipay.py

@@ -0,0 +1,89 @@
+#coding=utf-8
+import traceback
+from django.conf import settings
+from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
+from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
+from alipay.aop.api.domain.AlipayTradePrecreateModel import AlipayTradePrecreateModel
+from alipay.aop.api.domain.AlipayTradeQueryModel import AlipayTradeQueryModel
+from alipay.aop.api.request.AlipayTradePrecreateRequest import AlipayTradePrecreateRequest
+from alipay.aop.api.request.AlipayTradeQueryRequest import AlipayTradeQueryRequest
+from alipay.aop.api.response.AlipayTradePrecreateResponse import AlipayTradePrecreateResponse
+from alipay.aop.api.response.AlipayTradeQueryResponse import AlipayTradeQueryResponse
+
+from utils.exceptions import CustomError
+
+class Alipay():
+    @staticmethod
+    def payUnifiedOrder(payment_no,amount):
+        if amount < 0.01:
+            raise CustomError(u'无效的金额')
+
+        alipay_client_config = AlipayClientConfig()
+
+        try:
+            alipay_client_config.app_id = settings.ALIPAY_SETTING['app_id']
+            alipay_client_config.app_private_key = settings.ALIPAY_SETTING['app_private_key']
+            alipay_client_config.alipay_public_key  = settings.ALIPAY_SETTING['alipay_public_key']
+            notify_url = settings.ALIPAY_SETTING['notify_url']
+        except:
+            raise CustomError(u'没有配置支付宝对接信息')
+
+        alipay_client_config.timeout = 120
+        client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
+
+        model = AlipayTradePrecreateModel()
+        model.out_trade_no = payment_no
+        model.total_amount = amount
+        model.subject = ""
+
+        request = AlipayTradePrecreateRequest(biz_model=model)
+        request.notify_url = notify_url
+
+        try:
+            response_content = client.execute(request)
+        except Exception as e:
+            traceback.print_exc()
+            raise CustomError(str(e))
+        if not response_content:
+            raise CustomError(u'支付宝请求失败!')
+        response = AlipayTradePrecreateResponse()
+        response.parse_response_content(response_content)
+        if not response.is_success():
+            raise CustomError(response.code + "," + response.msg + "," + response.sub_code + "," + response.sub_msg)
+        return response.qr_code
+
+    @staticmethod
+    def queryUnifiedOrder(payment_no):
+        if not payment_no:
+            raise CustomError(u'无效的支付单号')
+
+        alipay_client_config = AlipayClientConfig()
+
+        try:
+            alipay_client_config.app_id = settings.ALIPAY_SETTING['app_id']
+            alipay_client_config.app_private_key = settings.ALIPAY_SETTING['app_private_key']
+            alipay_client_config.alipay_public_key  = settings.ALIPAY_SETTING['alipay_public_key']
+        except:
+            raise CustomError(u'没有配置支付宝对接信息')
+
+        alipay_client_config.timeout = 120
+        client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
+
+        model = AlipayTradeQueryModel()
+        model.out_trade_no = payment_no
+
+        request = AlipayTradeQueryRequest(biz_model=model)
+
+        try:
+            response_content = client.execute(request)
+        except Exception as e:
+            traceback.print_exc()
+            raise CustomError(str(e))
+        if not response_content:
+            raise CustomError(u'支付宝请求失败!')
+        response = AlipayTradeQueryResponse()
+        response.parse_response_content(response_content)
+        if not response.is_success():
+            raise CustomError(response.code + "," + response.msg + "," + response.sub_code + "," + response.sub_msg)
+        return response.buyer_pay_amount
+

+ 205 - 0
apps/WeChatResponse.py

@@ -0,0 +1,205 @@
+# coding=utf-8
+
+import uuid
+import requests
+import json
+import xmltodict
+import time
+from hashlib import md5
+from django.conf import settings
+from apps.WechatApplet.models import WechatApplet
+from utils.exceptions import CustomError
+
+
+# 微信支付sign_type
+WEIXIN_SIGN_TYPE = 'MD5'
+# 服务器IP地址
+WEIXIN_SPBILL_CREATE_IP = '39.106.109.89'
+# 微信支付用途
+# 微信KEY值 【API密钥】
+# WEIXIN_KEY = settings.WECHAT['merchant_key']
+# 微信统一下单URL
+WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
+
+# 微信查询订单URL
+WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
+
+# 微信支付回调API
+WEIXIN_CALLBACK_API = 'https://print.zzliaoyuan.com/api/wechat_notify/'
+
+# pc端支付
+class WeChatResponse():
+
+    def __init__(self,appid, agent_id=None):
+        applet = getAPPID(appid, agent_id)
+        self.params = {
+            "appid": appid,
+            'mch_id': applet.agent_num,
+            'nonce_str': '',
+            'sign_type': WEIXIN_SIGN_TYPE,
+            'sign': '',
+            'out_trade_no': '',
+        }
+        self.prepay_id = None
+        self.merchant_key = applet.agent_key
+
+    # 查询订单
+    def orderquery(self, out_trade_no):
+        self.params['out_trade_no'] = out_trade_no
+        self.params['nonce_str'] = generate_nonce_str()
+        self.params['sign'] = generate_sign(self.params, self.merchant_key)
+        data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
+        headers = {'Content-Type': 'application/xml'}
+        res = requests.post(WEIXIN_QUERY_ORDER_URL, data=data, headers=headers)
+        if res.status_code != 200:
+            raise CustomError(u'微信请求失败!')
+        result = json.loads(json.dumps(xmltodict.parse(res.content)))
+
+        if result['xml']['return_code'] != 'SUCCESS':
+            raise CustomError(u'微信通信失败![%s]' % result['xml']['return_msg'])
+        print(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
+        if result['xml']['trade_state'] == 'NOTPAY':
+            return result['xml']['total_fee']
+            # raise CustomError(u'微信交易状态![%s]' % (result['xml']['trade_state_desc']))
+        # 其他状态,返回金额0
+        return 0
+        # return result['xml']['total_fee']
+
+def getAPPID(appid, agent_id):
+    applet = WechatApplet.objects.filter(authorizer_appid=appid, is_authorize=True)
+    if agent_id:
+        applet = applet.filter(agent_id=agent_id)
+    if applet:
+        applet = applet.first()
+    else:
+        raise CustomError('小程序appid认证失败!')
+    return applet
+
+#小程序支付
+class WechatAppletPay():
+
+    def __init__(self, appid, agent_id=None):
+        applet = getAPPID(appid, agent_id)
+        self.params = {
+            "appid":  appid,
+            "body": applet.nick_name,
+            "mch_id": applet.agent_num,
+            "nonce_str": '',
+            "notify_url": WEIXIN_CALLBACK_API + appid + '/',
+            "openid": '',  # 获取小程序openid
+            "out_trade_no": '',
+            "spbill_create_ip": WEIXIN_SPBILL_CREATE_IP,
+            "total_fee": '',
+            "trade_type": 'JSAPI',
+            "sign":''
+        }
+        self.prepay_id = None
+        self.merchant_key = applet.agent_key
+
+
+
+    def weChatUnifiedOrder(self, openid, out_trade_no, total_fee):
+        self.params['out_trade_no'] = out_trade_no
+        self.params['total_fee'] = int(round(float(total_fee) * 100, 0))
+        self.params['nonce_str'] = generate_nonce_str()
+        self.params['openid'] = openid
+        self.params['sign'] = generate_sign(self.params,self.merchant_key)
+        # 拿到封装好的xml数据
+        xml_data = xmltodict.unparse({'xml': self.params}, pretty=True, full_document=False).encode('utf-8')
+        # 请求微信统一下单URL
+        headers = {'Content-Type': 'application/xml'}
+        res = requests.post(WEIXIN_UNIFIED_ORDER_URL, data=xml_data, headers=headers)
+
+        if res.status_code != 200:
+            raise CustomError(u'微信请求失败!')
+
+        # 回复数据为xml,将其转为字典
+        content = json.loads(json.dumps(xmltodict.parse(res.content)))
+
+        if content['xml']['return_code'] != 'SUCCESS':
+            raise CustomError(u'微信通信失败![%s]' % content['xml']['return_msg'])
+        if content['xml']['result_code'] != 'SUCCESS':
+            raise CustomError(u'微信交易失败![%s:%s]' % (content['xml']['err_code'],content['xml']['err_code_des']))
+
+        self.prepay_id = content['xml']['prepay_id']
+
+        return self.getPaymentInfo()
+
+    def getPaymentInfo(self):
+        data = {
+            'appId': self.params['appid'],
+            'package': "prepay_id={}".format(self.prepay_id),
+            'nonceStr': generate_nonce_str(),
+            'timeStamp': str(int(time.time())),
+            'signType':'MD5'
+        }
+        data['paySign'] = generate_sign(data,self.merchant_key)
+        return data
+
+class WechatPayNotify():
+    def __init__(self, params):
+        self.params = params
+
+    def handle(self):
+        resp_dict = json.loads(json.dumps(xmltodict.parse(self.params)))['xml']
+        return_code = resp_dict['return_code']
+
+        if return_code != 'SUCCESS':
+            return None
+
+        if not validate_sign(resp_dict):
+            return None
+
+        return resp_dict
+
+    @staticmethod
+    def response_ok():
+        return_info = {
+            'return_code': 'SUCCESS',
+            'return_msg': 'OK'
+        }
+        return generate_response_data(return_info)
+
+    @staticmethod
+    def response_fail():
+        return_info = {
+            'return_code': 'FAIL',
+            'return_msg': 'FAIL'
+        }
+        return generate_response_data(return_info)
+
+def generate_nonce_str():
+    """
+    生成随机字符串
+    """
+    return str(uuid.uuid4()).replace('-', '')
+
+
+def generate_sign(params,merchant_key):
+    """
+    生成md5签名的参数
+    """
+    if 'sign' in params:
+        params.pop('sign')
+    src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % merchant_key
+    return md5(src.encode('utf-8')).hexdigest().upper()
+
+
+def validate_sign(resp_dict):
+    """
+    验证微信返回的签名
+    """
+    if 'sign' not in resp_dict:
+        return False
+    wx_sign = resp_dict['sign']
+    sign = generate_sign(resp_dict, '')
+    if sign == wx_sign:
+        return True
+    return False
+
+
+def generate_response_data(resp_dict):
+    """
+    字典转xml
+    """
+    return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')

+ 0 - 0
apps/WechatApplet/__init__.py


+ 26 - 0
apps/WechatApplet/filters.py

@@ -0,0 +1,26 @@
+# coding=utf-8
+
+import django_filters
+
+from apps.WechatApplet.models import WechatApplet, MessageTemplate
+
+
+class WechatAppletFilter(django_filters.FilterSet):
+    user_version = django_filters.CharFilter(field_name='user_version', lookup_expr='icontains')
+    wait_audit_version = django_filters.CharFilter(field_name='wait_audit_version', lookup_expr='icontains')
+    wait_audit_template = django_filters.CharFilter(field_name='wait_audit_template', lookup_expr='icontains')
+    audit_status = django_filters.CharFilter(field_name='audit_status')
+    is_admin = django_filters.CharFilter(field_name='is_admin')
+
+    class Meta:
+        model = WechatApplet
+        fields = '__all__'
+
+class MsgTemplateFilter(django_filters.FilterSet):
+    agent_name = django_filters.CharFilter(field_name='wechat_app__agent__name', lookup_expr='icontains')
+    appid = django_filters.CharFilter(field_name='wechat_app__authorizer_appid')
+    type = django_filters.CharFilter(field_name='type')
+
+    class Meta:
+        model = MessageTemplate
+        fields = '__all__'

+ 0 - 0
apps/WechatApplet/migrations/__init__.py


+ 352 - 0
apps/WechatApplet/models.py

@@ -0,0 +1,352 @@
+# coding=utf-8
+
+import time
+import os
+from django.conf import settings
+from django.db import models
+from django.utils import timezone
+from apps.WechatTp.models import WechatTp
+from utils.wx.wechat import WeChat
+from utils.exceptions import CustomError
+from utils.file_operation import CertPath
+
+class WechatApplet(models.Model):
+    AUDIT_SUCCESS = 0
+    AUDIT_REJECT = 1
+    AUDITING = 2
+    RECALL = 3
+    AUDIT_DELAY = 4
+
+    AUDIT_STATUS_CHOICE = (
+        (AUDIT_SUCCESS, u'审核通过'),
+        (AUDIT_REJECT, u'审核拒绝'),
+        (AUDITING, u'审核中'),
+        (RECALL, u'已撤回'),
+        (AUDIT_DELAY, u'审核延后'),
+    )
+
+    # agent = models.ForeignKey(Agent, verbose_name=u'代理商', on_delete=models.PROTECT, null=True, editable=False)
+    wechat_tp = models.ForeignKey(WechatTp, verbose_name=u'第三方平台', on_delete=models.PROTECT, editable=False)
+    authorizer_appid = models.CharField(max_length=512, verbose_name=u'授权方appid')
+    secret = models.CharField(max_length=512, verbose_name=u'小程序秘钥', null=True)
+    authorizer_refresh_token = models.CharField(max_length=512, verbose_name=u'刷新令牌')
+    authorizer_access_token = models.CharField(max_length=512, verbose_name=u'令牌')
+    access_token_gtime = models.DateTimeField(verbose_name=u"获取令牌时间")
+    expires_in = models.IntegerField(verbose_name=u'令牌有效期')
+    is_authorize = models.BooleanField(verbose_name=u'是否授权', default=False)
+    message_template_id = models.CharField(max_length=255, verbose_name=u'消息模版ID', null=True)
+
+    nick_name = models.CharField(max_length=255, verbose_name=u'昵称', default="")
+    head_img = models.CharField(max_length=255, verbose_name=u'头像', default="")
+    principal_name = models.CharField(max_length=255, verbose_name=u'主体名称', default="")
+    qrcode_url = models.CharField(max_length=512, verbose_name=u'二维码', default="")
+    user_version = models.CharField(max_length=512, verbose_name=u'当前程序版本', default="")
+    template_id = models.CharField(max_length=512, verbose_name=u'当前模板', default="")
+    agent_num = models.CharField(max_length=512, verbose_name=u'商户号', default="")
+    agent_key = models.CharField(max_length=512, verbose_name=u'商户密钥', default="")
+
+    apiclient_cert = models.CharField(max_length=255, verbose_name=u'API证书cert', default="")
+    apiclient_key = models.CharField(max_length=255, verbose_name=u'API证书key', default="")
+
+    auditid = models.CharField(max_length=512, verbose_name=u'待审核ID', default="")
+    wait_audit_version = models.CharField(max_length=512, verbose_name=u'待审核版本', null=True)
+    wait_audit_template = models.CharField(max_length=512, verbose_name=u'待审核模板', null=True)
+    audit_status = models.IntegerField(choices=AUDIT_STATUS_CHOICE, verbose_name=u'审核状态', null=True)
+    reject_reason = models.CharField(max_length=512, verbose_name=u'拒绝原因', default="")
+
+    is_admin = models.BooleanField(verbose_name=u'是管理者小程序', default=False) # 会员信息的小程序属于管理者小程序,其他是代理商小程序
+
+    class Meta:
+        db_table = "wechat_applet"
+        ordering = ['-id']
+        index_together = ()
+        verbose_name = u"代理商小程序"
+        default_permissions = ()
+        permissions = [
+            ('manage_wechat_applet', u'管理'),
+        ]
+
+    @staticmethod
+    def getById(id):
+        try:
+            id = int(id)
+        except:
+            raise CustomError(u'无效的小程序ID!')
+
+        instance = WechatApplet.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError(u'未找到相应的小程序!')
+        return instance
+
+    @staticmethod
+    def getByAppid(appid):
+        instance = WechatApplet.objects.filter(authorizer_appid=appid, is_authorize=True).first()
+        if not instance:
+            raise CustomError(u'未找到相应的小程序!')
+        return instance
+
+    @staticmethod
+    def addAuthorizer(wechat_component, authorization_code, agent):
+        gtime = timezone.now()
+        component_appid = wechat_component.getAppid()
+        component_access_token = wechat_component.getAccessToken()
+        res = WeChat.getAuthorizationInfo(component_appid, authorization_code, component_access_token)
+        ares = WeChat.getAuthorizerInfo(component_appid, res['authorization_info']['authorizer_appid'], component_access_token)
+        authorizer = WechatApplet.getByAppidAndComponentAppid(res['authorization_info']['authorizer_appid'], component_appid)
+        if authorizer:
+            if authorizer.is_authorize:
+                raise CustomError(u'该小程序已授权!')
+            authorizer.refresh(res['authorization_info'], ares['authorizer_info'], gtime, agent)
+        else:
+            authorizer = WechatApplet.objects.create(
+                agent=agent,
+                wechat_tp=wechat_component,
+                authorizer_appid=res['authorization_info']['authorizer_appid'],
+                authorizer_refresh_token=res['authorization_info']['authorizer_refresh_token'],
+                authorizer_access_token=res['authorization_info']['authorizer_access_token'],
+                access_token_gtime=gtime,
+                expires_in=res['authorization_info']['expires_in'],
+                principal_name=ares['authorizer_info']['principal_name'],
+                nick_name=ares['authorizer_info']['nick_name'],
+                head_img=ares['authorizer_info']['head_img'],
+                qrcode_url=ares['authorizer_info']['qrcode_url'],
+                is_authorize=True
+            )
+        # 设置服务器域名
+        authorizer.setDomain()
+        return authorizer
+
+    @staticmethod
+    def getByAppidAndComponentAppid(appid, component_appid):
+        authorizer = WechatApplet.objects.filter(authorizer_appid=appid, wechat_tp__component_appid=component_appid).first()
+        return authorizer
+
+    def getComponent(self):
+        return self.wechat_tp
+
+    def getAccessToken(self):
+        if self.authorizer_access_token:
+            last_time = time.mktime(self.access_token_gtime.timetuple()) + self.expires_in
+            now = time.mktime(timezone.now().timetuple())
+            if last_time > now:
+                return self.authorizer_access_token
+        gtime = timezone.now()
+        res = WeChat.getAccessToken(self.authorizer_appid, self.secret)
+        self.refreshAccessToken(gtime, res['access_token'], res['expires_in'])
+        return self.authorizer_access_token
+
+    def getAccessToken2(self):
+        # 绑定第三方平台小程序,获取token
+        if self.authorizer_access_token:
+            last_time = time.mktime(self.access_token_gtime.timetuple()) + self.expires_in
+            now = time.mktime(timezone.now().timetuple())
+            if last_time > now:
+                return self.authorizer_access_token
+        if not self.authorizer_refresh_token:
+            return ''
+        gtime = timezone.now()
+        component = self.getComponent()
+        res = WeChat.getAuthorizerAccessToken(component.getAppid(), self.authorizer_appid, self.authorizer_refresh_token, component.getAccessToken())
+        self.refreshAccessToken(gtime, res['authorizer_access_token'], res['expires_in'], res['authorizer_refresh_token'])
+        return self.authorizer_access_token
+
+    def revoke(self):
+        agent = self.agent
+        agent.is_bind_app = False
+        agent.save()
+        self.is_authorize = False
+        self.save()
+
+    def refreshAccessToken(self, gtime, access_token, expires_in, refresh_token=''):
+        self.authorizer_access_token = access_token
+        self.access_token_gtime = gtime
+        self.expires_in = expires_in
+        self.authorizer_refresh_token = refresh_token
+        self.save()
+
+    def refresh(self, authorization_info, authorizer_info, access_token_gtime, agent):
+        self.authorizer_refresh_token = authorization_info['authorizer_refresh_token']
+        self.authorizer_access_token = authorization_info['authorizer_access_token']
+        self.access_token_gtime = access_token_gtime
+        self.expires_in = authorization_info['expires_in']
+        self.principal_name = authorizer_info['principal_name']
+        self.nick_name = authorizer_info['nick_name']
+        self.head_img = authorizer_info['head_img']
+        self.qrcode_url = authorizer_info['qrcode_url']
+        self.is_authorize = True
+        self.agent = agent
+        self.save()
+
+    def uploadCode(self, template_id, user_version, user_desc):
+        WeChat.commitCode(self.getAccessToken2(), template_id, user_version, user_desc)
+        result = WeChat.submitAuditCode(self.getAccessToken2())
+        self.auditid = result['auditid']
+        self.wait_audit_version = user_version
+        self.wait_audit_template = template_id
+        self.audit_status = WechatApplet.AUDITING
+        self.reject_reason = ''
+        self.save()
+
+    def refreshAuditStatus(self):
+        result = WeChat.getLastSubmitAuditCodeStatus(self.getAccessToken2())
+        if self.auditid == str(result['auditid']):
+            if result['status'] == WechatApplet.AUDIT_SUCCESS:
+                self.weapp_audit_success()
+            elif result['status'] == WechatApplet.AUDIT_REJECT:
+                self.weapp_audit_fail(result['reason'])
+            elif result['status'] == WechatApplet.RECALL:
+                self.weapp_audit_recall()
+            elif result['status'] == WechatApplet.AUDIT_DELAY:
+                self.weapp_audit_delay(result['reason'])
+
+    def weapp_audit_recall(self):
+        self.auditid = ''
+        self.wait_audit_template = None
+        self.wait_audit_version = None
+        self.audit_status = None
+        self.reject_reason = ''
+        self.save()
+
+    def weapp_audit_success(self):
+        self.user_version = self.wait_audit_version
+        self.template_id = self.wait_audit_template
+        self.auditid = ''
+        self.wait_audit_template = None
+        self.wait_audit_version = None
+        self.audit_status = None
+        self.reject_reason = ''
+        self.save()
+
+    def weapp_audit_fail(self, reason):
+        self.audit_status = WechatApplet.AUDIT_REJECT
+        self.reject_reason = reason
+        self.save()
+
+    def weapp_audit_delay(self, reason):
+        self.audit_status = WechatApplet.AUDIT_DELAY
+        self.reject_reason = reason
+        self.save()
+
+    def setDomain(self):
+        requestdomain = uploaddomain = downloaddomain = ['https://print.zzliaoyuan.com', ]
+        wsrequestdomain = []
+        WeChat.modify_domain(self.getAccessToken2(), 'set', requestdomain, wsrequestdomain, uploaddomain, downloaddomain)
+
+    def addPlugin(self):
+        result = WeChat.addPlugin(self.getAccessToken2())
+        return result
+
+    def releaseApplet(self):
+        result = WeChat.releaseCode(self.getAccessToken2())
+        return result
+
+    def getMsgTemplateId(self, title):
+        templates = WeChat.getTemplateList(self.getAccessToken())
+        for template in templates:
+            if template['title'] == title:
+                return template['priTmplId']
+        return ''
+
+    def sendCreateOrderMsg(self, openid, msg_data, value=0):
+        template_id = self.getMsgTemplateId('订单状态通知')
+        if not template_id:
+            return
+        data = {
+            'character_string1': {'value': msg_data['no']},
+            'date3':{'value':msg_data['create_time'].strftime('%Y-%m-%d %H:%M:%S')},
+            'phrase2':{'value':msg_data['status']},
+            'thing4':{'value': msg_data['commoditys']},
+            'thing8':{'value': msg_data['tips']},
+        }
+        page = 'pages/order/order?value={}'.format(value)
+        WeChat.sendSubscribeMessage(self.getAccessToken(), openid, template_id, page, data)
+
+    def sendConfirmOrderMsg(self, openid, msg_data):
+        template_id = self.getMsgTemplateId('订单确认通知')
+        if not template_id:
+            return
+        data = {
+            'character_string1': {'value': msg_data['no']},
+            'time2':{'value':msg_data['create_time'].strftime('%Y-%m-%d %H:%M:%S')},
+            'thing3':{'value':msg_data['commoditys']},
+            'thing4':{'value': msg_data['address']},
+            'thing5':{'value': msg_data['tips']},
+        }
+        page = 'pages/order/order?value=2'
+        WeChat.sendSubscribeMessage(self.getAccessToken(), openid, template_id, page, data)
+
+    def sendSubmitLogisicsMsg(self, openid, msg_data):
+        template_id = self.getMsgTemplateId('订单发货通知')
+        if not template_id:
+            return
+        data = {
+            'character_string8': {'value': msg_data['no']},
+            'time2':{'value':msg_data['dispatch_time']},
+            'character_string4':{'value':msg_data['logistics_no']},
+            'thing9':{'value': msg_data['logistics_company']},
+            'thing12':{'value': msg_data['address']},
+        }
+        page = 'pages/order/order?value=3'
+        WeChat.sendSubscribeMessage(self.getAccessToken(), openid, template_id, page, data)
+
+    def sendPayOrderMsg(self, openid, msg_data):
+        template_id = self.getMsgTemplateId('订单支付成功通知')
+        if not template_id:
+            return
+        data = {
+            'date3':{'value':msg_data['pay_time'].strftime('%Y-%m-%d %H:%M:%S')},
+            'phrase4': {'value': msg_data['payment']},
+            'character_string5': {'value': msg_data['no']},
+            'amount2':{'value': msg_data['pay_amount']},
+            'thing7':{'value': msg_data['tips']},
+        }
+        page = 'pages/order/order?value=1'
+        WeChat.sendSubscribeMessage(self.getAccessToken(), openid, template_id, page, data)
+
+    def upload_cert_file(self, file):
+        path = 'zzly_xcx_cert/%d/' % self.id
+        upload_path = CertPath(path)
+        filename = '%s%s' % (upload_path.path, file.name)
+        full_filename = "%s/%s" % (settings.MEDIA_ROOT, filename)
+        with open(full_filename, 'wb+') as destination:
+            for chunk in file.chunks():
+                destination.write(chunk)
+        if file.name == 'apiclient_cert.pem':
+            self.apiclient_cert = full_filename
+        elif file.name == 'apiclient_key.pem':
+            self.apiclient_key = full_filename
+        self.save()
+
+    def getWXAppCode(self, param):
+        page = 'pages/index/index'
+        filename = WeChat.getWXAppCode(self.getAccessToken2(), page, param)
+        return filename
+
+
+class MessageTemplate(models.Model):
+    CREATE_ORDER = 1
+    PAY_ORDER = 2
+    CONFIRM_ORDER = 3
+    DELIVER = 4
+
+    TYPE_CHOICE = (
+        (CREATE_ORDER, u'用户下单'),
+        (PAY_ORDER, u'支付成功'),
+        (CONFIRM_ORDER, u'确认订单'),
+        (DELIVER, u'发货通知'),
+    )
+    wechat_app = models.ForeignKey(WechatApplet, verbose_name=u'小程序', on_delete=models.PROTECT)
+    type = models.IntegerField(choices=TYPE_CHOICE, verbose_name=u'模板类别', null=True)
+    template = models.CharField(max_length=60, verbose_name=u'模板ID', default="")
+
+    class Meta:
+        db_table = "wechat_message_temp"
+        ordering = ['-id']
+        index_together = ()
+        verbose_name = u"小程序消息模板"
+        default_permissions = ()
+
+    @staticmethod
+    def get_temp_list(wechat_app):
+        rows = MessageTemplate.objects.filter(wechat_app=wechat_app).exclude(template='').values_list('template', flat=True)
+        return rows

+ 30 - 0
apps/WechatApplet/serializers.py

@@ -0,0 +1,30 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from apps.WechatApplet.models import WechatApplet, MessageTemplate
+
+
+class WechatAppletSerializer(serializers.ModelSerializer):
+    audit_status_text = serializers.CharField(source='get_audit_status_display', read_only=True)
+    agent_name = serializers.CharField(source='agent.name', read_only=True)
+    authorize_text = serializers.SerializerMethodField()
+
+    def get_authorize_text(self, obj):
+        if obj.is_authorize:
+            return u'是'
+        return u'否'
+
+    class Meta:
+        model = WechatApplet
+        fields = '__all__'
+
+
+class MsgTemplateSerializer(serializers.ModelSerializer):
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+    nick_name = serializers.CharField(source='wechat_app.nick_name', read_only=True)
+    principal_name = serializers.CharField(source='wechat_app.principal_name', read_only=True)
+    agent_name = serializers.CharField(source='wechat_app.agent.name', read_only=True)
+
+    class Meta:
+        model = MessageTemplate
+        fields = '__all__'

+ 18 - 0
apps/WechatApplet/urls.py

@@ -0,0 +1,18 @@
+# coding=utf-8
+
+from rest_framework.routers import SimpleRouter
+from django.conf.urls import url, include
+from .views import *
+
+urlpatterns = [
+    url(r'templates/$', TemplateListViewSet.as_view()), # 上传代码模板
+    url(r'draft_template/$', DraftTemplateListViewSet.as_view()), # 上传草稿箱模板到标准普通模板库
+    url(r'get_dict/$', DictListView.as_view()),
+    url(r'wx_msg_template/$', WxMsgTempView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'company_information', RegisterCompanyInformationViewSet)
+router.register(r'msg_template', MsgTemplateViewSet)
+router.register(r'', WechatAppletViewSet)
+urlpatterns += router.urls

+ 226 - 0
apps/WechatApplet/views.py

@@ -0,0 +1,226 @@
+# coding=utf-8
+
+import json
+from django.db import transaction
+from rest_framework.views import APIView
+from rest_framework.decorators import action
+
+from utils.custom_modelviewset import CustomModelViewSet
+from utils.permission import isLogin
+from utils import response_ok
+from apps.log.models import BizLog
+# from apps.admin import admin_log
+
+from .serializers import WechatAppletSerializer, MsgTemplateSerializer
+from apps.WechatApplet.filters import WechatAppletFilter, MsgTemplateFilter
+from apps.WechatApplet.models import WechatApplet, MessageTemplate
+from apps.WechatTp.models import WechatTp
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+from apps.account.models import CustomerWechat
+
+
+class TemplateListViewSet(APIView):
+    permission_classes = [isLogin, ]
+
+    def get(self, request):
+        tp = WechatTp.getDefault()
+        ret = tp.getTemplateList()
+        return response_ok(ret)
+
+class DraftTemplateListViewSet(APIView):
+    permission_classes = [isLogin, ]
+
+    def get(self, request):
+        tp = WechatTp.getDefault()
+        ret = tp.getDraftTemplateList()
+        return response_ok(ret)
+
+
+class DictListView(APIView):
+    permission_classes = [isLogin, ]
+
+    def get(self, request):
+        data = {
+            'temp_types': [],
+            'wechat_apps': [],
+        }
+
+        for type in MessageTemplate.TYPE_CHOICE:
+            item = {
+                'id': type[0],
+                'name': type[1],
+            }
+            data['temp_types'].append(item)
+
+        rows = WechatApplet.objects.filter(is_authorize=True)
+        for row in rows:
+            item = {
+                'id': row.id,
+                'name': row.nick_name,
+            }
+            data['wechat_apps'].append(item)
+
+        return response_ok(data)
+
+
+class WxMsgTempView(APIView):
+
+    def get(self, request):
+        appid = request.GET.get('appid')
+        data = []
+        if request.user and request.user.is_authenticated:
+            wechat = CustomerWechat.objects.filter(customer=request.user, wechat_app__authorizer_appid=appid).first()
+            if not wechat:
+                return response_ok(data)
+            rows = MessageTemplate.objects.filter(wechat_app=wechat.wechat_app)
+            if request.user.type == User.EMPLOYEE:
+                rows = rows.filter(type__in=[MessageTemplate.CREATE_ORDER, MessageTemplate.PAY_ORDER])
+            elif request.user.type == User.AGENT:
+                rows = rows.filter(
+                    type__in=[MessageTemplate.CONFIRM_ORDER, MessageTemplate.DELIVER, MessageTemplate.CREATE_ORDER])
+            else:
+                rows = rows.filter(type=MessageTemplate.CREATE_ORDER)
+
+            rows = rows.values_list('template', flat=True)
+            return response_ok(rows)
+        return response_ok(data)
+
+
+class WechatAppletViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = WechatApplet.objects.filter(is_admin=False)
+    serializer_class = WechatAppletSerializer
+
+    def filter_queryset(self, queryset):
+        f = WechatAppletFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    @action(methods=['post'], detail=True)
+    def upload_code(self, request, pk):
+        # 上传代码
+        template_id = request.POST.get('template_id')
+        user_version = request.POST.get('user_version')
+        user_desc = request.POST.get('user_desc')
+        app = WechatApplet.getById(pk)
+        with transaction.atomic():
+            app.uploadCode(template_id, user_version, user_desc)
+            BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'小程序上传审核代码,id=%d' % app.id, request.POST.dict())
+        return response_ok()
+
+    @action(methods=['post'], detail=False)
+    def batch_upload_code(self, request):
+        # 批量上传代码
+        template_id = request.POST.get('template_id')
+        user_version = request.POST.get('user_version')
+        user_desc = request.POST.get('user_desc')
+        app_ids = json.loads(request.POST.get('ids'))
+
+        apps = WechatApplet.objects.filter(id__in=app_ids)
+        for app in apps:
+            try:
+                with transaction.atomic():
+                    app.uploadCode(template_id, user_version, user_desc)
+            except:
+                pass
+        # admin_log(request.user, BizLog.INSERT, u'小程序批量上传审核代码', request.POST.dict())
+
+        return response_ok()
+
+    @action(methods=['post'], detail=False)
+    def add_to_temp(self, request):
+        # 上传草稿箱模板到标准模板
+        draft_id = request.POST.get('draft_id')
+        tp = WechatTp.getDefault()
+        tp.addToemplate(draft_id)
+        with transaction.atomic():
+            BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'小程序上传草稿到标准模板,draft_id=%s' % draft_id)
+        return response_ok()
+
+    @action(methods=['post'], detail=False)
+    def refresh_audit_status(self, request):
+        # 查询最新一次提交代码的审核状态  多选更新 审核状态
+        app_ids = json.loads(request.POST.get('ids'))
+        for app_id in app_ids:
+            app = WechatApplet.getById(app_id)
+            app.refreshAuditStatus()
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def set_secret(self, request, pk):
+        secret = request.POST.get('secret')
+        app = WechatApplet.getById(pk)
+        with transaction.atomic():
+            app.secret = secret
+            app.save()
+            # admin_log(request.user, BizLog.INSERT, u'设置小程序秘钥, id=%d' % app.id, secret)
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def set_merchant(self, request, pk):
+        agent_num = request.POST.get('agent_num')
+        agent_key = request.POST.get('agent_key')
+        app = WechatApplet.getById(pk)
+        with transaction.atomic():
+            app.agent_num = agent_num
+            app.agent_key = agent_key
+            app.save()
+            # admin_log(request.user, BizLog.INSERT, u'设置小程序商户号和商户秘钥, id=%d' % app.id)
+        return response_ok()
+
+    @action(methods=['post'], detail=False)
+    def add_plugin(self, request):
+        app_ids = json.loads(request.POST.get('ids'))
+        for app_id in app_ids:
+            apps = WechatApplet.getById(app_id)
+            apps.addPlugin()
+        return response_ok()
+
+    @action(methods=['post'], detail=False)
+    def release(self, request):
+        app_ids = json.loads(request.POST.get('ids'))
+        for app_id in app_ids:
+            app = WechatApplet.getById(app_id)
+            app.releaseApplet()
+        return response_ok()
+
+    @action(methods=['post'], detail=True)
+    def upload_cert(self, request, pk):
+        file = request.FILES.get('file', None)
+        app = WechatApplet.getById(pk)
+        with transaction.atomic():
+            app.upload_cert_file(file)
+            # admin_log(request.user, BizLog.INSERT, u'设置小程序企业支付api证书, id=%d' % app.id)
+        return response_ok()
+
+
+class MsgTemplateViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = MessageTemplate.objects.filter()
+    serializer_class = MsgTemplateSerializer
+
+    def filter_queryset(self, queryset):
+        f = MsgTemplateFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(MsgTemplateViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.INSERT,
+                              u'添加模板[%s],id=%d' % (instance.wechat_app.principal_name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(MsgTemplateViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.UPDATE,
+                              u'修改模板[%s],id=%d' % (instance.wechat_app.principal_name, instance.id), validated_data)
+
+    def destroy(self, request, *args, **kwargs):
+        instance = self.get_object()
+        BizLog.objects.addnew(self.request.user, BizLog.DELETE,
+                              u'删除模板[%s],id=%d' % (instance.wechat_app.principal_name, instance.id))
+        super(MsgTemplateViewSet, self).perform_destroy(instance)
+        return response_ok()

+ 0 - 0
apps/WechatTp/__init__.py


+ 0 - 0
apps/WechatTp/migrations/__init__.py


+ 92 - 0
apps/WechatTp/models.py

@@ -0,0 +1,92 @@
+# coding=utf-8
+
+import time
+from django.db import models
+from django.utils import timezone
+
+from utils.exceptions import CustomError
+
+from utils.wx.wechat import WeChat
+
+
+class WechatTp(models.Model):
+    component_appid = models.CharField(max_length=512, verbose_name=u'第三方平台appid')
+    component_appsecret = models.CharField(max_length=512, verbose_name=u'第三方平台appsecret')
+    m_encode_key = models.CharField(max_length=512, verbose_name=u'消息加密key')
+    m_token = models.CharField(max_length=512, verbose_name=u'消息校验token')
+
+    component_verify_ticket = models.CharField(max_length=512, verbose_name=u'验证票据', null=True)
+    component_access_token = models.CharField(max_length=512, verbose_name=u'令牌', null=True)
+    access_token_gtime = models.DateTimeField(verbose_name=u"获取令牌时间", null=True)
+    expires_in = models.IntegerField(verbose_name=u'令牌有效期', null=True)
+
+    class Meta:
+        db_table = "wechat_tp"
+        ordering = ['-id']
+        index_together = ()
+        verbose_name = u"第三方平台"
+        default_permissions = ()
+
+    @staticmethod
+    def getDefault():
+        tp = WechatTp.objects.filter().first()
+        if not tp:
+            raise CustomError(u'未找到微信第三方平台!')
+        return tp
+
+    @staticmethod
+    def getByAppid(appid):
+        component = WechatTp.objects.filter(component_appid=appid).first()
+        if not component:
+            raise CustomError(u'未找到相应的第三方平台!')
+        return component
+
+    def getAppid(self):
+        return self.component_appid
+
+    def getToken(self):
+        return self.m_token
+
+    def getEncodeKey(self):
+        return self.m_encode_key
+
+    def getAccessToken(self):
+        if self.component_access_token:
+            last_time = time.mktime(self.access_token_gtime.timetuple()) + self.expires_in
+            now = time.mktime(timezone.now().timetuple())
+            if last_time > now:
+                return self.component_access_token
+        res = WeChat.getComponentAccessToken(self.component_appid, self.component_appsecret, self.component_verify_ticket)
+        self.refreshAccessToken(timezone.now(), res['component_access_token'], res['expires_in'])
+        return self.component_access_token
+
+    def getPreAuthCode(self):
+        res = WeChat.getPreAuthCode(self.component_appid, self.getAccessToken())
+        return res['pre_auth_code']
+
+    def refreshVerifyTicket(self, component_verify_ticket):
+        self.component_verify_ticket = component_verify_ticket
+        self.save()
+
+    def refreshAccessToken(self, gtime, component_access_token, expires_in):
+        self.access_token_gtime = gtime
+        self.component_access_token = component_access_token
+        self.expires_in = expires_in
+        self.save()
+
+    def getAuthUrl(self, agent_id):
+        url = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=' + self.getAppid() + '&pre_auth_code=' + self.getPreAuthCode()
+        url += '&redirect_uri=https://print.zzliaoyuan.com/api/redirect_authorize/' + agent_id + '/&auth_type=2'
+        return url
+
+    def getTemplateList(self):
+        template_list = WeChat.getCodeTemplateList(self.getAccessToken())
+        return template_list
+
+    def getDraftTemplateList(self):
+        draft_list = WeChat.getDraftTemplateList(self.getAccessToken())
+        return draft_list
+
+    def addToemplate(self, draft_id):
+        result = WeChat.addToemplate(self.getAccessToken(), draft_id)
+        return result

+ 19 - 0
apps/WechatTp/serializers.py

@@ -0,0 +1,19 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from apps.WechatTp.models import WechatTp
+
+
+class WechatTpSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = WechatTp
+        fields = '__all__'
+
+    def create(self, validated_data):
+        instance = WechatTp.objects.filter().first()
+        if instance:
+            instance = super(WechatTpSerializer, self).update(instance, validated_data)
+        else:
+            instance = super(WechatTpSerializer, self).create(validated_data)
+        return instance

+ 12 - 0
apps/WechatTp/urls.py

@@ -0,0 +1,12 @@
+# coding=utf-8
+
+from rest_framework.routers import SimpleRouter
+from django.conf.urls import url, include
+from .views import *
+
+urlpatterns = [
+]
+
+router = SimpleRouter()
+router.register(r'', WechatTpViewSet)
+urlpatterns += router.urls

+ 29 - 0
apps/WechatTp/views.py

@@ -0,0 +1,29 @@
+# coding=utf-8
+
+from utils.custom_modelviewset import CustomModelViewSet
+from utils.permission import isLogin
+from utils import response_ok
+from apps.log.models import BizLog
+# from apps.admin import admin_log
+
+from .serializers import WechatTpSerializer
+from apps.WechatTp.models import WechatTp
+
+
+class WechatTpViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = WechatTp.objects.filter()
+    serializer_class = WechatTpSerializer
+
+    def list(self, request, *args, **kwargs):
+        instance = WechatTp.objects.filter().first()
+        if not instance:
+            return response_ok()
+        serializer = self.get_serializer(instance)
+        return response_ok(serializer.data)
+
+    def perform_create(self, serializer):
+        super(WechatTpViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        # admin_log(self.request.user, BizLog.INSERT, u'添加/修改用户第三方平台信息,id=%d' % instance.id, validated_data)

+ 0 - 0
apps/__init__.py


+ 1 - 0
apps/account/__init__.py

@@ -0,0 +1 @@
+#coding=utf-8

+ 78 - 0
apps/account/consts.py

@@ -0,0 +1,78 @@
+# coding=utf-8
+
+CONTENT_TYPE_SORTING = (
+    # 系统设置
+    'account-user',  # 用户管理
+    'account-manageagentuser',  # 权限管理
+    'option-option',  # 自定义项
+    'option-agentlevel',  # 代理商等级
+
+    'commodity-commodity',  # 商品管理
+    'commodity-commodityparameter',  # 商品参数
+
+    'agent-agentapply',  # 代理商申请
+    'agent-agent',  # 代理商
+    'agent-agentcommodity',  # 代理商商品
+    'order-order',  # 代理商订单
+
+    'WechatApplet-wechatapplet',  # 小程序管理
+
+    'deliver-productdeliver',  # 防窜货管理
+)
+
+MENU_TO_MODEL = (
+    (
+        u'基础数据', (
+            'account-user',
+            'account-manageagentuser',
+            'option-option',
+            'option-agentlevel',
+        )
+    ),
+    (
+        u'商品管理', (
+            'commodity-commodity',
+            'commodity-commodityparameter',
+        )
+    ),
+    (
+        u'代理商管理', (
+            'agent-agentapply',
+            'agent-agent',
+            'agent-agentcommodity',
+            'order-order',
+        )
+    ),
+    (
+        u'其他', (
+            'WechatApplet-wechatapplet',
+            'deliver-productdeliver',
+        ),
+    ),
+
+)
+
+
+class PermissionMenu(object):
+
+    def __init__(self):
+        self.sort = CONTENT_TYPE_SORTING
+        self.menus = MENU_TO_MODEL
+
+    def get_index(self, app_label, model):
+        try:
+            return self.sort.index('{}-{}'.format(app_label, model))
+        except:
+            return 9999
+
+    def sort_perms(self, perms):
+        perms = perms.order_by('content_type__model', 'id')
+        perms = sorted(perms, key=lambda n: self.get_index(n.content_type.app_label, n.content_type.model))
+        return perms
+
+    def get_menuname_of_contenttype(self, app_label, model):
+        for menu in self.menus:
+            val = '{}-{}'.format(app_label, model)
+            if val in menu[1]:
+                return menu[0]
+        return u'未分类'

+ 18 - 0
apps/account/filters.py

@@ -0,0 +1,18 @@
+# coding=utf-8
+import django_filters
+
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
+
+User = get_user_model()
+
+
+class UserFilter(django_filters.FilterSet):
+    name = django_filters.CharFilter(field_name='name', lookup_expr='icontains')
+    tel = django_filters.CharFilter(field_name='tel', lookup_expr='icontains')
+    username = django_filters.CharFilter(field_name='username', lookup_expr='icontains')
+    is_active = django_filters.CharFilter(field_name='is_active')
+
+    class Meta:
+        model = User
+        fields = ['name', 'username', 'tel', 'is_active']

+ 0 - 0
apps/account/migrations/__init__.py


+ 184 - 0
apps/account/models.py

@@ -0,0 +1,184 @@
+# coding=utf-8
+
+from django.db import models
+from django.db.models import Q
+from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager, Group, AbstractUser
+from django.utils import timezone
+from rest_framework.utils import model_meta
+from utils.exceptions import CustomError
+from django.conf import settings
+from utils.wx.WXBizDataCrypt import WXBizDataCrypt
+from utils.wx.wechat import WeChat
+from apps.WechatApplet.models import WechatApplet
+
+class UserManager(BaseUserManager):
+    def create_superuser(self, username, password, **extra_fields):
+        u = self.create_user(User.EMPLOYEE, username, password, **extra_fields)
+        u.is_active = True
+        u.is_superuser = True
+        u.save(using=self._db)
+        return u
+
+    def create_user(self, type, username, password=None, **extra_fields):
+        if not username:
+            raise CustomError(u'请输入账号!')
+        count = User.objects.filter(username=username).count()
+        if count > 0:
+            raise CustomError(u'该账号已存在!')
+        user = self.model(
+            type=type,
+            username=username,
+            is_superuser=False,
+            last_login=timezone.now(),
+            **extra_fields
+        )
+
+        user.set_password(password)
+        user.save(using=self._db)
+        return user
+
+class User(AbstractBaseUser, PermissionsMixin):
+    EMPLOYEE = 1
+    CUSTOMER = 2
+    TYPE_CHOICES = (
+        (EMPLOYEE, u'员工'),  # 内部员工
+        (CUSTOMER, u'客户'),  # 客户
+    )
+    username = models.CharField(verbose_name=u'用户名', max_length=30, unique=True, db_index=True,
+                                help_text=u'不多于20个字符。只能用字母、数字和字符。')
+    # password = models.CharField(u'密码', max_length=128, blank=True,)
+
+    is_active = models.BooleanField(verbose_name=u'是否可用', default=True, editable=False)
+    date_joined = models.DateTimeField(verbose_name=u'注册时间', auto_now_add=True, editable=False)
+    type = models.PositiveSmallIntegerField(verbose_name=u"类型", choices=TYPE_CHOICES, editable=False, default=CUSTOMER)
+    name = models.CharField(max_length=20, verbose_name=u"姓名")
+    gender = models.PositiveSmallIntegerField(choices=settings.GENDER_CHOICES, verbose_name=u"性别",
+                                              default=settings.MALE)
+    face = models.CharField(max_length=200, verbose_name=u'头像', null=True)
+    ID_card = models.CharField(max_length=18, verbose_name=u"身份证号", null=True, blank=True)
+    address = models.CharField(max_length=100, verbose_name=u"家庭住址", null=True, blank=True)
+    tel = models.CharField(max_length=15, verbose_name=u"手机", null=True, )
+    position = models.CharField(max_length=15, verbose_name=u"岗位", null=True)
+    create_user = models.ForeignKey('self', verbose_name='创建者', null=True, on_delete=models.PROTECT)
+
+    objects = UserManager()
+
+    USERNAME_FIELD = 'username'
+    REQUIRED_FIELDS = []
+
+    class Meta:
+        db_table = "auth_user"
+        verbose_name = u"人员管理"
+        unique_together = [
+            ('username')
+        ]
+        ordering = ['-id']
+        default_permissions = ()
+        permissions = [
+        ]
+
+    def __unicode__(self):
+        return self.username
+
+    def change_password(self, new_password, confirm_password, old_password):
+        if new_password != confirm_password:
+            raise CustomError(u'两次输入的密码不一致,请检查')
+        if not self.check_password(old_password):
+            raise CustomError(u'原密码输入错误,请检查')
+        self.set_password(new_password)
+        self.save()
+
+    def update_item(self, validated_data):
+        def update():
+            info = model_meta.get_field_info(self)
+            for attr, value in validated_data.items():
+                if attr in info.relations and info.relations[attr].to_many:
+                    field = getattr(self, attr)
+                    field.set(value)
+                else:
+                    setattr(self, attr, value)
+
+        if not 'username' in validated_data:
+            raise CustomError(u'账号不能为空!')
+        count = User.objects.filter(username=validated_data['username']).exclude(id=self.id).count()
+        if count > 0:
+            raise CustomError(u'该账号已存在!')
+
+        if not 'password' in validated_data or not validated_data['password']:
+            validated_data['password'] = self.password
+            update()
+        else:
+            update()
+            self.set_password(validated_data['password'])
+        self.save()
+        return self
+
+    def is_login(self):
+        if self.is_authenticated and self:
+            return True
+        return False
+
+class CustomerWechat(models.Model):
+    wechat_app = models.ForeignKey(WechatApplet, verbose_name=u'小程序', on_delete=models.PROTECT, editable=False)
+    customer = models.ForeignKey(User, verbose_name=u'用户', on_delete=models.PROTECT, editable=False, null=True)
+    openid = models.CharField(max_length=512, verbose_name=u"openid")
+    session_key = models.CharField(max_length=512, verbose_name=u'session_key', null=True)
+
+    # 保存客户+小程序的关联信息。如果一个客户登录多个小程序,这里会有多条数据
+    class Meta:
+        db_table = 'customer_wechat'
+        verbose_name = u'微信客户'
+        unique_together = [
+            ('openid', 'wechat_app')
+        ]
+        default_permissions = ()
+
+    @staticmethod
+    def login(code, appid):
+        wechat_applet = WechatApplet.getByAppid(appid)
+        res = WeChat.code2Session(appid, wechat_applet.secret, code)
+        instance = CustomerWechat.objects.filter(openid=res['openid'], wechat_app__authorizer_appid=appid).first()
+        if not instance:
+            instance = CustomerWechat.objects.create(
+                wechat_app=wechat_applet,
+                openid=res['openid'],
+                session_key=res['session_key']
+            )
+        else:
+            instance.session_key = res['session_key']
+            instance.save()
+        return instance
+
+    @staticmethod
+    def bindWechat(appid, openid, phoneEncryptedData, phoneIv):
+        customer_wechat = CustomerWechat.objects.filter(openid=openid, wechat_app__authorizer_appid=appid).first()
+        if not customer_wechat:
+            raise CustomError(u'未找到相应的微信客户!')
+
+        pc = WXBizDataCrypt(appid, customer_wechat.session_key)
+        phon_data = pc.decrypt(phoneEncryptedData, phoneIv)
+        tel = phon_data['purePhoneNumber']
+
+        if customer_wechat.customer and customer_wechat.customer.username == tel:
+            # 已绑定用户,且用户账号和手机号一致
+            return customer_wechat.customer
+
+        # 用户用手机号、密码登录后,在绑定微信,两个号码可能会不符
+        # 张三是绑定用户。 张三的账号,在李四小程序上登录,绑定信息时,手机号可能不符。
+        # 这种情况,应该返回tel对应的账号,或者创建tel账号
+        user = User.objects.filter(username=tel).first()
+        if not user:
+            # 密码默认手机号
+            user = User.objects.create_user(User.CUSTOMER, tel, password=tel,
+                                            **{
+                                                'tel': tel,
+                                                'name': tel,
+                                            }
+                                            )
+
+        customer_wechat.customer = user
+        customer_wechat.save()
+        return user
+
+Group.add_to_class('create_user',
+                   models.ForeignKey(User, verbose_name=u"创建人", on_delete=models.PROTECT, editable=False))

+ 202 - 0
apps/account/serializers.py

@@ -0,0 +1,202 @@
+# coding=utf-8
+
+import json
+from django.conf import settings
+from django.contrib.auth import get_user_model, authenticate
+from rest_framework import serializers
+from rest_framework_jwt.serializers import JSONWebTokenSerializer
+from rest_framework_jwt.settings import api_settings
+from django.contrib.auth.models import Group, Permission
+from apps.log.models import BizLog
+from utils import get_remote_addr
+from utils.booleancharfield import BooleanCharField
+from utils.exceptions import CustomError
+from apps.account.models import CustomerWechat
+from apps.option.models import Config, Balance
+User = get_user_model()
+jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
+jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
+
+class JWTSerializer(JSONWebTokenSerializer):
+    def validate(self, attrs):
+        credentials = {
+            self.username_field: attrs.get(self.username_field),
+            'password': attrs.get('password')
+        }
+        if all(credentials.values()):
+            user = authenticate(**credentials)
+
+            if user:
+                if not user.is_active:
+                    msg = u'禁用帐户,禁止登录!'
+                    BizLog.objects.addnew(user,  BizLog.INSERT,u'禁用帐户[%s]尝试登录系统,IP[%s]' % (user.username, get_remote_addr(self.request)))
+                    raise serializers.ValidationError(msg)
+
+                payload = jwt_payload_handler(user)
+                BizLog.objects.addnew(user, BizLog.INSERT,
+                                      u'[%s]登录系统,IP[%s]' % (user.username,get_remote_addr(self.request)))
+                permissions = list(user.get_all_permissions())
+                return {
+                    'token': jwt_encode_handler(payload),
+                    'user_id': user.id,
+                    'username': user.username,
+                    'permissions': permissions,
+                }
+            else:
+                msg = u'账号或者密码错误!'
+                raise serializers.ValidationError(msg)
+        else:
+            msg = u'必须包含“{username field}”和“password.'
+            msg = msg.format(username_field=self.username_field)
+            raise serializers.ValidationError(msg)
+
+class EmployeeSerializer(serializers.ModelSerializer):
+    enable_text = BooleanCharField(source='is_active', read_only=True)
+    create_user_text = serializers.CharField(source='create_user.name', read_only=True)
+    gender_text = serializers.CharField(source='get_gender_display', read_only=True)
+    type_text = serializers.CharField(source='get_type_display', read_only=True)
+    date_joined_f = serializers.DateTimeField(source='date_joined', format=settings.SHORT_DATETIME_FORMAT,
+                                              read_only=True)
+
+    class Meta:
+        model = User
+        # fields = '__all__'
+        exclude = ('password',)
+
+    def create(self, validated_data):
+        username = self.initial_data['username']
+        user = User.objects.filter(username=username).first()
+        if user:
+            raise CustomError(u'账号[{0}]已存在。'.format(username))
+        # 代理商添加的账号,默认就是操作人所在的代理商
+        validated_data['is_active'] = self.initial_data['is_active'] == '1'
+        user = self.context['request'].user
+        validated_data['create_user'] = user
+        validated_data['type'] = User.EMPLOYEE
+        instance = super(EmployeeSerializer, self).create(validated_data)
+        instance.set_password(self.initial_data['password'])
+        instance.save()
+        return instance
+
+    def update(self, instance, validated_data):
+
+        password = instance.password
+        validated_data['is_superuser'] = instance.is_superuser
+        validated_data['is_active'] = self.initial_data['is_active'] == '1'
+        instance = super(EmployeeSerializer, self).update(instance, validated_data)
+        if not 'password' in self.initial_data or not self.initial_data['password']:
+            instance.password = password
+        else:
+           instance.set_password(self.initial_data['password'])
+        instance.save()
+        return instance
+
+class PermissionSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Permission
+        fields = ('id', 'name',)
+
+class GroupSerializer(serializers.ModelSerializer):
+    employees = serializers.SerializerMethodField()
+    permissions = PermissionSerializer(many=True, read_only=True)
+
+    def get_employees(self, obj):
+        users = obj.user_set.all()
+        data = []
+        for user in users:
+            data.append(user.name)
+        return data
+
+    class Meta:
+        model = Group
+        fields = ('id', 'name', 'permissions', 'employees',)
+
+    def create(self, validated_data):
+        user = self.context['request'].user
+        validated_data['create_user'] = user
+        group = Group.objects.filter(name=validated_data['name']).first()
+        if group:
+            raise CustomError(u'名称为[%s]的权限组已存在' % validated_data['name'])
+
+        permissions = self.context['request'].data.get('permissions', None)
+        if permissions:
+            permissions = json.loads(permissions)
+        else:
+            permissions = []
+        instance = super(GroupSerializer, self).create(validated_data)
+        instance.permissions.set(permissions)
+        return instance
+
+    def update(self, instance, validated_data):
+        group = Group.objects.filter(name=validated_data['name']).exclude(id=instance.id).first()
+        if group:
+            raise CustomError(u'名称为[%s]的权限组已存在' % validated_data['name'])
+        permissions = self.context['request'].data.get('permissions', None)
+        if permissions:
+            permissions = json.loads(permissions)
+        else:
+            permissions = []
+        instance = super(GroupSerializer, self).update(instance, validated_data)
+        instance.permissions.set(permissions)
+        return instance
+
+class GroupDictSerializer(serializers.ModelSerializer):
+    value = serializers.CharField(source='id', read_only=True)
+
+    class Meta:
+        model = Group
+        fields = ('value', 'name',)
+
+class WechatLoginSerializer(serializers.Serializer):
+    def validate(self, attrs):
+        code = self.initial_data.get('code') # 用户code
+        appid = self.initial_data.get('appid') # 小程序appid
+        if code and appid:
+            customer_wechat = CustomerWechat.login(code, appid)
+            if not customer_wechat.customer:
+                return {
+                    'openid': customer_wechat.openid,
+                }
+
+            user = customer_wechat.customer
+            if not user.is_active:
+                msg = '用户帐户已禁用.'
+                raise serializers.ValidationError(msg)
+
+            payload = jwt_payload_handler(user)
+
+            return {
+                'user_id': user.id,
+                'token': jwt_encode_handler(payload),
+                'openid': customer_wechat.openid,
+                'name': customer_wechat.customer.name or '',
+                'tel': customer_wechat.customer.username or '',
+                'face': customer_wechat.customer.face,
+            }
+
+        else:
+            msg = '参数无效'
+            raise serializers.ValidationError(msg)
+
+class WechatBindSerializer(serializers.Serializer):
+    def validate(self, attrs):
+        appid = self.initial_data.get('appid')
+        openid = self.initial_data.get('openid')
+        phoneEncryptedData = self.initial_data.get('encryptedData')
+        phoneIv = self.initial_data.get('iv')
+
+        if openid and phoneEncryptedData and phoneIv:
+            customer = CustomerWechat.bindWechat(appid, openid, phoneEncryptedData, phoneIv)
+            payload = jwt_payload_handler(customer)
+            Balance.objects.get_or_create(create_user=customer)
+            return {
+                'token': jwt_encode_handler(payload),
+                'user_id': customer.id,
+                'name': customer.name or '',
+                'tel': customer.username or '',
+                'face': customer.face,
+            }
+
+        else:
+            msg = '参数无效'
+            raise serializers.ValidationError(msg)

+ 17 - 0
apps/account/urls.py

@@ -0,0 +1,17 @@
+from django.conf.urls import url
+from rest_framework.routers import SimpleRouter
+from apps.account.views import *
+
+urlpatterns = [
+    url(r'^login/$', LoginView.as_view()),
+    url(r'^token_refresh/$', RefreshTokenView.as_view()),
+    url(r'^employee/change_password/$', ChangePassword.as_view()),
+
+    url(r'^code2Session/$', WxLoginView.as_view()), # 自动登录
+    url(r'^setUserInfo/$', SetUserInfoView.as_view()),
+    url(r'^wxbind/$', WxBindView.as_view()), # 微信快捷登录
+]
+
+router = SimpleRouter()
+router.register(r'employee', EmployeeViewSet)
+urlpatterns += router.urls

+ 153 - 0
apps/account/views.py

@@ -0,0 +1,153 @@
+# coding=utf-8
+import traceback
+import json
+import datetime
+from django.db.models import Q
+from rest_framework.decorators import action
+from django.db import transaction
+from rest_framework.views import APIView
+from rest_framework.serializers import ValidationError
+from utils.permission import permission_required, isLogin, check_permission
+from django.contrib.auth.models import Group, Permission
+from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken
+from utils import response_error, response_ok
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+from apps.account.serializers import JWTSerializer, EmployeeSerializer, \
+    WechatLoginSerializer, WechatBindSerializer
+from utils.custom_modelviewset import CustomModelViewSet
+from apps.account.filters import UserFilter
+from apps.log.models import BizLog
+from utils.exceptions import CustomError
+from apps.account.models import CustomerWechat
+from utils.wx.WXBizDataCrypt import WXBizDataCrypt
+
+class LoginView(ObtainJSONWebToken):
+    serializer_class = JWTSerializer
+
+    def post(self, request, *args, **kwargs):
+        try:
+            ser = self.serializer_class(data=request.data)
+            ser.request = request
+            if ser.is_valid(raise_exception=True):
+                return response_ok(ser.validated_data)
+        except ValidationError as e:
+            return response_error(e.detail['error'][0])
+        except CustomError as e:
+            return response_error(str(e))
+
+class RefreshTokenView(RefreshJSONWebToken):
+
+    def post(self, request, *args, **kwargs):
+        try:
+            ser = self.serializer_class(data=request.data)
+            if ser.is_valid(raise_exception=True):
+                return response_ok({'token': ser.validated_data['token']})
+        except ValidationError as e:
+            return response_ok({'error':True})
+        except CustomError as e:
+            return response_error(str(e))
+
+class ChangePassword(APIView):
+
+    def post(self, request, *args, **kwargs):
+        id = request.GET.get('id')
+        data = json.loads(request.body)
+        try:
+            with transaction.atomic():
+                user = User.objects.filter(id=id).first()
+                if not user:
+                    raise CustomError(u'用户信息错误,请刷新重试!')
+                user.change_password(data['new_password'], data['confirm_password'], data['old_password'])
+                BizLog.objects.addnew(request.user, BizLog.UPDATE, u"修改密码[%s],id=%d" % (user.username, user.id))
+        except CustomError as e:
+            return response_error(str(e))
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(u'保存失败!')
+        return response_ok()
+
+
+class EmployeeViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = User.objects.filter(type=User.EMPLOYEE)
+    serializer_class = EmployeeSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter()
+        user = self.request.user
+        queryset = queryset.filter(
+            Q(id=user.id) |
+            Q(create_user=user)
+        )
+
+        f = UserFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def perform_create(self, serializer):
+        super(EmployeeViewSet, self).perform_create(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.INSERT,
+                              u'添加用户[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+    def perform_update(self, serializer):
+        super(EmployeeViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        BizLog.objects.addnew(self.request.user, BizLog.UPDATE,
+                              u'修改用户[%s],id=%d' % (instance.name, instance.id), validated_data)
+
+
+class SetUserInfoView(APIView):
+    permission_classes = [isLogin, ]
+
+    def post(self, request, *args, **kwargs):
+        appid = request.POST.get('appid')
+        openid = request.POST.get('openid')
+        encryptedData = request.POST.get('encryptedData')
+        iv = request.POST.get('iv')
+
+        customer_wechat = CustomerWechat.objects.filter(openid=openid, wechat_app__authorizer_appid=appid).first()
+        if not customer_wechat:
+            raise CustomError(u'未找到相应的微信客户!')
+
+        employee = self.request.user
+        if customer_wechat.customer and customer_wechat.customer != employee:
+            # 用户用手机号、密码登录后,同步微信信息时,customer可能会不一样
+            #张三、李四都是注册、同步用户。 张三的账号,在李四小程序上登录,同步的李四微信信息,可能会两个用户不同。
+            raise CustomError(u'该微信已同步其他客户!')
+        if not customer_wechat.customer:
+            customer_wechat.customer = employee
+            customer_wechat.save()
+        pc = WXBizDataCrypt(appid, customer_wechat.session_key)
+        result = pc.decrypt(encryptedData, iv)
+        with transaction.atomic():
+            if employee.name == employee.tel:
+                employee.name = result['nickName']
+            employee.gender = result['gender']
+            employee.face = result['avatarUrl']
+            employee.save()
+        return response_ok({'face':employee.face,'name':employee.name})
+
+class WxLoginView(APIView):
+    serializer_class = WechatLoginSerializer
+
+    def post(self, request, *args, **kwargs):
+        ser = self.serializer_class(data=request.data)
+        if ser.is_valid():
+            return response_ok(ser.validated_data)
+        else:
+            return response_error('参数错误')
+
+
+class WxBindView(APIView):
+    serializer_class = WechatBindSerializer
+
+    def post(self, request, *args, **kwargs):
+        ser = self.serializer_class(data=request.data)
+        if ser.is_valid():
+            return response_ok(ser.validated_data)
+        else:
+            return response_error('参数错误')

+ 0 - 0
apps/api/__init__.py


+ 10 - 0
apps/api/urls.py

@@ -0,0 +1,10 @@
+# coding=utf-8
+
+from django.conf.urls import url
+from .views import *
+urlpatterns = [
+    url(r'^callback_authorize/$', CallbackAuthorize.as_view()),
+    url('^(?P<appid>.+)/callback_event/$', CallbackEvent.as_view()),
+    url(r'^alipay_notify/$', AlipayNotifyView.as_view()),
+    url(r'^wechat_notify/(?P<appid>.+)/$', WechatNotifyView.as_view()),
+]

+ 154 - 0
apps/api/views.py

@@ -0,0 +1,154 @@
+# coding=utf-8
+
+import json
+import xmltodict
+import traceback
+from django.db import transaction
+from django.http import HttpResponse
+from rest_framework.views import APIView
+from django.conf import settings
+from utils.wx.WXBizMsgCrypt import WXBizMsgCrypt
+from utils.exceptions import CustomError
+from apps.order.models import Pay
+from utils.wechatpay import WechatPayNotify
+from utils import response_error, response_ok
+from apps.log.models import BizLog
+from apps.WechatTp.models import WechatTp
+from apps.WechatApplet.models import WechatApplet
+from alipay.aop.api.util.SignatureUtils import verify_with_rsa
+# from apps.WeChatResponse import WechatPayNotify
+
+class CallbackAuthorize(APIView):
+    '''验证票据(component_verify_ticket)在第三方平台创建审核通过后,微信服务器会向其 ”授权事件接收URL”
+    每隔 10 分钟以 POST 的方式推送 component_verify_ticket 接收 POST 请求后,只需直接返回字符串 success。
+    为了加强安全性,postdata 中的 xml 将使用服务申请时的加解密 key 来进行加密'''
+
+    def post(self, request):
+        sMsgSignature = request.GET.get('msg_signature')
+        sTimeStamp = request.GET.get('timestamp')
+        sNonce = request.GET.get('nonce')
+        sPostData = request.body.decode('utf-8')
+
+        try:
+            component = WechatTp.getDefault()
+            if component:
+                appid = component.getAppid()
+                msg_crypt = WXBizMsgCrypt(component.getToken(), component.getEncodeKey(), appid)
+                ret, decryp_xml = msg_crypt.DecryptMsg(sPostData, sMsgSignature, sTimeStamp, sNonce)
+                data = json.loads(json.dumps(xmltodict.parse(decryp_xml)))['xml']
+                if data['AppId'] == appid:
+                    if data['InfoType'] == 'component_verify_ticket':
+                        component.refreshVerifyTicket(data['ComponentVerifyTicket'])
+                    elif data['InfoType'] == 'unauthorized':
+                        authorizer_appid = data['AuthorizerAppid']
+                        authorizer = WechatApplet.getByAppidAndComponentAppid(authorizer_appid, appid)
+                        if authorizer:
+                            authorizer.revoke()
+        except:
+            pass
+
+        return HttpResponse('success')
+
+
+class CallbackEvent(APIView):
+    '''消息与事件接收'''
+
+    def post(self, request, appid):
+        appid = appid
+        sMsgSignature = request.GET.get('msg_signature')
+        sTimeStamp = request.GET.get('timestamp')
+        sNonce = request.GET.get('nonce')
+        sPostData = request.body.decode('utf-8')
+
+        try:
+            tp = WechatTp.getDefault()
+            msg_crypt = WXBizMsgCrypt(tp.getToken(), tp.getEncodeKey(), tp.getAppid())
+            ret, decryp_xml = msg_crypt.DecryptMsg(sPostData, sMsgSignature, sTimeStamp, sNonce)
+            data = json.loads(json.dumps(xmltodict.parse(decryp_xml)))['xml']
+            if data['MsgType'] == 'event':
+                app = WechatApplet.getByAppid(appid)
+                if data['Event'] == 'weapp_audit_success':  # 代码审核通过
+                    app.weapp_audit_success()
+                elif data['Event'] == 'weapp_audit_fail':  # 代码审核不通过
+                    app.weapp_audit_fail(data['Reason'])
+                elif data['Event'] == 'weapp_audit_delay':  # 代码审核延后
+                    app.weapp_audit_delay(data['Reason'])
+        except:
+            pass
+        return HttpResponse('success')
+
+class AlipayNotifyView(APIView):
+    def check_pay(self, params):
+        sign = params.pop('sign', None)
+        params.pop('sign_type')
+        params = sorted(params.items(), key=lambda e: e[0], reverse=False)
+        message = "&".join(u"{}={}".format(k, v) for k, v in params).encode()
+        try:
+            status = verify_with_rsa(settings.ALIPAY_SETTING['alipay_public_key'].encode('utf-8').decode('utf-8'), message,sign)
+            return status
+        except:
+            return False
+
+    def post(self, request):
+        params = request.POST.dict()
+        if not self.check_pay(params):
+            return HttpResponse('')
+
+
+        no = request.POST.get('out_trade_no')
+        app_id = request.POST.get('app_id')
+        notify_type = request.POST.get('notify_type')
+        trade_status = request.POST.get('trade_status')
+        amount = request.POST.get('total_amount')
+
+        if notify_type != 'trade_status_sync':
+            return HttpResponse('')
+
+        if app_id != settings.ALIPAY_SETTING['app_id']:
+            return HttpResponse('')
+
+        try:
+            with transaction.atomic():
+                pay = Pay.getByNo(no)
+                if trade_status == 'TRADE_CLOSED':
+                    pay.payClosed()
+                    BizLog.objects.addnew(None,pay.user, BizLog.INSERT, u'支付取消,pay_no=%s' % no,params)
+
+                if trade_status == 'TRADE_SUCCESS' or trade_status == 'TRADE_FINISHED':
+                    pay.paySuccess(float(amount))
+                    BizLog.objects.addnew(None,pay.user, BizLog.INSERT, u'支付宝支付成功,pay_no=%s' % no,params)
+        except:
+            import traceback
+            traceback.print_exc()
+            return HttpResponse('')
+        return HttpResponse('success')
+
+
+class WechatNotifyView(APIView):
+
+    def dispatch(self, request, *args, **kwargs):
+        param = request.body
+        appid = kwargs['appid']
+        applet = WechatApplet.getByAppid(appid)
+        # param = request.body.decode('utf-8')
+        notify = WechatPayNotify(param, applet.agent_key)
+        try:
+            data = notify.handle()
+            if not data:
+                raise CustomError(u'错误的请求!')
+
+            result_code = data['result_code']
+            if result_code != 'SUCCESS':
+                raise CustomError(u'错误的请求!')
+
+            no = data['out_trade_no']
+            amount = float(data['total_fee']) / 100.0
+
+            with transaction.atomic():
+                pay = Pay.getByNo(no)
+                pay.paySuccess(amount)
+                BizLog.objects.addnew(pay.user, BizLog.INSERT, u'微信支付成功,pay_no=%s' % no, param)
+        except Exception as e:
+            traceback.print_exc()
+            return HttpResponse(WechatPayNotify.response_fail())
+        return HttpResponse(WechatPayNotify.response_ok())

+ 53 - 0
apps/base.py

@@ -0,0 +1,53 @@
+#coding=utf-8
+
+class Formater():
+    @staticmethod
+    def formatStr(value):
+        res = u''
+        if value != None:
+            try:
+                res = str(value)
+            except:
+                pass
+        return res
+
+    @staticmethod
+    def formatCount(value):
+        return int(round(float(value or 0) * 100,0))
+
+    @staticmethod
+    def formatPrice(value):
+        return int(round(float(value or 0) * 100,0))
+
+    @staticmethod
+    def formatCountShow(value):
+        return '%.2f' % (float(value or 0)/100.0)
+
+    @staticmethod
+    def formatPriceShow(value):
+        return '%.2f' % (float(value or 0)/100.0)
+
+    @staticmethod
+    def formatAmount(value):
+        return int(round(float(value or 0) * 10000,0))
+
+    @staticmethod
+    def formatAmountShow(value):
+        return '%.2f' % (float(value or 0) / 10000.0 + 0.0000001)
+
+
+def clean_datetime_range(data, fieldname):
+    if data is not None and fieldname in data and data[fieldname] != '':
+        if data is not None:
+            t = data[fieldname].split(' - ')
+            # t = data[fieldname]
+            data = data.copy()
+            data[fieldname + '_0'] = t[0]+ ' 00:00:00'
+            data[fieldname + '_1'] = t[1] + ' 23:59:59'
+        else:
+            t = data[fieldname].split(' - ')
+            data = data.copy()
+            data[fieldname+'_0'] = t[0]
+            data[fieldname+'_1'] = t[1] + ' 23:59:59'
+        data.pop(fieldname)
+    return data

+ 0 - 0
apps/dashboard/__init__.py


+ 11 - 0
apps/dashboard/views.py

@@ -0,0 +1,11 @@
+#coding=utf-8
+
+from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
+
+def index(request):
+    user_id = request.META.get('HTTP_USER_ID')
+    token = request.META.get('HTTP_ACCESS_TOKEN')
+    if not user_id or not token:
+        return HttpResponseRedirect('/views/account/login.html')
+    else:
+        return HttpResponsePermanentRedirect("/views/index.html")

+ 0 - 0
apps/log/__init__.py


+ 20 - 0
apps/log/filters.py

@@ -0,0 +1,20 @@
+# coding=utf-8
+
+import django_filters
+
+from .models import BizLog
+
+from utils.format import clean_datetime_range
+
+
+class BizLogFilter(django_filters.FilterSet):
+    type = django_filters.ChoiceFilter(choices=BizLog.TYPE_CHOICES, field_name='type')
+    create_time = django_filters.DateTimeFromToRangeFilter(field_name='create_time')
+
+    class Meta:
+        model = BizLog
+        fields = ('create_time', 'type', )
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(BizLogFilter, self).__init__(data, *args, **kwargs)

+ 0 - 0
apps/log/migrations/__init__.py


+ 54 - 0
apps/log/models.py

@@ -0,0 +1,54 @@
+# coding=utf-8
+
+import json
+import datetime
+
+from django.db import models
+from django.utils import timezone
+from django.conf import settings
+from utils.format import strftime, strfdate
+
+class BizLogManager(models.Manager):
+    def addnew(self, user, type, description, data=None):
+        def default(o):
+            if isinstance(o, datetime.datetime):
+                return strftime(o)
+            elif isinstance(o, datetime.date):
+                return strfdate(o)
+
+        row = self.model(user=user, type=type, description=description)
+        if data:
+            row.data = json.dumps(data, default=default)
+        row.save()
+        return row
+
+class BizLog(models.Model):
+    INSERT = 1
+    UPDATE = 2
+    DELETE = 3
+    IMPORT = 4
+    TYPE_CHOICES = (
+        (INSERT, u'添加'),
+        (UPDATE, u'修改'),
+        (DELETE, u'删除'),
+        (IMPORT, u'导入'),
+    )
+    TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
+
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类别")
+    description = models.CharField(max_length=1000, verbose_name=u"内容")
+    data = models.TextField(verbose_name=u"数据", null=True, blank=True)
+    create_time = models.DateTimeField(verbose_name=u"添加时间", default=timezone.now, editable=False)
+
+    objects = BizLogManager()
+
+    class Meta:
+        db_table = "system_log"
+        ordering = ['-id']
+        index_together = (
+            'create_time',
+            'type',
+        )
+        verbose_name = u"系统日志"
+        default_permissions = ()

+ 9 - 0
apps/option/filters.py

@@ -0,0 +1,9 @@
+import django_filters
+from .models import *
+
+class ConfigFilter(django_filters.FilterSet):
+    id = django_filters.CharFilter(field_name='id')
+
+    class Meta:
+        model = Config
+        fields = '__all__'

+ 0 - 0
apps/option/migrations/__init__.py


+ 93 - 0
apps/option/models.py

@@ -0,0 +1,93 @@
+
+from django.db import models
+from django.conf import settings
+from django.db.models import F
+from PIL import Image
+from utils.file_operation import UploadFile
+
+class Config(models.Model):
+    LEAVE_LEAD_TIME = "leave_lead_time"  # 付款码支付__付款码图片
+
+    property = models.CharField(max_length=100, verbose_name='属性')
+    value = models.TextField(blank=True, max_length=250, null=True, verbose_name='值')
+
+    class Meta:
+        db_table = "system_config"
+        verbose_name = u"综合设置"
+        default_permissions = ()
+        permissions = [
+            ('manage_system_config', u'管理'),
+        ]
+
+    @staticmethod
+    def get_value(property):
+        if property in [Config.LEAVE_LEAD_TIME,]:
+            row = Config.objects.get(property=property)
+            return row.value
+        return ''
+
+class Balance(models.Model):
+    balance = models.IntegerField(verbose_name=u'余额', default=0)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u"创建人", related_name='balance_create_user',
+                                    on_delete=models.PROTECT, editable=False)
+
+    class Meta:
+        verbose_name = u"用户余额"
+        db_table = "user_balance"
+        ordering = ['-id']
+
+    @staticmethod
+    def update_balance(user, amount, desc):
+        Balance.objects.filter(create_user=user).update(balance=F('balance') + amount)
+        BalanceLog.objects.create(description=desc, amount=amount,create_user=user)
+
+class BalanceLog(models.Model):
+    description = models.CharField(verbose_name=u'描述', max_length=100)
+    amount = models.IntegerField(verbose_name=u'金额', default=0)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u"创建人", related_name='balance_log_create_user',
+                                    on_delete=models.PROTECT, editable=False)
+    create_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True, editable=False)
+
+    class Meta:
+        verbose_name = u"余额明细"
+        db_table = "user_balance_log"
+        ordering = ['-id']
+
+class Poster(models.Model):
+
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u"创建人", on_delete=models.PROTECT,
+                                    editable=False)
+    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
+    enable = models.BooleanField(verbose_name=u"在用", default=True)
+    image = models.TextField(verbose_name=u'图片路径', null=True)
+    width = models.IntegerField(verbose_name=u"图片宽度", null=True)
+    height = models.IntegerField(verbose_name=u"图片高度", null=True)
+
+    class Meta:
+        db_table = 'poster'
+        ordering = ['-id']
+        verbose_name = u'轮播广告'
+
+    def _add_img(self, file):
+        width = None
+        height = None
+
+        path = "poster/"
+        filename = UploadFile(file, path)
+        fullname = "%s%s" % (settings.MEDIA_ROOT, filename)
+
+        try:
+            img = Image.open(fullname)
+            width, height = img.size
+            # 缩略图压缩宽或高最大200
+            if width > 1440:
+                img = img.resize((1440, int((height / width) * 1440)), Image.ANTIALIAS)
+                img.save(fullname)
+                width, height = img.size
+        except:
+            pass
+
+        self.image = "%s%s" % (settings.MEDIA_URL, filename)
+        self.width = width
+        self.height = height
+        self.save()

+ 44 - 0
apps/option/serializers.py

@@ -0,0 +1,44 @@
+# coding=utf-8
+
+from rest_framework import serializers
+from .models import *
+from utils.booleancharfield import PriceShowCharField
+from apps.base import Formater
+
+class ConfigSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Config
+        fields = '__all__'
+
+class BalanceSerializer(serializers.ModelSerializer):
+    create_user_text = serializers.CharField(source='create_user.username', read_only=True)
+    balance = PriceShowCharField(read_only=True)
+    date_joined_f = serializers.DateTimeField(source='create_user.date_joined', format=settings.SHORT_DATETIME_FORMAT,
+                                              read_only=True)
+
+    class Meta:
+        model = Balance
+        fields = '__all__'
+
+class BalanceLogSerializer(serializers.ModelSerializer):
+    create_time_f = serializers.DateTimeField(source='create_time', format=settings.SHORT_DATETIME_FORMAT,
+                                              read_only=True)
+    create_user_text = serializers.CharField(source='create_user.username', read_only=True)
+    amount = PriceShowCharField(read_only=True)
+    balance = serializers.SerializerMethodField()
+
+    def get_balance(self, obj):
+        balance = Balance.objects.filter(create_user=obj.create_user).first()
+        return Formater.formatPriceShow(balance.balance)
+
+    class Meta:
+        model = BalanceLog
+        fields = '__all__'
+
+class PosterSerializer(serializers.ModelSerializer):
+    create_user_name = serializers.CharField(source='create_user.employee.name', read_only=True)
+
+    class Meta:
+        model = Poster
+        fields = '__all__'

+ 15 - 0
apps/option/urls.py

@@ -0,0 +1,15 @@
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'getPoster/$', PosterView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'config',ConfigViewSet)
+router.register(r'balance_log',BalanceLogViewSet)
+router.register(r'balance',BalanceViewSet)
+router.register(r'poster',PosterViewSet)
+urlpatterns += router.urls

+ 129 - 0
apps/option/views.py

@@ -0,0 +1,129 @@
+from utils.custom_modelviewset import CustomModelViewSet
+from django.db import transaction
+import json
+from django.db.models import Sum
+from rest_framework.decorators import action
+from .serializers import *
+from .filters import *
+from rest_framework.views import APIView
+from apps.log.models import BizLog
+from utils import response_ok, response_error
+from utils.exceptions import CustomError
+from utils.permission import isLogin
+from django.contrib.auth import get_user_model
+User = get_user_model()
+from utils.file_operation import attachment_save
+
+class ConfigViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = Config.objects.filter()
+    serializer_class = ConfigSerializer
+
+    def list(self, request, *args, **kwargs):
+        queryset = self.filter_queryset(self.get_queryset())
+        serializer = self.get_serializer(queryset, many=True)
+        return response_ok(serializer.data)
+
+    def create(self, request, *args, **kwargs):
+        try:
+            data = json.loads(request.POST.get('data'))
+            pay_picture = request.FILES.get('pay_picture')
+            keys = ['leave_lead_time', ]
+            with transaction.atomic():
+                for item in data:
+                    config = Config.objects.filter(property=item['key']).first()
+                    if item['value']:
+                        if item['key'] not in keys:
+                            raise CustomError(u'综合设置属性[%s]不存在' % item['key'])
+                        else:
+                            item['value'] = item['value'].strip()
+                        if config:
+                            config.value = item['value']
+                            config.save()
+                        else:
+                            Config.objects.create(property=item['key'], value=item['value'])
+
+                BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'修改系统配置', data)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            return response_error(str(e))
+        return response_ok()
+
+class BalanceViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = Balance.objects.filter()
+    serializer_class = BalanceSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter()
+        if self.request.user.type == User.CUSTOMER:
+            queryset = queryset.filter(create_user=self.request.user)
+        return queryset
+
+    @action(methods=['post'], detail=False)
+    def clear_balance(self, request):
+        create_user = request.GET.get('create_user')
+        reason = request.POST.get('reason')
+        instance = Balance.objects.filter(create_user_id=create_user).first()
+        try:
+            if not reason:
+                raise CustomError('请填写清空原因!')
+            if not instance:
+                raise CustomError('学生信息错误,请刷新重试!')
+            with transaction.atomic():
+                if instance.balance <= 0:
+                    raise CustomError('余额为0,不能清空!')
+                desc = '清空余额,原因:{}'.format(reason)
+                Balance.update_balance(instance.create_user, -instance.balance, desc)
+                instance.balance = 0
+                instance.save()
+                BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'清空[{}]余额, id={}'.
+                                      format(instance.create_user.username, instance.id), )
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            return response_error(str(e))
+        return response_ok()
+
+class BalanceLogViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = BalanceLog.objects.filter()
+    serializer_class = BalanceLogSerializer
+
+    def filter_queryset(self, queryset):
+        description = self.request.GET.get('description')
+        queryset = queryset.filter()
+        if self.request.user.type == User.CUSTOMER:
+            queryset = queryset.filter(create_user=self.request.user)
+        if description:
+            queryset = queryset.filter(description__icontains=description)
+        return queryset
+
+class PosterView(APIView):
+
+    def get(self, request):
+        data = []
+        queryset = Poster.objects.filter()
+        for row in queryset:
+            data.append(settings.SERVER_DOMAIN + row.image)
+        return response_ok(data)
+
+class PosterViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = Poster.objects.filter()
+    serializer_class = PosterSerializer
+
+    def create(self, request, *args, **kwargs):
+        banner_img = request.FILES.get('banner_img')
+        try:
+            with transaction.atomic():
+                poster = Poster.objects.create(create_user=request.user)
+                poster._add_img(banner_img)
+                BizLog.objects.addnew(self.request.user, BizLog.INSERT, u'添加轮播信息, id={}'.
+                                      format(poster.id), )
+            return response_ok()
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            return response_error(str(e))

+ 1 - 0
apps/order/__init__.py

@@ -0,0 +1 @@
+# coding=utf-8

+ 26 - 0
apps/order/filters.py

@@ -0,0 +1,26 @@
+# coding=utf-8
+import django_filters
+
+from .models import *
+from apps.base import clean_datetime_range
+
+class PackageFilter(django_filters.FilterSet):
+
+    class Meta:
+        model = Package
+        fields = '__all__'
+
+class OrderFilter(django_filters.FilterSet):
+    status = django_filters.CharFilter(field_name='status')
+    no = django_filters.CharFilter(field_name='no', lookup_expr='icontains')
+    tel = django_filters.CharFilter(field_name='create_user__username', lookup_expr='icontains')
+    create_time_0 = django_filters.DateTimeFilter(field_name='create_time', lookup_expr='gte')
+    create_time_1 = django_filters.DateTimeFilter(field_name='create_time', lookup_expr='lte')
+
+    class Meta:
+        model = Order
+        fields = '__all__'
+
+    def __init__(self, data=None, *args, **kwargs):
+        data = clean_datetime_range(data, 'create_time')
+        super(OrderFilter, self).__init__(data, *args, **kwargs)

+ 1 - 0
apps/order/migrations/__init__.py

@@ -0,0 +1 @@
+# coding=utf-8

+ 158 - 0
apps/order/models.py

@@ -0,0 +1,158 @@
+# coding=utf-8
+from django.db import models
+from django.conf import settings
+from django.utils import timezone
+from utils.exceptions import CustomError
+from apps.base import Formater
+from apps.option.models import Balance
+from apps.log.models import BizLog
+from django.contrib.auth import get_user_model
+User = get_user_model()
+
+class Pay(models.Model):
+    WAIT = 0
+    PAY = 1
+    UNDO = 2
+    STATUS_CHOICES = (
+        (WAIT, u'待付款'),
+        (PAY, u'已付款'),
+        (UNDO, u'已取消'),
+    )
+    CUSTOMER = 1
+    TYPE_CHOICES = (
+        (CUSTOMER, u'客户'),
+    )
+
+    pay_no = models.CharField(max_length=25, verbose_name='支付单号', unique=True, null=True)
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name='状态', default=WAIT)
+    type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name='类别', default=CUSTOMER)
+    precreate_amount = models.BigIntegerField(verbose_name=u"预支付金额")
+    create_time = models.DateTimeField(verbose_name=u"创建时间", default=timezone.now)
+    amount = models.BigIntegerField(verbose_name=u"支付金额", null=True)
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'下单人', related_name='pay_user',
+                             on_delete=models.PROTECT)
+    pay_time = models.DateTimeField(verbose_name=u"创建时间", null=True)
+
+    class Meta:
+        db_table = "pay"
+        verbose_name = u"支付信息"
+        ordering = ('-id',)
+        default_permissions = ()
+
+    def payClosed(self):
+        if self.status != Pay.WAIT:
+            return
+
+        self.status = Pay.UNDO
+        self.save()
+
+    def paySuccess(self, pay_amount):
+        if self.status != Pay.WAIT:
+            return
+
+        self.status = Pay.PAY
+        self.amount = Formater.formatPrice(pay_amount)
+        self.pay_time = timezone.now()
+        self.save()
+        order = Order.objects.filter(pay=self).first()
+        if order:
+            order.status = Order.FINISH
+            order.actual_amount = self.amount
+
+            if order.package:
+                desc = '充值{}元,赠送{}元'.format(Formater.formatPriceShow(order.package.amount),
+                                            Formater.formatPriceShow(order.package.give_amount))
+                Balance.update_balance(order.create_user, order.package.amount+order.package.give_amount, desc)
+            order.save()
+
+    @staticmethod
+    def getByNo(pay_no):
+        instance = Pay.objects.filter(pay_no=pay_no).first()
+        if not instance:
+            raise CustomError(u'未找到相应的支付单号!')
+        return instance
+
+    # 小程序支付
+    @staticmethod
+    def wechatAppPay(user, amount, type=1):
+        item = Pay._precreatePay(
+            user,
+            amount,
+            type,
+        )
+        return item
+
+    @staticmethod
+    def _precreatePay(user, amount, type):
+
+        pay_no = '{0}{1}'.format(user.id, timezone.now().strftime('%y%m%d%H%M%S'))
+        pay = Pay.objects.create(
+            pay_no=pay_no,
+            status=Pay.WAIT,
+            precreate_amount=amount,
+            user=user,
+            type=type,
+        )
+        return pay
+
+class Package(models.Model):
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='package_create_user', verbose_name=u"添加人",
+                                    on_delete=models.PROTECT, editable=False)
+    create_time = models.DateTimeField(verbose_name=u'添加时间', auto_now_add=True, editable=False)
+    amount = models.BigIntegerField(verbose_name=u'充值金额', default=0)
+    give_amount = models.BigIntegerField(verbose_name=u'赠送余额', default=0)
+
+    class Meta:
+        db_table = "package"
+        verbose_name = u"套餐"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = [
+        ]
+
+class Order(models.Model):
+    WAIT_PAY = 1
+    FINISH = 2
+    CANCEL = 3
+    STATUS_CHOICES = (
+        (WAIT_PAY, u'待付款'),
+        (FINISH, u'已完成'),
+        (CANCEL, u'已取消'),
+    )
+
+    no = models.CharField(max_length=50, verbose_name=u'订单号', blank=True)
+    pay = models.ForeignKey(Pay, verbose_name='支付信息', on_delete=models.PROTECT, null=True, editable=False)
+    package = models.ForeignKey(Package, verbose_name='套餐', on_delete=models.PROTECT, null=True, editable=False)
+
+    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u"订单状态", default=WAIT_PAY)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='order_create_user', verbose_name=u"添加人",
+                                    on_delete=models.PROTECT, editable=False)
+    create_time = models.DateTimeField(verbose_name=u'添加时间', auto_now_add=True, editable=False)
+    total_amount = models.BigIntegerField(verbose_name=u'总金额', default=0)
+    balance_deduction = models.BigIntegerField(verbose_name=u'余额抵扣', editable=False, default=0)
+    actual_amount = models.BigIntegerField(verbose_name=u'实付金额', editable=False, default=0)
+
+    class Meta:
+        db_table = "order"
+        verbose_name = u"订单管理"
+        ordering = ('-id',)
+        default_permissions = ()
+        permissions = [
+        ]
+
+    def get_no(self):
+        now = timezone.now()
+        no = '%s%s' % (self.create_user.id, now.strftime('%Y%m%d%H%M%S'))
+        return no
+
+    @staticmethod
+    def get_instance_by_id(id):
+        try:
+            id = int(id)
+        except:
+            raise CustomError('无效的订单ID')
+        instance = Order.objects.filter(pk=id).first()
+        if not instance:
+            raise CustomError('未找到对应的订单')
+        return instance
+

+ 54 - 0
apps/order/serializers.py

@@ -0,0 +1,54 @@
+# coding=utf-8
+import datetime
+from rest_framework import serializers
+from .models import *
+from django.conf import settings
+from utils.booleancharfield import PriceShowCharField
+from apps.base import Formater
+
+class PackageSerializer(serializers.ModelSerializer):
+    create_user_text = serializers.CharField(source='create_user.username', read_only=True)
+    create_time_f = serializers.DateTimeField(source='create_time', format=settings.SHORT_DATETIME_FORMAT,
+                                              read_only=True)
+    amount = PriceShowCharField(read_only=True)
+    give_amount = PriceShowCharField(read_only=True)
+
+    class Meta:
+        model = Package
+        fields = '__all__'
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        validated_data['amount'] = Formater.formatPrice(self.initial_data['amount'])
+        validated_data['give_amount'] = Formater.formatPrice(self.initial_data['give_amount'])
+        instance = super(PackageSerializer, self).create(validated_data)
+        instance.save()
+        return instance
+
+    def update(self, instance, validated_data):
+        validated_data['amount'] = Formater.formatPrice(self.initial_data['amount'])
+        validated_data['give_amount'] = Formater.formatPrice(self.initial_data['give_amount'])
+        instance = super(PackageSerializer, self).update(instance, validated_data)
+        instance.save()
+        return instance
+
+class OrderSerializer(serializers.ModelSerializer):
+    create_user_text = serializers.CharField(source='create_user.username', read_only=True)
+    create_time_f = serializers.DateTimeField(source='create_time', format=settings.SHORT_DATETIME_FORMAT,
+                                              read_only=True)
+    status_text = serializers.CharField(source='get_status_display', read_only=True)
+
+    total_amount = PriceShowCharField(read_only=True)
+    balance_deduction = PriceShowCharField(read_only=True)
+    actual_amount = PriceShowCharField(read_only=True)
+    class Meta:
+        model = Order
+        fields = '__all__'
+
+    def create(self, validated_data):
+        validated_data['create_user'] = self.context['request'].user
+        validated_data['total_amount'] = abs(Formater.formatPrice(self.initial_data['total_amount']))
+        instance = super(OrderSerializer, self).create(validated_data)
+        instance.no = instance.get_no()
+        instance.save()
+        return instance

+ 14 - 0
apps/order/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'packageDict/$', PackageView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'package', PackageViewSet)
+router.register(r'', OrderViewSet)
+urlpatterns += router.urls

+ 127 - 0
apps/order/views.py

@@ -0,0 +1,127 @@
+# coding=utf-8
+import json
+from django.db.models import Sum, F
+from django.db import transaction
+from django.db.models import Q
+from rest_framework.views import APIView
+from utils.custom_modelviewset import CustomModelViewSet
+from .serializers import *
+from .filters import *
+from apps.log.models import BizLog
+from apps.base import Formater
+from utils import response_ok, response_error
+from utils.permission import isLogin
+from apps.order.models import Order
+from apps.WeChatResponse import WechatAppletPay
+from apps.option.models import Balance
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+
+
+class PackageView(APIView):
+
+    def get(self, request):
+        data = []
+        queryset = Package.objects.filter()
+        for row in queryset:
+            item = {
+                'id':row.id,
+                'amount':Formater.formatPriceShow(row.amount),
+                'give_amount':Formater.formatPriceShow(row.give_amount),
+            }
+            data.append(item)
+        return response_ok(data)
+
+class PackageViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = Package.objects.filter()
+    serializer_class = PackageSerializer
+
+    def filter_queryset(self, queryset):
+        f = PackageFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+class OrderViewSet(CustomModelViewSet):
+    permission_classes = [isLogin, ]
+    queryset = Order.objects.filter()
+    serializer_class = OrderSerializer
+
+    def list(self, request, *args, **kwargs):
+        # 底栏合计
+        queryset = self.filter_queryset(self.get_queryset())
+        total = queryset.aggregate(total_amount=Sum('total_amount'), balance_deduction=Sum('balance_deduction'),
+                                   actual_amount=Sum('actual_amount'))
+        totalRow = {'totalRow': 1,
+                    'total_amount': Formater.formatPriceShow(total['total_amount']),
+                    'balance_deduction': Formater.formatPriceShow(total['balance_deduction']),
+                    'actual_amount': Formater.formatPriceShow(total['actual_amount'], )}
+        page = self.paginate_queryset(queryset)
+        if page is not None:
+            serializer = self.get_serializer(page, many=True)
+            data = serializer.data
+            if len(data) > 0:
+                data.append(totalRow)
+            return self.get_paginated_response(data)
+
+        serializer = self.get_serializer(queryset, many=True)
+        return response_ok(serializer.data)
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter()
+        f = OrderFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+    def create(self, request, *args, **kwargs):
+        openid = request.POST.get('openid')
+        appid = request.POST.get('appid')
+        package = request.POST.get('package') or ''
+        try:
+            with transaction.atomic():
+                serializer = self.get_serializer(data=request.data)
+                if serializer.is_valid(raise_exception=True):
+                    instance = serializer.save()
+                    total_amount = instance.total_amount
+
+                    # 充值套餐
+                    if package:
+                        package = Package.objects.filter(id=package).first()
+                        if package:
+                            instance.package = package
+                            actual_amount = package.amount
+                        else:
+                            raise CustomError('套餐错误,请刷新重试!')
+                    else:
+                        # 余额抵扣
+                        balance = Balance.objects.filter(create_user=request.user).first()
+                        if balance and balance.balance > 0:
+                            if balance.balance >= total_amount:
+                                instance.balance_deduction = total_amount
+                                actual_amount = 0
+                                instance.status = Order.FINISH
+
+                                desc = '支付抵扣{}元'.format(Formater.formatPriceShow(total_amount))
+                                Balance.update_balance(instance.create_user, -instance.balance_deduction, desc)
+                            else:
+                                raise CustomError('余额不足,请充值!')
+                        else:
+                            raise CustomError('余额不足,请充值!')
+                    data = ''
+                    if actual_amount:
+                        # 小程序在线支付
+                        pay = Pay.wechatAppPay(self.request.user, actual_amount, Pay.CUSTOMER)
+                        wechatpay = WechatAppletPay(appid)
+                        data = wechatpay.weChatUnifiedOrder(openid, pay.pay_no,
+                                                            Formater.formatPriceShow(actual_amount))
+                        instance.pay = pay
+
+                    instance.save()
+                    BizLog.objects.addnew(self.request.user, BizLog.INSERT,
+                                          u'添加订单[%s],id=%d' % (instance.no, instance.id), request.data)
+                    return response_ok(data)
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            import traceback
+            traceback.print_exc()
+            return response_error(str(e))

+ 184 - 0
apps/serializer_errors.py

@@ -0,0 +1,184 @@
+#coding=utf-8
+
+from rest_framework import fields
+
+def dump_serializer_errors(serializer):
+    error_messages = {
+        u'blank': u'此字段不能为空',
+        u'required': u'此字段必填',
+        u'max_length':u'此字段不超过%d个字符',
+        u'min_length':u'此字段不少于%d个字符',
+        u'null': u'该字段不能为空'  # This field may not be null
+    }
+    field_error_messages = {
+        fields.BooleanField:{
+            u'invalid': u'此字段不是一个合法的布尔值'
+        },
+        fields.CharField: {
+            u'invalid': u'此字段不是一个合法的字符串'
+        },
+        fields.NullBooleanField: {
+            u'invalid': u'此字段不是一个合法的布尔值'
+        },
+        fields.EmailField: {
+            u'invalid': u'邮箱格式不正确'
+        },
+        fields.RegexField: {
+            u'invalid': u'此字段没有匹配的一个必要的模板'
+        },
+        fields.SlugField: {
+            u'invalid': u'此字段不是一个合法的slug,应该由字母,数字,下划线,连字符组成',
+            u'invalid_unicode': u'此字段不是一个合法的slug,应该由unicode字母,数字,下划线,连字符组成'
+        },
+        fields.URLField: {
+            u'invalid': u'此字段不是一个合法的URL'
+        },
+        fields.UUIDField: {
+            u'invalid': u'此字段不是一个合法的UUID'
+        },
+        fields.IPAddressField: {
+            u'invalid': u'此字段不是一个合法的IPv4或者IPv6地址'
+        },
+        fields.IntegerField: {
+            u'invalid': u'此字段不是一个合法的整数',
+            u'max_value': u'此字段的数值要小于等于%d',
+            u'min_value': u'此字段的数值要大于等于%d',
+            u'max_string_length': u'此字段长度超过1000',
+        },
+        fields.FloatField: {
+            u'invalid': u'此字段不是一个合法的浮点数',
+            u'max_value': u'此字段的数值要小于等于%d',
+            u'min_value': u'此字段的数值要大于等于%d',
+            u'max_string_length': u'此字段长度超过1000',
+        },
+        fields.DecimalField: {
+            u'invalid': u'此字段不是一个合法的数字',
+            u'max_value': u'此字段的数值要小于等于%d',
+            u'min_value': u'此字段的数值要大于等于%d',
+            u'max_digits': u'此字段的数值不超过%d位',
+            u'max_decimal_places': u'此字段十进制位不超过%d',
+            u'max_whole_digits': u'此字段小数点前的数字不超过%d。',
+            u'max_string_length': u'此字段长度超过1000',
+        },
+        fields.DateTimeField: {
+            u'invalid': u'日期时间格式不合法',
+            u'date': u'不能只输入日期,应输入日期和时间',
+            u'make_aware': u'时区%d不合法',
+            u'overflow': u'日期时间超出范围',
+        },
+        fields.DateField: {
+            u'invalid': u'日期格式不合法',
+            u'date': u'应输入日期,不能输入日期和时间',
+        },
+        fields.TimeField: {
+            u'invalid': u'时间格式不合法',
+        },
+        fields.DurationField: {
+            u'invalid': u'时间段格式不合法,应该用格式:%d',
+        },
+        fields.ChoiceField: {
+            u'invalid_choice': u'此字段不是一个合法的选项',
+        },
+        fields.MultipleChoiceField: {
+            u'invalid_choice': u'此字段不是一个合法的选项',
+            u'not_a_list': u'应输入数组(list)格式',
+            u'empty': u'此选项不能为空',
+        },
+        fields.FilePathField: {
+            u'invalid_choice': u'此字段不是一个合法的路径',
+        },
+        fields.FileField: {
+            u'required': u'没有提交文件',
+            u'invalid': u'提交的不是文件,请检查表单的编码类型',
+            u'no_name': u'没有文件名',
+            u'empty': u'提交的是一个空文件',
+            u'max_length': u'文件内容要小于%d个字符',
+        },
+        fields.ImageField: {
+            u'invalid_image': u'提交的不是图片文件或者图片文件损坏,请提交一个合法的图片文件',
+        },
+        fields.ListField: {
+            u'not_a_list': u'此字段应传入数组(list)类型',
+            u'empty': u'传入的list不能为空',
+            u'min_length': u'该字段至少要%d个元素',
+            u'max_length': u'该字段最多能有%d个元素',
+        },
+        fields.DictField: {
+            u'not_a_dict': u'此字段应传入字典(dict)类型',
+        },
+        fields.JSONField: {
+            u'invalid': u'此字段不是一个合法的JSON类型'
+        },
+        fields.ModelField: {
+            u'max_length': u'此字段长度要小于等于%d个字符'
+        }
+    }
+
+    result = [u'数据错误<br />', ]
+    for i in range(0, len(serializer.errors.keys())):
+        k = serializer.errors.keys()[i]
+        v = serializer.errors.values()[i]
+        field = serializer.fields.get(k)
+
+        label = field.label
+        if label:
+            label += ' - '
+        else:
+            label = ''
+
+        error_detail = v[0]
+        try:
+            msg = field_error_messages[type(field)][error_detail.code]
+            if type(field) == fields.IntegerField or type(field) == fields.FloatField:
+                if error_detail.code == u'max_value':
+                    msg = msg % field.max_value
+                if error_detail.code == u'min_value':
+                    msg = msg % field.min_value
+
+            if type(field) == fields.DecimalField:
+                if error_detail.code == u'max_value':
+                    msg = msg % field.max_value
+                if error_detail.code == u'min_value':
+                    msg = msg % field.min_value
+                if error_detail.code == u'max_digits':
+                    msg = msg % field.max_digits
+                if error_detail.code == u'max_decimal_places':
+                    msg = msg % field.decimal_places
+                if error_detail.code == u'max_whole_digits':
+                    msg = msg % field.max_whole_digits
+
+            if type(field) == fields.DateTimeField:
+                if error_detail.code == u'make_aware':
+                    msg = msg % field.timezone
+
+            if type(field) == fields.DurationField:
+                if error_detail.code == u'invalid':
+                    msg = msg % u'[DD] [HH:[MM:]]ss[.uuuuuu]'
+
+            if type(field) == fields.DateTimeField:
+                if error_detail.code == u'make_aware':
+                    msg = msg % field.timezone
+
+            if type(field) == fields.ListField:
+                if error_detail.code == u'min_length':
+                    msg = msg % field.min_length
+                if error_detail.code == u'max_length':
+                    msg = msg % field.max_length
+
+            if type(field) == fields.ModelField:
+                if error_detail.code == u'max_length':
+                    msg = msg % field.max_length
+
+        except:
+            try:
+                msg = error_messages[error_detail.code]
+                if error_detail.code == u'max_length':
+                    msg = msg % field.max_length
+                if error_detail.code == u'min_length':
+                    msg = msg % field.min_length
+
+            except:
+                msg = str(error_detail)
+        msg = label + msg
+        result.append(msg)
+    return u'<br />'.join(result)

+ 21 - 0
manage.py

@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nostone_loan.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 0 - 0
nostone_loan/__init__.py


+ 262 - 0
nostone_loan/settings.py

@@ -0,0 +1,262 @@
+# coding=utf-8
+"""
+Django settings for ly_baoxiu_admin project.
+
+Generated by 'django-admin startproject' using Django 2.2.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/2.2/ref/settings/
+"""
+
+import os, datetime
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'c+)5afbc(m&qe^8t7gc54w6f-*==b2(l0(8rqef-cbwtv*3n$w'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+
+# Application definition
+
+INSTALLED_APPS = [
+    # 'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+
+    'corsheaders',
+    'rest_framework',
+    'rest_framework_jwt',
+    'django_filters',
+    'apps.account',
+    'apps.log',
+    'apps.WechatApplet',
+    'apps.WechatTp',
+    'apps.api',
+    'apps.option',
+    'apps.order',
+
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'corsheaders.middleware.CorsMiddleware',
+]
+
+CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_ALLOW_METHODS = (
+    'DELETE',
+    'GET',
+    'OPTIONS',
+    'PATCH',
+    'POST',
+    'PUT',
+    'VIEW',
+)
+CORS_ALLOW_HEADERS = (
+    'XMLHttpRequest',
+    'X_FILENAME',
+    'accept-encoding',
+    'authorization',
+    'content-type',
+    'dnt',
+    'origin',
+    'token',
+    'user-agent',
+    'x-csrftoken',
+    'x-requested-with',
+    'Pragma',
+)
+
+SHORT_DATETIME_FORMAT = '%Y-%m-%d %H:%M'
+
+ROOT_URLCONF = 'nostone_loan.urls'
+AUTH_USER_MODEL = "account.User"
+AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'nostone_loan.wsgi.application'
+
+# Database
+# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.mysql',
+        'NAME': 'nostone_loan',
+        'USER': 'carwin',
+        'PASSWORD': 'carwin!@#',
+        'HOST': '39.106.109.89',
+        'PORT': 3306,
+    },
+}
+# 配置缓存为redis
+# windowns 系统,需要安装Redis-x64-3.0.503.msi,并启动服务。
+# CACHES = {
+#     'default': {
+#         'BACKEND': 'django_redis.cache.RedisCache',
+#         'LOCATION': 'redis://127.0.0.1:6379',
+#         "OPTIONS": {
+#             "CLIENT_CLASS": "django_redis.client.DefaultClient",
+#         },
+#     },
+# }
+
+# DRF扩展
+REST_FRAMEWORK_EXTENSIONS = {
+    # 缓存时间
+    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60 * 24,  # 单位s,一天
+    # 缓存存储
+    'DEFAULT_USE_CACHE': 'default',
+}
+
+# Password validation
+# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+FEMALE = 1
+MALE = 2
+GENDER_CHOICES = (
+    (FEMALE, u'女'),
+    (MALE, u'男'),
+)
+
+DEFAULT = 0
+PASS = 1
+CHECK_STATE_CHOICES = (
+    (DEFAULT, u'待审核'),
+    (PASS, u'已审核'),
+)
+
+OFFLINE = 0
+ONLINE = 1
+SALES_STATUS_CHOICES = (
+    (OFFLINE, u'下架'),
+    (ONLINE, u'上架'),
+)
+
+# Internationalization
+# https://docs.djangoproject.com/en/2.2/topics/i18n/
+
+LANGUAGE_CODE = 'zh-hans'
+
+TIME_ZONE = 'Asia/Shanghai'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = False
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/2.2/howto/static-files/
+
+STATIC_URL = '/static/'
+STATIC_ROOT = os.path.join(BASE_DIR, "uis/static/")
+STATICFILES_DIRS = (
+    os.path.join(BASE_DIR, "static"),
+)
+
+MEDIA_URL = '/up/'
+MEDIA_ROOT = os.path.join(BASE_DIR, "uis/up/")
+
+UIS_URL = '/'
+UIS_ROOT = os.path.join(BASE_DIR, "uis/")
+
+EXPORT_URL = '/export/'
+EXPORT_ROOT = os.path.join(BASE_DIR, "export/")
+
+SERVER_DOMAIN = 'https://print.zzliaoyuan.com'
+# SERVER_DOMAIN = 'http://192.168.2.45:8887'
+
+JWT_AUTH = {
+    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30),
+    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=360),
+    'JWT_ALLOW_REFRESH': True,
+}
+
+REST_FRAMEWORK = {
+    'DEFAULT_PERMISSION_CLASSES': (
+        'rest_framework.permissions.AllowAny',
+    ),
+    'DEFAULT_AUTHENTICATION_CLASSES': (
+        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
+        # 'rest_framework.authentication.SessionAuthentication',
+        # 'rest_framework.authentication.BasicAuthentication',
+    ),
+    'DEFAULT_PARSER_CLASSES': (
+        'rest_framework.parsers.JSONParser',
+        'rest_framework.parsers.FormParser',
+        'rest_framework.parsers.MultiPartParser'
+    ),
+    'DEFAULT_RENDERER_CLASSES': (
+        'rest_framework.renderers.JSONRenderer',
+        # 'rest_framework.renderers.BrowsableAPIRenderer',
+    ),
+    'DEFAULT_FILTER_BACKENDS': (
+        'django_filters.rest_framework.DjangoFilterBackend',
+    ),
+    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+    'DEFAULT_PAGINATION_CLASS': 'utils.pagination.CustomPagination',
+    # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
+    'EXCEPTION_HANDLER': 'utils.handler.custom_exception_handler',
+    'PAGE_SIZE': 10,
+    'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
+    'NON_FIELD_ERRORS_KEY': "error",  # 序列化器错误KEY名称
+}
+# 数据备份地址
+BACKUP_ROOT = '/var/db_backups_lsr/'
+# 导入本地设置
+try:
+    from nostone_loan.local_settings import *
+except ImportError:
+    pass

+ 36 - 0
nostone_loan/urls.py

@@ -0,0 +1,36 @@
+# coding=utf-8
+"""hagrid URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/2.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+
+from django.conf.urls import url, include
+from django.conf.urls.static import static
+from django.conf import settings
+
+from apps.dashboard.views import index
+
+urlpatterns = [
+    url(r'^$', index),
+    url(r'^account/', include('apps.account.urls')),
+    url(r'^api/', include('apps.api.urls')),
+    url(r'^option/', include('apps.option.urls')),
+    url(r'^order/', include('apps.order.urls')),
+
+]
+
+urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+urlpatterns += static(settings.EXPORT_URL, document_root=settings.EXPORT_ROOT)
+urlpatterns += static(settings.UIS_URL, document_root=settings.UIS_ROOT)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

+ 12 - 0
nostone_loan/wsgi.ini

@@ -0,0 +1,12 @@
+[uwsgi]
+socket = 0.0.0.0:9001
+http-keepalive = 1
+chdir = /var/www/nostone_loan
+pythonpath = /var/www/nostone_loan/nostone_loan/
+module = wsgi
+master = 1
+processes = 2
+daemonize = /var/log/uwsgi/nostone_loan.log
+buffer-size = 16384
+harakiri = 360
+log-maxsize = 256000000

+ 16 - 0
nostone_loan/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for haly_baoxiu_admingrid project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nostone_loan.settings')
+
+application = get_wsgi_application()

+ 17 - 0
requirements

@@ -0,0 +1,17 @@
+django==2.2.5
+django-filter
+djangorestframework
+djangorestframework-jwt
+django-cors-headers
+mysqlclient
+Pillow
+pycryptodome
+requests
+xmltodict
+tablib
+openpyxl
+alipay-sdk-python
+drf-extensions
+django-redis
+django-redis-cache
+django-import-export

BIN
static/imgs/aliPay.png


BIN
static/imgs/wechart.png


+ 95 - 0
uis/layuiadmin/config.js

@@ -0,0 +1,95 @@
+/**
+
+ @Name:layuiAdmin iframe版全局配置
+ @Author:贤心
+ @Site:http://www.layui.com/admin/
+ @License:LPPL(layui付费产品协议)
+    
+ */
+ 
+layui.define(['laytpl', 'layer', 'element', 'util'], function(exports){
+  exports('setter', {
+    container: 'LAY_app' //容器ID
+    ,base: layui.cache.base //记录静态资源所在路径
+    ,views: layui.cache.base + 'tpl/' //动态模板所在目录
+    ,entry: 'index' //默认视图文件名
+    ,engine: '.html' //视图文件后缀名
+    ,pageTabs: true //是否开启页面选项卡功能。iframe 常规版推荐开启
+    
+    ,name: 'layuiAdmin'
+    ,tableName: 'layuiAdmin' //本地存储表名
+    ,MOD_NAME: 'admin' //模块事件名
+    
+    ,debug: false //是否开启调试模式。如开启,接口异常时会抛出异常 URL 等信息
+
+    //自定义请求字段
+    ,request: {
+      userId: 'USER-ID',
+      tokenName: 'Authorization' //自动携带 token 的字段名(如:access_token)。可设置 false 不携带。
+    }
+    
+    //自定义响应字段
+    ,response: {
+      statusName: 'code' //数据状态的字段名称
+      ,statusCode: {
+        ok: 0 //数据状态一切正常的状态码
+        ,logout: 460 //登录状态失效的状态码
+      }
+      ,msgName: 'msg' //状态信息的字段名称
+      ,dataName: 'data' //数据详情的字段名称
+    }
+    
+    //扩展的第三方模块
+    ,extend: [
+      'echarts', //echarts 核心包
+      'echartsTheme' //echarts 主题
+    ]
+    
+    //主题配置
+    ,theme: {
+      //配色方案,如果用户未设置主题,第一个将作为默认
+      color: [{
+        main: '#20222A' //主题色
+        ,selected: '#009688' //选中色
+        ,alias: 'default' //默认别名
+      },{
+        main: '#03152A'
+        ,selected: '#3B91FF'
+        ,alias: 'dark-blue' //藏蓝
+      },{
+        main: '#2E241B'
+        ,selected: '#A48566'
+        ,alias: 'coffee' //咖啡
+      },{
+        main: '#50314F'
+        ,selected: '#7A4D7B'
+        ,alias: 'purple-red' //紫红
+      },{
+        main: '#344058'
+        ,logo: '#1E9FFF'
+        ,selected: '#1E9FFF'
+        ,alias: 'ocean' //海洋
+      },{
+        main: '#3A3D49'
+        ,logo: '#2F9688'
+        ,selected: '#5FB878'
+        ,alias: 'green' //墨绿
+      },{
+        main: '#20222A'
+        ,logo: '#F78400'
+        ,selected: '#F78400'
+        ,alias: 'red' //橙色
+      },{
+        main: '#28333E'
+        ,logo: '#AA3130'
+        ,selected: '#AA3130'
+        ,alias: 'fashion-red' //时尚红
+      },{
+        main: '#24262F'
+        ,logo: '#3A3D49'
+        ,selected: '#009688'
+        ,alias: 'classic-black' //经典黑
+      }]
+    }
+  });
+});

ファイルの差分が大きいため隠しています
+ 0 - 0
uis/layuiadmin/layui/css/layui.css


ファイルの差分が大きいため隠しています
+ 1 - 0
uis/layuiadmin/layui/css/layui.mobile.css


+ 1 - 0
uis/layuiadmin/layui/css/modules/code.css

@@ -0,0 +1 @@
+html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #eee;border-left-width:6px;background-color:#FAFAFA;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:40px;line-height:40px;border-bottom:1px solid #eee}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 10px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view .layui-code-ol li:first-child{padding-top:10px}.layui-code-view .layui-code-ol li:last-child{padding-bottom:10px}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}.layui-code-demo .layui-code{visibility:visible!important;margin:-15px;border-top:none;border-right:none;border-bottom:none}.layui-code-demo .layui-tab-content{padding:15px;border-top:none}

ファイルの差分が大きいため隠しています
+ 0 - 0
uis/layuiadmin/layui/css/modules/laydate/default/laydate.css


BIN
uis/layuiadmin/layui/css/modules/layer/default/icon-ext.png


BIN
uis/layuiadmin/layui/css/modules/layer/default/icon.png


ファイルの差分が大きいため隠しています
+ 0 - 0
uis/layuiadmin/layui/css/modules/layer/default/layer.css


BIN
uis/layuiadmin/layui/css/modules/layer/default/loading-0.gif


BIN
uis/layuiadmin/layui/css/modules/layer/default/loading-1.gif


BIN
uis/layuiadmin/layui/css/modules/layer/default/loading-2.gif


BIN
uis/layuiadmin/layui/font/iconfont.eot


ファイルの差分が大きいため隠しています
+ 25 - 0
uis/layuiadmin/layui/font/iconfont.svg


BIN
uis/layuiadmin/layui/font/iconfont.ttf


BIN
uis/layuiadmin/layui/font/iconfont.woff


BIN
uis/layuiadmin/layui/font/iconfont.woff2


BIN
uis/layuiadmin/layui/images/face/0.gif


BIN
uis/layuiadmin/layui/images/face/1.gif


BIN
uis/layuiadmin/layui/images/face/10.gif


BIN
uis/layuiadmin/layui/images/face/11.gif


BIN
uis/layuiadmin/layui/images/face/12.gif


BIN
uis/layuiadmin/layui/images/face/13.gif


BIN
uis/layuiadmin/layui/images/face/14.gif


BIN
uis/layuiadmin/layui/images/face/15.gif


BIN
uis/layuiadmin/layui/images/face/16.gif


BIN
uis/layuiadmin/layui/images/face/17.gif


BIN
uis/layuiadmin/layui/images/face/18.gif


BIN
uis/layuiadmin/layui/images/face/19.gif


BIN
uis/layuiadmin/layui/images/face/2.gif


BIN
uis/layuiadmin/layui/images/face/20.gif


BIN
uis/layuiadmin/layui/images/face/21.gif


BIN
uis/layuiadmin/layui/images/face/22.gif


BIN
uis/layuiadmin/layui/images/face/23.gif


BIN
uis/layuiadmin/layui/images/face/24.gif


BIN
uis/layuiadmin/layui/images/face/25.gif


BIN
uis/layuiadmin/layui/images/face/26.gif


BIN
uis/layuiadmin/layui/images/face/27.gif


BIN
uis/layuiadmin/layui/images/face/28.gif


BIN
uis/layuiadmin/layui/images/face/29.gif


BIN
uis/layuiadmin/layui/images/face/3.gif


BIN
uis/layuiadmin/layui/images/face/30.gif


BIN
uis/layuiadmin/layui/images/face/31.gif


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません