| @@ -0,0 +1,4 @@ | ||
| 1 | +from django.contrib import admin | |
| 2 | + | |
| 3 | + | |
| 4 | +# Register your models here. | 
| @@ -0,0 +1,5 @@ | ||
| 1 | +from django.apps import AppConfig | |
| 2 | + | |
| 3 | + | |
| 4 | +class AccountConfig(AppConfig): | |
| 5 | + name = 'account' | 
| @@ -0,0 +1,74 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +from django.db import models | |
| 4 | +from django.utils.translation import ugettext_lazy as _ | |
| 5 | +from django_models_ext import BaseModelMixin, SexModelMixin | |
| 6 | +from shortuuidfield import ShortUUIDField | |
| 7 | + | |
| 8 | + | |
| 9 | +class UserInfo(BaseModelMixin): | |
| 10 | + UNVERIFIED = 0 | |
| 11 | + ACTIVATED = 1 | |
| 12 | + DISABLED = 2 | |
| 13 | + DELETED = 3 | |
| 14 | + ASSIGN = 10 | |
| 15 | + | |
| 16 | + USER_STATUS = ( | |
| 17 | + (UNVERIFIED, u'未验证'), | |
| 18 | + (ACTIVATED, u'已激活'), | |
| 19 | + (DISABLED, u'已禁用'), | |
| 20 | + (DELETED, u'已删除'), | |
| 21 | + (ASSIGN, u'已分配'), | |
| 22 | + ) | |
| 23 | + | |
| 24 | + MALE = 1 | |
| 25 | + FEMALE = 0 | |
| 26 | + | |
| 27 | + SEX_TYPE = ( | |
| 28 | + (MALE, u'男'), | |
| 29 | + (FEMALE, u'女'), | |
| 30 | + ) | |
| 31 | + | |
| 32 | +    user_id = ShortUUIDField(_('user_id'), max_length=32, blank=True, null=True, help_text='用户唯一标识', db_index=True, unique=True) | |
| 33 | + | |
| 34 | + # 微信授权用户 | |
| 35 | + unionid = models.CharField(_(u'unionid'), max_length=32, blank=True, null=True, help_text=u'微信 Unionid', db_index=True, unique=True) | |
| 36 | + openid = models.CharField(_(u'openid'), max_length=32, blank=True, null=True, help_text=u'微信公众号 Openid', db_index=True, unique=True) | |
| 37 | + openid_miniapp = models.CharField(_(u'openid_miniapp'), max_length=32, blank=True, null=True, help_text=u'微信小程序 Openid', db_index=True, unique=True) | |
| 38 | + | |
| 39 | + # 用户基本信息 | |
| 40 | + name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'用户姓名') | |
| 41 | + sex = models.IntegerField(_(u'sex'), choices=SexModelMixin.SEX_TUPLE, default=SexModelMixin.UNKNOWN, help_text=u'用户性别') | |
| 42 | + nickname = models.CharField(_(u'nickname'), max_length=255, blank=True, null=True, help_text=u'用户昵称') | |
| 43 | + avatar = models.CharField(_(u'avatar'), max_length=255, blank=True, null=True, help_text=u'用户头像') | |
| 44 | + phone = models.CharField(_(u'phone'), max_length=11, blank=True, null=True, help_text=u'用户电话', db_index=True) | |
| 45 | + country = models.CharField(_(u'country'), max_length=255, blank=True, null=True, help_text=u'用户国家') | |
| 46 | + province = models.CharField(_(u'province'), max_length=255, blank=True, null=True, help_text=u'用户省份') | |
| 47 | + city = models.CharField(_(u'city'), max_length=255, blank=True, null=True, help_text=u'用户城市') | |
| 48 | + | |
| 49 | + user_status = models.IntegerField(_(u'user_status'), choices=USER_STATUS, default=UNVERIFIED, help_text=u'用户状态') | |
| 50 | + | |
| 51 | + class Meta: | |
| 52 | + verbose_name = _(u'用户信息') | |
| 53 | + verbose_name_plural = _(u'用户信息') | |
| 54 | + | |
| 55 | + def __unicode__(self): | |
| 56 | + return '%d' % self.pk | |
| 57 | + | |
| 58 | + @property | |
| 59 | + def data(self): | |
| 60 | +        return { | |
| 61 | + 'user_id': self.user_id, | |
| 62 | + 'unionid': self.unionid, | |
| 63 | + 'openid': self.openid, | |
| 64 | + 'openid_miniapp': self.openid_miniapp, | |
| 65 | + 'name': self.name, | |
| 66 | + 'sex': self.sex, | |
| 67 | + 'nickname': self.nickname, | |
| 68 | + 'avatar': self.avatar, | |
| 69 | + 'phone': self.phone, | |
| 70 | + 'country': self.country, | |
| 71 | + 'province': self.province, | |
| 72 | + 'city': self.city, | |
| 73 | + 'user_status': self.user_status, | |
| 74 | + } | 
| @@ -0,0 +1,4 @@ | ||
| 1 | +from django.test import TestCase | |
| 2 | + | |
| 3 | + | |
| 4 | +# Create your tests here. | 
| @@ -0,0 +1,4 @@ | ||
| 1 | +from django.shortcuts import render | |
| 2 | + | |
| 3 | + | |
| 4 | +# Create your views here. | 
| @@ -0,0 +1,175 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +import logging | |
| 4 | + | |
| 5 | +from django.conf import settings | |
| 6 | +from django.db import transaction | |
| 7 | +from django_logit import logit | |
| 8 | +from django_response import response | |
| 9 | +from ipaddr import client_ip | |
| 10 | +from pywe_miniapp import get_session_info, get_session_key, get_userinfo, store_session_key | |
| 11 | +from pywe_storage import RedisStorage | |
| 12 | +from TimeConvert import TimeConvert as tc | |
| 13 | + | |
| 14 | +from account.models import UserInfo | |
| 15 | +from utils.redis.connect import r | |
| 16 | + | |
| 17 | + | |
| 18 | +WECHAT = settings.WECHAT | |
| 19 | +logger = logging.getLogger('logit') | |
| 20 | + | |
| 21 | + | |
| 22 | +@logit | |
| 23 | +@transaction.atomic | |
| 24 | +def get_userinfo_api(request): | |
| 25 | +    brand_id = request.POST.get('brand_id', settings.KODO_DEFAULT_BRAND_ID) | |
| 26 | +    appId = request.POST.get('appId', 'MINIAPP') | |
| 27 | + | |
| 28 | +    wxcfg = WECHAT.get(appId, {}) | |
| 29 | + | |
| 30 | +    appid = wxcfg.get('appID') | |
| 31 | +    secret = wxcfg.get('appsecret') | |
| 32 | + | |
| 33 | +    code = request.POST.get('code', '') | |
| 34 | +    encryptedData = request.POST.get('encryptedData', '') | |
| 35 | +    iv = request.POST.get('iv', '') | |
| 36 | + | |
| 37 | +    # {u'avatarUrl': u'http://wx.qlogo.cn/mmopen/vi_32/aSKcBBPpibyKNicHNTMM0qJVh8Kjgiak2AHWr8MHM4WgMEm7GFhsf8OYrySdbvAMvTsw3mo8ibKicsnfN5pRjl1p8HQ/0', | |
| 38 | + # u'city': u'Guangzhou', | |
| 39 | + # u'country': u'CN', | |
| 40 | + # u'gender': 1, | |
| 41 | + # u'language': u'zh_CN', | |
| 42 | + # u'nickName': u'Band', | |
| 43 | + # u'openId': u'oGZUI0egBJY1zhBYw2KhdUfwVJJE', | |
| 44 | + # u'province': u'Guangdong', | |
| 45 | + # u'unionId': u'ocMvos6NjeKLIBqg5Mr9QjxrP1FA', | |
| 46 | +    #  u'watermark': {u'appid': u'wx4f4bc4dec97d474b', u'timestamp': 1477314187}} | |
| 47 | + session_key = get_session_key(appid=appid, secret=secret, code=code) | |
| 48 | + # Get Userinfo | |
| 49 | + userinfo = get_userinfo(appid=appid, secret=secret, code=code, session_key=session_key, encryptedData=encryptedData, iv=iv) | |
| 50 | + | |
| 51 | + # Get or Create User | |
| 52 | +    user, created = UserInfo.objects.select_for_update().get_or_create(unionid=userinfo.get('unionId', '')) | |
| 53 | + | |
| 54 | + # Set User Key's Value | |
| 55 | +    user.unionid = userinfo.get('unionId', '') | |
| 56 | +    user.openid_miniapp = userinfo.get('openId', '') | |
| 57 | +    user.sex = userinfo.get('gender', '') | |
| 58 | +    user.nickname = userinfo.get('nickName', '') | |
| 59 | +    user.avatar = userinfo.get('avatarUrl', '') | |
| 60 | +    user.country = userinfo.get('country', '') | |
| 61 | +    user.province = userinfo.get('province', '') | |
| 62 | +    user.city = userinfo.get('city', '') | |
| 63 | + user.user_status = UserInfo.ACTIVATED | |
| 64 | + user.signup_ip = client_ip(request) | |
| 65 | + user.signup_at = tc.utc_datetime() | |
| 66 | + user.save() | |
| 67 | + | |
| 68 | + # Store SessionKey | |
| 69 | + store_session_key(appid=appid, secret=secret, session_key=session_key, unid=user.user_id, storage=RedisStorage(r)) | |
| 70 | + # Just for compatible because of store session_key has changed | |
| 71 | + store_session_key(appid=appid, secret=secret, session_key=session_key, unid='', storage=RedisStorage(r)) | |
| 72 | + | |
| 73 | + return response(200, 'Mini App Login Success', u'微信小程序登录成功', user.brandata(brand_id=brand_id)) | |
| 74 | + | |
| 75 | + | |
| 76 | +@logit(res=True) | |
| 77 | +@transaction.atomic | |
| 78 | +def mini_login_api(request): | |
| 79 | +    brand_id = request.POST.get('brand_id', settings.KODO_DEFAULT_BRAND_ID) | |
| 80 | +    appId = request.POST.get('appId', 'MINIAPP') | |
| 81 | + | |
| 82 | +    wxcfg = WECHAT.get(appId, {}) | |
| 83 | + | |
| 84 | +    appid = wxcfg.get('appID') | |
| 85 | +    secret = wxcfg.get('appsecret') | |
| 86 | + | |
| 87 | +    code = request.POST.get('code', '') | |
| 88 | + | |
| 89 | + # // 正常返回的JSON数据包 | |
| 90 | +    # { | |
| 91 | + # "openid": "OPENID", | |
| 92 | + # "session_key": "SESSIONKEY", | |
| 93 | + # } | |
| 94 | + # | |
| 95 | + # // 满足UnionID返回条件时,返回的JSON数据包 | |
| 96 | +    # { | |
| 97 | + # "openid": "OPENID", | |
| 98 | + # "session_key": "SESSIONKEY", | |
| 99 | + # "unionid": "UNIONID" | |
| 100 | + # } | |
| 101 | + # // 错误时返回JSON数据包(示例为Code无效) | |
| 102 | +    # { | |
| 103 | + # "errcode": 40029, | |
| 104 | + # "errmsg": "invalid code" | |
| 105 | + # } | |
| 106 | + session_info = get_session_info(appid=appid, secret=secret, code=code) | |
| 107 | + logger.debug(session_info) | |
| 108 | +    session_key = session_info.get('session_key', '') | |
| 109 | +    unionid = session_info.get('unionid', '') | |
| 110 | +    openid = session_info.get('openid', '') | |
| 111 | + | |
| 112 | + # Get or Create User | |
| 113 | + user, created = UserInfo.objects.select_for_update().get_or_create(openid_miniapp=openid) | |
| 114 | + | |
| 115 | + # Set User Key's Value | |
| 116 | + if unionid: | |
| 117 | + user.unionid = unionid | |
| 118 | + | |
| 119 | + user.user_status = UserInfo.ACTIVATED | |
| 120 | + user.save() | |
| 121 | + | |
| 122 | + # Store SessionKey | |
| 123 | + store_session_key(appid=appid, secret=secret, session_key=session_key, unid=user.user_id, storage=RedisStorage(r)) | |
| 124 | + # Just for compatible because of store session_key has changed | |
| 125 | + store_session_key(appid=appid, secret=secret, session_key=session_key, unid='', storage=RedisStorage(r)) | |
| 126 | + | |
| 127 | + return response(200, 'Mini App Login Success', u'微信小程序登录成功', user.brandata(brand_id=brand_id)) | |
| 128 | + | |
| 129 | + | |
| 130 | +@logit | |
| 131 | +@transaction.atomic | |
| 132 | +def get_userinfo_api2(request): | |
| 133 | +    brand_id = request.POST.get('brand_id', settings.KODO_DEFAULT_BRAND_ID) | |
| 134 | +    user_id = request.POST.get('user_id', '') | |
| 135 | +    appId = request.POST.get('appId', 'MINIAPP') | |
| 136 | + | |
| 137 | +    wxcfg = WECHAT.get(appId, {}) | |
| 138 | + | |
| 139 | +    appid = wxcfg.get('appID') | |
| 140 | +    secret = wxcfg.get('appsecret') | |
| 141 | + | |
| 142 | +    encryptedData = request.POST.get('encryptedData', '') | |
| 143 | +    iv = request.POST.get('iv', '') | |
| 144 | + | |
| 145 | + try: | |
| 146 | + user = UserInfo.objects.select_for_update().get(user_id=user_id, status=True) | |
| 147 | + except UserInfo.DoesNotExist: | |
| 148 | + return response() | |
| 149 | + | |
| 150 | +    # {u'avatarUrl': u'http://wx.qlogo.cn/mmopen/vi_32/aSKcBBPpibyKNicHNTMM0qJVh8Kjgiak2AHWr8MHM4WgMEm7GFhsf8OYrySdbvAMvTsw3mo8ibKicsnfN5pRjl1p8HQ/0', | |
| 151 | + # u'city': u'Guangzhou', | |
| 152 | + # u'country': u'CN', | |
| 153 | + # u'gender': 1, | |
| 154 | + # u'language': u'zh_CN', | |
| 155 | + # u'nickName': u'Band', | |
| 156 | + # u'openId': u'oGZUI0egBJY1zhBYw2KhdUfwVJJE', | |
| 157 | + # u'province': u'Guangdong', | |
| 158 | + # u'unionId': u'ocMvos6NjeKLIBqg5Mr9QjxrP1FA', | |
| 159 | +    #  u'watermark': {u'appid': u'wx4f4bc4dec97d474b', u'timestamp': 1477314187}} | |
| 160 | + session_key = get_session_key(appid=appid, secret=secret, unid=user_id, storage=RedisStorage(r)) | |
| 161 | + # Get Userinfo | |
| 162 | + userinfo = get_userinfo(appid=appid, secret=secret, session_key=session_key, encryptedData=encryptedData, iv=iv) | |
| 163 | + | |
| 164 | + # Set User Key's Value | |
| 165 | +    user.unionid = userinfo.get('unionId', '') | |
| 166 | +    user.openid_miniapp = userinfo.get('openId', '') | |
| 167 | +    user.sex = userinfo.get('gender', '') | |
| 168 | +    user.nickname = userinfo.get('nickName', '') | |
| 169 | +    user.avatar = userinfo.get('avatarUrl', '') | |
| 170 | +    user.country = userinfo.get('country', '') | |
| 171 | +    user.province = userinfo.get('province', '') | |
| 172 | +    user.city = userinfo.get('city', '') | |
| 173 | + user.save() | |
| 174 | + | |
| 175 | + return response(200, 'Mini App Get Userinfo Success', u'微信小程序获取用户信息成功', user.brandata(brand_id=brand_id)) | 
| @@ -2,7 +2,8 @@ | ||
| 2 | 2 |  | 
| 3 | 3 | from django.conf.urls import url | 
| 4 | 4 |  | 
| 5 | -from api import branch_campus_admin_views, branch_campus_views, course_field_views, course_views, oauth_views, wx_views | |
| 5 | +from api import (branch_campus_admin_views, branch_campus_views, course_field_views, course_views, mini_views, | |
| 6 | + oauth_views, wx_views) | |
| 6 | 7 |  | 
| 7 | 8 |  | 
| 8 | 9 | urlpatterns = [ | 
| @@ -42,3 +43,10 @@ urlpatterns += [ | ||
| 42 | 43 | url(r'^course/field/list$', course_field_views.get_course_field_list, name='get_course_field_list'), | 
| 43 | 44 | url(r'^course/field/delete$', course_field_views.delete_course_field, name='delete_course_field'), | 
| 44 | 45 | ] | 
| 46 | + | |
| 47 | +# Mini App | |
| 48 | +urlpatterns = [ | |
| 49 | + url(r'^mini/userinfo$', mini_views.get_userinfo_api, name='get_userinfo_api'), # 获取用户信息 | |
| 50 | + url(r'^mini/login$', mini_views.mini_login_api, name='mini_login_api'), # 小程序登录 | |
| 51 | + url(r'^mini/userinfo2$', mini_views.get_userinfo_api2, name='get_userinfo_api2'), # 获取用户信息 | |
| 52 | +] | 
| @@ -56,6 +56,7 @@ INSTALLED_APPS = [ | ||
| 56 | 56 | 'django_we', | 
| 57 | 57 | 'commands', | 
| 58 | 58 | 'api', | 
| 59 | + 'account', | |
| 59 | 60 | 'registration', | 
| 60 | 61 | ] | 
| 61 | 62 |  |