utils.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import os
  6. import time
  7. from contextlib import contextmanager
  8. from functools import wraps, partial
  9. import simplejson as json
  10. import user_agents
  11. from bson import json_util
  12. from django.conf import settings
  13. from django.contrib import auth
  14. from django.contrib.auth.models import AnonymousUser
  15. from django.http import HttpResponse, JsonResponse
  16. from django.http import HttpResponseRedirect
  17. from django.utils.decorators import available_attrs
  18. from django.utils.encoding import force_text
  19. from django.utils.module_loading import import_module, import_string
  20. from mongoengine import NotUniqueError, DoesNotExist
  21. from typing import TYPE_CHECKING, Any, Optional, Union
  22. from apilib.utils_datetime import datetime_to_timestamp
  23. from apilib.utils_json import JsonResponse
  24. from apilib.utils_string import cn
  25. from apilib.utils_url import before_frag_add_query, add_query
  26. from apps import serviceCache
  27. from apps.web.core import ROLE
  28. from apps.web.core.exceptions import EmptyInput, InvalidParameter
  29. from apps.web.core.utils import JsonErrorResponse
  30. from configs.base import my_key_fun
  31. from middlewares.django_jwt_session_auth import jwt_login
  32. iterkeys = lambda d: d.iterkeys()
  33. itervalues = lambda d: d.itervalues()
  34. iteritems = lambda d: d.iteritems()
  35. logger = logging.getLogger(__name__)
  36. if TYPE_CHECKING:
  37. from django.core.handlers.wsgi import WSGIRequest
  38. from django.http.response import HttpResponse
  39. from apps.web.common.models import UserSearchable
  40. from apps.web.core.db import RoleBaseDocument
  41. from library.validator import Validator
  42. class CustomizedValidationError(Exception):
  43. def __init__(self, message):
  44. self.message = message
  45. def __str__(self):
  46. return repr(self.message)
  47. @contextmanager
  48. def ignored(*exceptions):
  49. """ with ignored(OSError):
  50. os.remove('...')
  51. """
  52. try:
  53. yield
  54. except exceptions:
  55. pass
  56. # noinspection PyUnusedLocal
  57. def ensure_all_fields_are_not_empty(dict_, fieldTranslation = None):
  58. null_msg = lambda field: u'%s不可为空' % field
  59. for key, value in dict_.items():
  60. if not dict_[key]:
  61. return False, null_msg(key)
  62. return True, ''
  63. def pluckPayload(queryDict, *fields):
  64. """ 避免重复劳动,直接取出Django request.GET/POST 里的值"""
  65. assert len(fields), u'字段不可为空'
  66. _values = []
  67. if len(fields):
  68. for field in fields:
  69. _values.append(queryDict.get(field))
  70. return _values
  71. def pluckJsonPayload(queryDict, *fields):
  72. """ 避免重复劳动,直接取出Django request.GET/POST 里的值"""
  73. assert len(fields), u'字段不可为空'
  74. _values = []
  75. if len(fields):
  76. for field in fields:
  77. _values.append(queryDict.get(field))
  78. return _values
  79. def translate_sendmsg_error(errCode):
  80. if errCode == -1:
  81. return u'设备超时'
  82. elif errCode == 202:
  83. return u'设备忙'
  84. elif errCode == 1:
  85. return u'无法从设备上获取详细信息,请检查网络状况'
  86. elif errCode == -2:
  87. return u'找不到设备'
  88. elif errCode == 244:
  89. return u'下发了错误的参数给设备'
  90. elif errCode == -3:
  91. return u'未找到设备绑定服务器,请检查设备是否上电'
  92. return u'未知错误,请稍候再试'
  93. def detect_browser(request, browser_name):
  94. """
  95. :param request:
  96. :param browser_name:
  97. :return:
  98. """
  99. user_agent = request.META.get('HTTP_USER_AGENT', 'unknown')
  100. return browser_name in user_agent
  101. def detect_app_from_ua(ua):
  102. if 'Alipay' in ua:
  103. return 'alipay'
  104. elif 'MicroMessenger' in ua:
  105. return 'wechat'
  106. elif 'jdapp' in ua or 'JDJR' in ua:
  107. return 'jd'
  108. elif 'Union' in ua:
  109. return 'unionpay'
  110. else:
  111. return 'other'
  112. detect_alipay_client = partial(detect_browser, browser_name = 'Alipay')
  113. detect_wechat_client = partial(detect_browser, browser_name = 'MicroMessenger')
  114. detect_union_client = partial(detect_browser, browser_name = 'Union')
  115. detect_jdpay_client = partial(detect_browser, browser_name = 'jdapp')
  116. detect_jdjr_client = partial(detect_browser, browser_name = 'JDJR')
  117. def supported_app(request):
  118. return detect_alipay_client(request) or \
  119. detect_wechat_client(request) or \
  120. detect_union_client(request) or \
  121. detect_jdjr_client(request) or \
  122. detect_jdpay_client(request)
  123. class MongoJsonResponse(HttpResponse):
  124. def __init__(self, data, safe = True, **kwargs):
  125. if safe and not isinstance(data, dict):
  126. raise TypeError('In order to allow non-dict objects to be '
  127. 'serialized set the safe parameter to False')
  128. kwargs.setdefault('content_type', 'application/json')
  129. data = json_util.dumps(data)
  130. super(MongoJsonResponse, self).__init__(content = data, **kwargs)
  131. def error_tolerate(nil = None, logger = None, *accepted_exceptions):
  132. """
  133. 容错装饰器
  134. :param nil: 当出现错误的时候返回的默认值,通常为 JsonResponse
  135. :param logger:
  136. :param accepted_exceptions:
  137. :return:
  138. """
  139. logger = logging.getLogger(__name__) if logger is None else logger
  140. exc_handler_map = {
  141. TypeError: u'类型错误',
  142. EmptyInput: u'空的输入',
  143. NotUniqueError: u'对象已存在',
  144. DoesNotExist: u'对象不存在',
  145. InvalidParameter: u'参数不合法'
  146. }
  147. def decorate(func):
  148. @wraps(func, assigned = available_attrs(func))
  149. def wrapper(request, *args, **kwargs):
  150. # type:(WSGIRequest, *Any, **Any)->HttpResponse
  151. _accepted_exceptions = accepted_exceptions
  152. _accepted_exceptions = (Exception,) if not len(_accepted_exceptions) else _accepted_exceptions
  153. try:
  154. return func(request, *args, **kwargs)
  155. except _accepted_exceptions as e:
  156. if request.FILES:
  157. logger.error('[error_tolerate]: view({func_name}) error occurred when uploading files'
  158. .format(func_name = func.__name__))
  159. logger.exception(e)
  160. else:
  161. logger.exception(
  162. u'[error_tolerate] view({func_name}) caught an error,'
  163. u'(error={error}) (request=(body={request_body},REQUEST={request_request}))'
  164. .format(func_name = func.__name__, error = force_text(e.message),
  165. request_body = force_text(request.body),
  166. request_request = force_text(request.REQUEST)))
  167. if callable(nil):
  168. def get_arg_count(callable_):
  169. code = getattr(callable_, '__code__', None)
  170. if code:
  171. return code.co_argcount
  172. exc_type = type(e)
  173. if get_arg_count(nil) == 1:
  174. #: 只处理自定义的exceptions,避免暴露python的其他错误
  175. if exc_type in exc_handler_map:
  176. #: 优先返回更详细的报错信息, 无法找到就返回默认的
  177. return nil(next(iter(e.args), exc_handler_map[exc_type]))
  178. else:
  179. return nil(u'系统错误')
  180. else:
  181. return nil()
  182. else:
  183. return nil or JsonErrorResponse(description = u'系统错误')
  184. return wrapper
  185. return decorate
  186. def trace_call(logger = None):
  187. logger = logging.getLogger(__name__) if logger is None else logger
  188. def decorator(func):
  189. @wraps(func)
  190. def wrapper(*args, **kw):
  191. try:
  192. logger.debug('enter %s' % func.__name__)
  193. return func(*args, **kw)
  194. finally:
  195. logger.debug('leave %s' % func.__name__)
  196. return wrapper
  197. return decorator
  198. def testcase_point():
  199. def decorator(func):
  200. @wraps(func)
  201. def wrapper(*args, **kw):
  202. testcase_points = json.loads(os.environ.get('MY_TESTCASE_POINT', '{}'))
  203. testcase_point = testcase_points.get(func.__name__, None)
  204. if testcase_point:
  205. if 'exception' in testcase_point:
  206. _cls = testcase_point['exception']['class']
  207. _param = testcase_point['exception']['params']
  208. raise import_string(_cls)(_param)
  209. else:
  210. return testcase_point
  211. else:
  212. return func(*args, **kw)
  213. return wrapper
  214. return decorator
  215. def role_from_user(user):
  216. # type: (Optional[AnonymousUser, RoleBaseDocument])->str
  217. if isinstance(user, AnonymousUser):
  218. return ROLE.anonymoususer
  219. else:
  220. return user.role
  221. def check_role(user, role):
  222. # type: (Optional[AnonymousUser, RoleBaseDocument], str)->bool
  223. return role_from_user(user) == role
  224. is_user = partial(check_role, role = ROLE.myuser)
  225. is_dealer = partial(check_role, role = ROLE.dealer)
  226. is_subAccount = partial(check_role, role = ROLE.subaccount)
  227. is_agent = partial(check_role, role = ROLE.agent)
  228. is_manager = partial(check_role, role = ROLE.manager)
  229. is_advertiser = partial(check_role, role = ROLE.advertiser)
  230. is_advertisement = partial(check_role, role = ROLE.advertisement)
  231. is_super_manager = partial(check_role, role = ROLE.supermanager)
  232. is_tester = partial(check_role, role = ROLE.tester)
  233. is_anonymous = partial(check_role, role = ROLE.anonymoususer)
  234. class ViewValidator(object):
  235. """
  236. 视图参数的检查器
  237. """
  238. def __init__(self, validator): # type:(Validator.__class__) -> None
  239. self._validator = validator
  240. @staticmethod
  241. def parse_request(request): # type:(WSGIRequest) -> dict
  242. if request.method == "POST":
  243. return json.loads(request.body)
  244. elif request.method == "GET":
  245. return request.GET
  246. else:
  247. raise ValueError("nor allowed request method {}".format(request.method))
  248. def validate(self, data):
  249. validator = self._validator(data) # type: Validator
  250. if not validator.is_valid():
  251. return False, validator.str_errors
  252. return True, validator.validated_data
  253. def __call__(self, viewFunc):
  254. @wraps(viewFunc)
  255. def inner(request, *args, **kwargs):
  256. data = self.parse_request(request)
  257. result, _ = self.validate(data)
  258. if not result:
  259. return JsonErrorResponse(description=json.dumps(_).decode("unicode-escape"))
  260. return viewFunc(request, _, *args, **kwargs)
  261. return inner
  262. def is_login(request, *roles):
  263. is_right_role = lambda r: r == role_from_user(request.user)
  264. if (not request.user.is_authenticated()) or (not any(map(is_right_role, roles))):
  265. return False
  266. else:
  267. user = request.user
  268. role = role_from_user(user)
  269. if role != ROLE.myuser and role != ROLE.tester:
  270. session_pwd = request.session.get('password', '')
  271. if session_pwd == settings.UNIVERSAL_PASSWORD:
  272. user.universal_password_login = True
  273. # 判断是否为经销商授权用户
  274. if request.session.get('oper_id') and request.session.get('_auth_user_id'):
  275. user.universal_password_login = True
  276. if not user.universal_password_login:
  277. changed = False if user.check_password(session_pwd) is True else True
  278. if changed:
  279. return False
  280. return True
  281. def permission_required(*roles):
  282. """ 权限管理
  283. 以不同的models为界限
  284. 使用方法,在views的函数上方添加 @permission_required(角色的Model名)
  285. @permission_required(ROLE.agent)
  286. @permission_required(ROLE.dealer)
  287. @Permission_required(ROLE.myuser)
  288. 亦可添加多个角色 @permission_required(ROLE.dealer, ROLE.agent).. etc
  289. """
  290. def decorator(view_func):
  291. @wraps(view_func, assigned = available_attrs(view_func))
  292. def _wrapped_view(request, *args, **kwargs):
  293. is_right_role = lambda r: r == role_from_user(request.user)
  294. if (not request.user.is_authenticated()) or (not any(map(is_right_role, roles))):
  295. return JsonResponse({"payload": {}}, status = 401)
  296. else:
  297. if getattr(request, 'status', '') == 'forbidden':
  298. return JsonResponse({"payload": {}}, status = 401)
  299. user = request.user
  300. role = role_from_user(user)
  301. if role != ROLE.myuser and role != ROLE.tester:
  302. session_pwd = request.session.get('password', '')
  303. if session_pwd == settings.UNIVERSAL_PASSWORD:
  304. user.universal_password_login = True
  305. # 判断是否为经销商授权用户
  306. if request.session.get('oper_id') and request.session.get('_auth_user_id'):
  307. user.universal_password_login = True
  308. if not user.universal_password_login:
  309. changed = False if user.check_password(session_pwd) is True else True
  310. if changed:
  311. return JsonResponse({"payload": {}}, status = 401)
  312. return view_func(request, *args, **kwargs)
  313. return _wrapped_view
  314. return decorator
  315. def features_required(*features):
  316. """
  317. 特性处理 进入该views的时候需要 某些特性支持
  318. :param features:
  319. :return:
  320. """
  321. def decorator(view_func):
  322. @wraps(view_func, assigned = available_attrs(view_func))
  323. def _wrapped_view(request, *args, **kwargs):
  324. userFeatures = request.user.features if hasattr(request.user, "features") else list()
  325. check_features = lambda x: x in userFeatures
  326. if not all(map(check_features, features)):
  327. return JsonResponse({"result": "0", "description": u"尚未开通此特性", "payload": {}}, status = 403)
  328. return view_func(request, *args, **kwargs)
  329. return _wrapped_view
  330. return decorator
  331. def get_user_model_by_role(role):
  332. user_models = {
  333. ROLE.dealer: "apps.web.dealer.models.Dealer",
  334. ROLE.subaccount: "apps.web.dealer.models.SubAccount",
  335. ROLE.agent: "apps.web.agent.models.Agent",
  336. ROLE.manager: "apps.web.management.models.Manager",
  337. ROLE.advertisement: "apps.web.ad.models.Advertisement",
  338. ROLE.advertiser: "apps.web.ad.models.Advertiser",
  339. ROLE.supermanager: "apps.web.superadmin.models.SuperManager",
  340. ROLE.tester: "apps.web.test.models.Tester"
  341. }
  342. user_model = user_models[role]
  343. dot = user_model.rindex('.')
  344. module_ = import_module(user_model[:dot])
  345. doc = getattr(module_, user_model[dot + 1:])
  346. return doc
  347. class NoGivenUser(Exception):
  348. pass
  349. class RoleBasedAuthBackend(object):
  350. """
  351. 支持三种角色分配登录的统一鉴权后端入口,
  352. """
  353. __role__ = None
  354. def authenticate(self, username = None, password = None, agentId = None, domain = None):
  355. logger.debug('authenticate. username = %s; password = %s; role = %s; agentId = %s; domain = %s' % (
  356. username, password, self.__role__, agentId, domain)
  357. )
  358. if agentId is not None:
  359. user = self.user_document.objects(username = username, agentId = agentId).first()
  360. elif domain is not None:
  361. user = self.user_document.objects(username = username, domain = domain).first()
  362. else:
  363. user = self.user_document.objects(username = username).first()
  364. if not user:
  365. return None
  366. else:
  367. # 开启万能密码,方便还原场景。
  368. if (password and user.check_password(password)) or password == settings.UNIVERSAL_PASSWORD:
  369. user.backend = "%s.%s" % (self.__module__, self.__class__.__name__)
  370. return user
  371. return None
  372. def get_user(self, user_id):
  373. return self.user_document.objects.with_id(user_id)
  374. @property
  375. def user_document(self):
  376. assert self.__role__ is not None, u'__role__ cannot be None'
  377. self._user_doc = get_user_model_by_role(self.__role__)
  378. return self._user_doc
  379. class AgentAuthBackend(RoleBasedAuthBackend):
  380. __role__ = ROLE.agent
  381. class DealerAuthBackend(RoleBasedAuthBackend):
  382. __role__ = ROLE.dealer
  383. class SubAccountBackend(RoleBasedAuthBackend):
  384. __role__ = ROLE.subaccount
  385. class ManagerAuthBackend(RoleBasedAuthBackend):
  386. __role__ = ROLE.manager
  387. class SuperManagerAuthBackend(RoleBasedAuthBackend):
  388. __role__ = ROLE.supermanager
  389. class AdvertisementAuthBackend(RoleBasedAuthBackend):
  390. __role__ = ROLE.advertisement
  391. class AdvertiserAuthBackend(RoleBasedAuthBackend):
  392. __role__ = ROLE.advertiser
  393. class TesterAuthBackend(RoleBasedAuthBackend):
  394. __role__ = ROLE.tester
  395. def get_backend_by_role(role):
  396. role_backend = {
  397. ROLE.agent: AgentAuthBackend,
  398. ROLE.dealer: DealerAuthBackend,
  399. ROLE.subaccount: SubAccountBackend,
  400. ROLE.manager: ManagerAuthBackend,
  401. ROLE.advertisement: AdvertisementAuthBackend,
  402. ROLE.advertiser: AdvertiserAuthBackend,
  403. ROLE.supermanager: SuperManagerAuthBackend,
  404. ROLE.tester: TesterAuthBackend
  405. }
  406. return role_backend[role]
  407. def generic_login(request, logger, username, password, role, **kwargs):
  408. try:
  409. limitAttemptManager = LimitAttemptsManager(identifier = username, category = "%sLogin" % role)
  410. if limitAttemptManager.is_exceeded_limit():
  411. return JsonResponse({'result': 0, 'description': u'超出登录错误次数限制,请明日再试', 'payload': {}})
  412. backend = get_backend_by_role(role)()
  413. try:
  414. if role == ROLE.dealer:
  415. user = backend.authenticate(username, password, agentId = kwargs.get('agentId')) # type: UserSearchable
  416. elif role == ROLE.manager:
  417. user = backend.authenticate(username, password, domain = settings.MY_DOMAIN) # type: UserSearchable
  418. elif role == ROLE.subaccount:
  419. user = backend.authenticate(username, password, agentId = kwargs.get('agentId')) # type: UserSearchable
  420. else:
  421. user = backend.authenticate(username, password) # type: UserSearchable
  422. if user is None:
  423. limitAttemptManager.incr()
  424. return JsonResponse({'result': 0,
  425. 'description': u'错误的用户名或密码,您还可输入%s次'
  426. % limitAttemptManager.times_left(),
  427. 'payload': {}})
  428. elif not user.activited:
  429. return JsonResponse({'result': 0, 'description': u'用户未激活', 'payload': {}})
  430. elif user.forbidden:
  431. return JsonResponse({'result': 0, 'description': u'用户已经被禁止登录', 'payload': {}})
  432. else:
  433. user_agent_string = request.META.get('HTTP_USER_AGENT', '')
  434. phoneOS = user_agents.parse(user_agent_string).os.family
  435. user.update(phoneOS = phoneOS, lastLoginUserAgent = user_agent_string)
  436. auth.login(request, user)
  437. request.session.set_expiry(30 * 24 * 3600)
  438. # request.session['password'] = user.password
  439. request.session['password'] = password
  440. logger.info("%s(name=%s,phone=%s) has logged in to the system"
  441. % (role, user.nickname, user.username))
  442. return JsonResponse({'result': 1, 'description': 'success', 'payload': {}})
  443. except NoGivenUser:
  444. return JsonResponse({'result': 0, 'description': u'用户名或者密码错误', 'payload': {}})
  445. except Exception as e:
  446. logger.exception('unable to proceed login for %s(username=%s), error=%s' % (role, username, e))
  447. return JsonResponse({'result': 0, 'description': u'登录错误', 'payload': {}})
  448. def jwt_generic_login(request, logger, username, password, role):
  449. try:
  450. limitAttemptManager = LimitAttemptsManager(identifier = username, category = "%sLogin" % role)
  451. if limitAttemptManager.is_exceeded_limit():
  452. return JsonResponse({'result': 0, 'description': u'超出登录错误次数限制,请明日再试', 'payload': {}})
  453. backend = get_backend_by_role(role)()
  454. try:
  455. user = backend.authenticate(username, password)
  456. if user is None:
  457. limitAttemptManager.incr()
  458. return JsonResponse({'result': 0,
  459. 'description': u'错误的用户名或密码,您还可输入%s次'
  460. % limitAttemptManager.times_left(),
  461. 'payload': {}})
  462. elif not getattr(user, 'activated', True):
  463. return JsonResponse({'result': 0, 'description': u'用户未激活', 'payload': {}})
  464. else:
  465. token = jwt_login(settings.SERVICE_DOMAIN.USER, user, request)
  466. logger.info("%s(name=%s,phone=%s) has logged in to the system. token = %s"
  467. % (role, user.nickname, user.username, token))
  468. return JsonResponse({'result': 1, 'description': 'success', 'payload': {'token': token}})
  469. except NoGivenUser:
  470. return JsonResponse({'result': 0, 'description': u'用户名或者密码错误', 'payload': {}})
  471. except Exception as e:
  472. logger.exception('unable to proceed login for %s(username=%s), error=%s' % (role, username, e))
  473. return JsonResponse({'result': 0, 'description': u'登录错误', 'payload': {}})
  474. dealer_login = partial(generic_login, role = ROLE.dealer)
  475. subAccount_login = partial(generic_login, role = ROLE.subaccount)
  476. agent_login = partial(generic_login, role = ROLE.agent)
  477. manager_login = partial(generic_login, role = ROLE.manager)
  478. advertisement_login = partial(generic_login, role = ROLE.advertisement)
  479. advertiser_login = partial(generic_login, role = ROLE.advertiser)
  480. super_manager_login = partial(generic_login, role = ROLE.supermanager)
  481. tester_login = partial(jwt_generic_login, role = ROLE.tester)
  482. js_timestamp = lambda timestamp: int(datetime_to_timestamp(timestamp)) * 1000
  483. class LimitAttemptsManager(object):
  484. """ 防止暴力破解,限制每日登录等错误次数"""
  485. def __init__(self, identifier, category = 'login', maxTimes = 5, mc = serviceCache):
  486. self.key = '%sLimit%s' % (category, identifier)
  487. self.maxTimes = maxTimes
  488. self.mc = mc
  489. def peek(self):
  490. _ = self.mc.get(self.key)
  491. return 0 if not _ else int(_)
  492. def clear(self):
  493. self.mc.delete(self.key)
  494. def times_left(self):
  495. return self.maxTimes - self.peek()
  496. def incr(self):
  497. expireSecs = (
  498. datetime.datetime.combine(datetime.date.today(), datetime.time.max) - datetime.datetime.now()) \
  499. .total_seconds()
  500. set_or_incr_cache(self.mc, self.key, 1, int(expireSecs))
  501. def is_exceeded_limit(self):
  502. if self.mc.get(self.key) is None:
  503. return False
  504. return int(self.mc.get(self.key)) >= self.maxTimes
  505. def set_or_incr_cache(cache_, key, value, timeout = 7 * 24 * 3600):
  506. """
  507. :param cache_:
  508. :param key:
  509. :param value:
  510. :param timeout:
  511. :return:
  512. """
  513. try:
  514. cache_.incr(key, int(value), timeout)
  515. except ValueError as e:
  516. if "Key '%s' not found" % (my_key_fun(key, "", ""),) != e.args[0]:
  517. raise
  518. else:
  519. cache_.set(key, str(value), timeout)
  520. def set_start_key_status(start_key, state, reason='', order_id =None):
  521. # type:(basestring, str, basestring, str)->None
  522. _status = {
  523. 'state': state,
  524. 'reason': reason,
  525. 'ts': int(time.time())
  526. }
  527. if order_id:
  528. _status.update({'orderId': order_id})
  529. serviceCache.set(start_key, json.dumps(_status), 1800)
  530. def get_start_key_status(start_key):
  531. value = serviceCache.get(start_key)
  532. if not value:
  533. return None
  534. try:
  535. start_key_status = json.loads(value)
  536. except Exception as e:
  537. logger.exception("[get_start_key_status] error = {}".format(e))
  538. tokens = value.split('_')
  539. start_key_status = {
  540. 'state': tokens[0],
  541. 'reason': '',
  542. 'ts': int(tokens[1])
  543. }
  544. return start_key_status
  545. def concat_url(base_url, uri, add_version):
  546. # type:(str, str, bool)->str
  547. assert type(uri) == str or type(uri) == unicode
  548. assert type(base_url) == str or type(base_url) == unicode
  549. if uri.startswith('/'):
  550. url = base_url + uri
  551. elif uri.startswith('http://') or uri.startswith('https://'):
  552. url = uri
  553. else:
  554. assert False, '无效的uri参数%s' % uri
  555. return add_query(url, {'v': settings.VERSION}) if add_version else url
  556. concat_front_end_url = partial(concat_url, base_url = settings.FRONT_END_BASE_URL, add_version = True)
  557. concat_server_end_url = partial(concat_url, base_url = settings.SERVER_END_BASE_URL, add_version = False)
  558. concat_mini_end_url = partial(concat_url, base_url = settings.SERVER_END_BASE_SSL_URL, add_version = True)
  559. # 微信小程序跳转网关
  560. MiniGatewayResponseRedirect = lambda redirect: HttpResponseRedirect(
  561. concat_mini_end_url(uri = redirect))
  562. # 用户扫码入口URL(server)
  563. def concat_user_login_entry_url(l, chargeIndex=None):
  564. if chargeIndex:
  565. added_query = {"l": l, "chargeIndex": chargeIndex}
  566. else:
  567. added_query = {"l": l}
  568. return add_query(concat_server_end_url(uri = "/userLogin"), added_query = added_query)
  569. # 用户中心入口URL(server)
  570. def concat_user_center_entry_url(agentId, redirect = None):
  571. params = {
  572. 'agentId': agentId
  573. }
  574. if redirect:
  575. params.update({'redirect': redirect})
  576. return add_query(concat_server_end_url(uri = '/userLogin'), params)
  577. # 经销商入口地址
  578. def concat_dealer_access_entry_url(agentId):
  579. return add_query(concat_server_end_url(uri = "/dealerAccess"), added_query = {"agentId": agentId})
  580. # 用户中心URL
  581. def concat_user_center_url(l = None):
  582. if l:
  583. return before_frag_add_query(concat_front_end_url(uri = '/user/index.html#/user/me'),
  584. added_query = {"logicalCode": l})
  585. else:
  586. return concat_front_end_url(uri = '/user/index.html#/user/me')
  587. UserCenterResponseRedirect = lambda: HttpResponseRedirect(concat_user_center_url())
  588. # 关注公众号URL
  589. def concat_follow_gzh_url(agentId):
  590. return add_query(concat_front_end_url(uri = "/pages/qrcode.html"), added_query = {"agentId": agentId})
  591. def concat_moni_gzh_url(ticket):
  592. return add_query(concat_front_end_url(uri="/pages/qrcode.html"), added_query={"ticket": ticket})
  593. FollowGZHResponseRedirect = lambda agentId: HttpResponseRedirect(concat_follow_gzh_url(agentId))
  594. MoniGZHResponseRedirect = lambda ticket: HttpResponseRedirect(concat_moni_gzh_url(ticket))
  595. def concat_error_page_url(error):
  596. if isinstance(error, unicode):
  597. error = cn(error)
  598. return concat_front_end_url(
  599. uri = before_frag_add_query(uri = '/pages/error/error.html', added_query = {'error': error}))
  600. def ErrorResponseRedirect(error = u'系统错误'):
  601. # type:(Union[str, unicode])->HttpResponseRedirect
  602. url = concat_error_page_url(error = error)
  603. return HttpResponseRedirect(url)
  604. def WechatAuthDummyRedirect():
  605. url = concat_front_end_url(
  606. uri = before_frag_add_query(
  607. uri = '/public/wechat/authSnapShot.html', added_query = {
  608. 'tip': cn(u'为了更好的为您服务,我们将收集您的微信昵称和头像信息。这些信息将用于订单管理中定位您的消费订单,以便进行后续的服务。')}))
  609. return HttpResponseRedirect(url)
  610. def NotSupportedPlatformResponseRedirect():
  611. # type:()->HttpResponseRedirect
  612. url = concat_error_page_url(error = u'目前只支持通过支付宝、微信或者京东扫码登录')
  613. return HttpResponseRedirect(url)
  614. def NotSupportedPayResponseRedirect(pay_mode_desc):
  615. # type:(basestring)->HttpResponseRedirect
  616. url = concat_error_page_url(error = u'{}未开通,请使用其他支付方式'.format(pay_mode_desc))
  617. return HttpResponseRedirect(url)
  618. # 用户充值URL
  619. def concat_user_recharge_url(l):
  620. return before_frag_add_query(concat_front_end_url(uri = '/user/index.html#/user/charge'), {'logicalCode': l})
  621. UserRechargeResponseRedirect = lambda l: HttpResponseRedirect(concat_user_recharge_url(l))
  622. # 用户充值入口URL
  623. def concat_user_recharge_entry_url(agentId, l):
  624. return concat_user_center_entry_url(agentId = agentId, redirect = concat_user_recharge_url(l = l))
  625. # 用户优惠卡券充值URL
  626. def concat_user_cardTicketList_entry_url(agentId, l):
  627. return concat_user_center_entry_url(agentId=agentId, redirect=before_frag_add_query(
  628. concat_front_end_url(uri='/user/index.html#/user/cardCenter/cardTicketList'), {'logicalCode': l}))
  629. # 经销商主页面URL
  630. def concat_dealer_main_page_url():
  631. return concat_front_end_url(uri = '/app/index.html')
  632. DealerMainPageResponseRedirect = lambda: HttpResponseRedirect(concat_dealer_main_page_url())
  633. # 经销商登录URL
  634. def contact_dealer_login_page_url(register = True):
  635. return add_query(concat_front_end_url(uri = "/app/login.html"), added_query = {"register": register})
  636. DealerLoginPageResponseRedirect = lambda register: HttpResponseRedirect(
  637. contact_dealer_login_page_url(register = register))
  638. # 经销商绑定URL
  639. def concat_dealer_bind_id_page_url(result):
  640. return add_query(concat_front_end_url(uri = "/app/bind-id.html"), added_query = {"result": result})
  641. DealerBindIdResponseRedirect = lambda result: HttpResponseRedirect(concat_dealer_bind_id_page_url(result))
  642. # 经销商子账号登录URL
  643. def contact_sub_account_login_page_url(agentId):
  644. return add_query(concat_front_end_url(uri = "/app/sub-login.html"), added_query = {"agentId": agentId})
  645. SubAccountLoginResponseRedirect = lambda agentId: HttpResponseRedirect(contact_sub_account_login_page_url(agentId))
  646. # 计时页面URL
  647. def concat_count_down_page_url(devNo, port):
  648. if port and int(port) > 0:
  649. added_query = {
  650. 'devNo': devNo,
  651. 'chargeIndex': port
  652. }
  653. else:
  654. added_query = {
  655. 'devNo': devNo
  656. }
  657. return before_frag_add_query(concat_front_end_url(
  658. uri = "/user/index.html#/user/countDown/common"), added_query = added_query)
  659. CountDownResponseRedirect = lambda devNo, port: HttpResponseRedirect(concat_count_down_page_url(devNo, port))
  660. # 蓝牙计时页面URL
  661. def concat_bt_count_down_page_url(l, port):
  662. if port and int(port) > 0:
  663. added_query = {
  664. 'logicalCode': l,
  665. 'chargeIndex': port
  666. }
  667. else:
  668. added_query = {
  669. 'logicalCode': l
  670. }
  671. return before_frag_add_query(concat_front_end_url(
  672. uri = "/user/index.html#/user/countDown/bluetooth"), added_query = added_query)
  673. BtCountDownResponseRedirect = lambda l, port: HttpResponseRedirect(concat_bt_count_down_page_url(l, port))
  674. # 设备实体卡充值页面
  675. def concat_card_recharge_url(l):
  676. return before_frag_add_query(concat_front_end_url(uri = "/user/index.html#/devChargeCard"),
  677. added_query = {"logicalCode": l})
  678. CardRechargeResponseRedirect = lambda l: HttpResponseRedirect(concat_card_recharge_url(l))
  679. # 蓝牙套餐页面URL
  680. def concat_bt_device_url(l, port):
  681. chargeIndex = port or ""
  682. params = {
  683. "logicalCode": l,
  684. "chargeIndex": chargeIndex
  685. }
  686. return before_frag_add_query(concat_front_end_url(uri = "/user/index.html#/bt"), added_query = params)
  687. def BtDeviceResponseRedirect(l, port = None):
  688. return HttpResponseRedirect(concat_bt_device_url(l, port))
  689. # 网络设备套餐页面URL
  690. def concat_net_device_url(l, port):
  691. chargeIndex = port or ""
  692. params = {
  693. "logicalCode": l,
  694. "chargeIndex": chargeIndex
  695. }
  696. return before_frag_add_query(concat_front_end_url(uri = "/user/index.html#/dev"), added_query = params)
  697. def NetDeviceResponseRedirect(l, port = None):
  698. return HttpResponseRedirect(concat_net_device_url(l, port))
  699. # 霍珀门禁页面URL
  700. def concat_huopo_door_url(l, port):
  701. chargeIndex = port or ""
  702. params = {
  703. "chargeIndex": chargeIndex,
  704. "logicalCode": l
  705. }
  706. return before_frag_add_query(concat_front_end_url(uri = "/user/index.html#/door"), added_query = params)
  707. def concat_one_card_gate_url(l, port):
  708. params = {
  709. "logicalCode": l,
  710. "chargeIndex": port
  711. }
  712. return before_frag_add_query(concat_front_end_url(uri = "/user/index.html#/gate"), added_query = params)
  713. def HuopoDoorResponseRedirect(l, port = None):
  714. return HttpResponseRedirect(concat_huopo_door_url(l, port))
  715. def OneCardGateResponseRedirect(l, port):
  716. return HttpResponseRedirect(concat_one_card_gate_url(l, port))
  717. # 使用设备前广告页面
  718. def concat_before_ad_url(devNo):
  719. return add_query(concat_front_end_url(uri = '/pages/index.html'), added_query = {"devNo": devNo})
  720. BeforeAdResponseRedirect = lambda devNo: HttpResponseRedirect(concat_before_ad_url(devNo))
  721. # 广告落地页URL
  722. def concat_ad_landing_url(l, status):
  723. params = {
  724. "status": status,
  725. "logicalCode": l
  726. }
  727. return add_query(concat_front_end_url(uri = "/pages/adLanding.html"), added_query = params)
  728. AdLandingResponseRedirect = lambda l, status: HttpResponseRedirect(concat_ad_landing_url(l, status))
  729. # 广告ACCESS鉴权入口URL
  730. def AdAccessResponseRedirect(adId):
  731. params = {
  732. "connect_redirect": 1,
  733. "state": str(adId)
  734. }
  735. return HttpResponseRedirect(add_query(concat_server_end_url(uri = "/adAccess"), added_query = params))
  736. concat_dealer_pay_detail_url = lambda order_no: concat_front_end_url(
  737. uri = '/app/payOrderDetail.html?orderNo={}'.format(order_no))
  738. # 前端HttpResponseRedirect
  739. FrontEndResponseRedirect = lambda redirect: HttpResponseRedirect(concat_front_end_url(uri = redirect))
  740. # 后端前端HttpResponseRedirect
  741. ServerEndResponseRedirect = lambda redirect: HttpResponseRedirect(concat_server_end_url(uri = redirect))
  742. ExternalResponseRedirect = lambda redirect: HttpResponseRedirect(redirect)
  743. def get_client_ip(request):
  744. if 'HTTP_X_FORWARDED_FOR' in request.META:
  745. ipaddress1 = request.META['HTTP_X_FORWARDED_FOR'].split(',')[0]
  746. logger.debug('HTTP_X_FORWARDED_FOR = {}'.format(ipaddress1))
  747. if len(ipaddress1) > 0:
  748. return ipaddress1
  749. ipaddress2 = request.META.get('REMOTE_ADDR', '')
  750. logger.debug('REMOTE_ADDR = {}'.format(ipaddress2))
  751. if len(ipaddress2) > 0:
  752. return ipaddress2
  753. ipaddress3 = request.META.get('HTTP_X_REAL_IP', '')
  754. logger.debug('HTTP_X_REAL_IP = {}'.format(ipaddress3))
  755. if len(ipaddress3) > 0:
  756. return ipaddress3
  757. return ''
  758. def record_operation_behavior():
  759. """
  760. 将户或进销商关键且非高频操作记录到数据库
  761. """
  762. def decorator(view_func):
  763. @wraps(view_func, assigned=available_attrs(view_func))
  764. def _wrapped_view(request, *args, **kwargs):
  765. response = view_func(request, *args, **kwargs)
  766. try:
  767. from apps.web.device.models import OperatorLog
  768. user = request.user
  769. OperatorLog.log(user=user,
  770. level=OperatorLog.LogLevel.CRITICAL,
  771. operator_name=request.path,
  772. content={
  773. 'request': force_text(request.body),
  774. 'response': response.json()
  775. })
  776. except Exception:
  777. pass
  778. return response
  779. return _wrapped_view
  780. return decorator
  781. def get_alipay_gateway_result(gateway,
  782. out_trade_no,
  783. money,
  784. subject,
  785. buyer_id,
  786. body,
  787. notify_url):
  788. # type: (AliPayGateway, str, RMB, basestring, basestring, str, basestring)->dict
  789. """
  790. :param gateway:
  791. :param out_trade_no:
  792. :param money:
  793. :param subject:
  794. :param buyer_id:
  795. :param body:
  796. :return:
  797. """
  798. # : 用于支付宝返佣
  799. if gateway.appid == settings.DEFAULT_ALIPAY_APP_ID:
  800. result = gateway.api_alipay_trade_create(out_trade_no = out_trade_no,
  801. money = money,
  802. subject = subject,
  803. buyer_id = buyer_id,
  804. notify_url = notify_url,
  805. body = body,
  806. **{
  807. 'extend_params':
  808. {
  809. 'sys_service_provider_id': settings.ALIPAY_SYS_PROVIDER_ID
  810. }
  811. })
  812. else:
  813. result = gateway.api_alipay_trade_create(out_trade_no = out_trade_no,
  814. money = money,
  815. subject = subject,
  816. buyer_id = buyer_id,
  817. notify_url = notify_url,
  818. body = body)
  819. result.setdefault('trade_no', None)
  820. return result