Commit 5c17dea5 by Aeolus

update

parent 9e71eecf
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
config/.env/
venv/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
__pycache__/
.idea/
*.pyc
.gz
.txt
*.zip
.log*
*.env
*.rar
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Cert
*.pem
*.pfx
*.key
*.crt
*.cer
*.truststore
# IDE
*.DS_Store
# Media
*.jpg
#Document
*.xls
env_path_config.py
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@time: 2021/03/25
@file: app_config.py
@function:
@modify:
"""
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.environ.get("SQLALCHEMY_DATABASE_URI")
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_RECORD_QUERIES = True
SQLALCHEMY_POOL_SIZE = 10
SQLALCHEMY_POOL_RECYCLE = 1800
JWT_SECRET = SECRET_KEY
TENCENT_REDIS_URL = os.getenv("TENCENT_REDIS_URL")
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
config = {
'dev': DevelopmentConfig,
'prod': ProductionConfig,
'default': DevelopmentConfig
}
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: base_config.py
"""
import os
SECRET_KEY = os.getenv('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI")
MONGO_DATABASE_URI = os.getenv("MONGO_DATABASE_URI")
TENCENT_REDIS_URL = os.getenv("TENCENT_REDIS_URL")
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: commen_config.py
"""
import os
SMS_CONFIG = {
'app_id': os.getenv("SMS_APP_ID"),
'app_key': os.getenv("SMS_APP_KEY")
}
EMAIL_CONFIG = {
'KEFU_EMAIL': os.getenv("EMAIL_ACCOUNT"),
'PASSWORD': os.getenv("EMAIL_PASSWORD"),
'SMTP_SERVER': os.getenv("EMAIL_SMTP_SERVER"),
'KEFU_NAME': '晓兔',
'SUBJECT': '电子发票已开具'
}
IMG_HEAD_URL = "https://dev-1255927177.cos.ap-shanghai.myqcloud.com"
IMG_DOMAIN_URL = "https://dev-1255927177.file.myqcloud.com"
# 微信session_key
WX_SESSION_KEY = "W_S_K_"
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: wechat_config.py
"""
import os
SK_CONFIG = {
"app_id": os.getenv("SK_MINI_PROGRAM_APPID"),
"app_secret": os.getenv("SK_MINI_PROGRAM_APPSECRET"),
}
SSW_PAY_CONFIG = {
"mch_id": os.getenv("SSW_MCHID"),
"pay_key": os.getenv("SSW_PAY_KEY"),
"cert_path": os.getenv("SSW_PAY_SSL_CERT_PATH"),
"key_path": os.getenv("SSW_PAY_SSL_KEY_PATH"),
'callback_url': '/rent/wx_pay_callback',
'refund_callback_url': '/rent/refund_callback',
'domain': 'https://guide.ssw-htzn.com/v2/tour/'
}
XX_PAY_CONFIG = {
"mch_id": os.getenv("SSW_MCHID"),
"pay_key": os.getenv("SSW_PAY_KEY"),
"cert_path": os.getenv("SSW_PAY_SSL_CERT_PATH"),
"key_path": os.getenv("SSW_PAY_SSL_KEY_PATH"),
'callback_url': '/rent/wx_pay_callback',
'refund_callback_url': '/rent/refund_callback',
'domain': 'https://guide.ssw-htzn.com/v2/tour/'
}
pay_config_list = ["", "ssw", "xx"]
pay_config_dict = {
"ssw": SSW_PAY_CONFIG,
"xx": XX_PAY_CONFIG,
}
platform_config_list = ["", "sukang24h", ]
platform_config_dict = {
"sukang24h": SK_CONFIG,
}
platform_appid_config_list = [
"",
SK_CONFIG["app_id"], # 苏康24H 平台序号 ==>1
]
platform_appid_config_dict = {
"sukang24h": SK_CONFIG["app_id"],
}
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: __init__.py.py
"""
\ No newline at end of file
# -*- coding: utf-8 -*-
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, text
from sqlalchemy.dialects.mysql import INTEGER, TIMESTAMP
from sqlalchemy.ext.declarative import declarative_base
db = SQLAlchemy(session_options={"autoflush": False})
class Base(db.Model):
__abstract__ = True
id = Column(INTEGER, primary_key=True)
created_at = Column(TIMESTAMP, nullable=False, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, nullable=False, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
# coding: utf-8
from sqlalchemy import Column, DateTime, Index, String, TIMESTAMP, Text, text
from sqlalchemy.dialects.mysql import CHAR, INTEGER, TINYINT, VARCHAR
from models.base_model import Base
class Hatch(Base):
__tablename__ = 'hatch'
__table_args__ = (
Index('hatch_machine_UNIQUE', 'machine_id', 'hatch_no', unique=True),
)
id = Column(INTEGER(10), primary_key=True, unique=True)
machine_id = Column(INTEGER(10), comment='机柜id')
hatch_no = Column(TINYINT(3), comment='机柜仓口号')
production_id = Column(INTEGER(10), comment='商品id')
status = Column(TINYINT(3), nullable=False, server_default=text("'1'"),
comment='充电宝状态1在仓库2在机柜可用3在机柜占用4出货成功5永久锁 7未清洁 8手动弹出')
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class Machine(Base):
__tablename__ = 'machine'
id = Column(INTEGER(10), primary_key=True)
mac_no = Column(CHAR(17), nullable=False, unique=True, comment='机柜编号')
position = Column(String(20, 'utf8mb4_unicode_ci'), comment='机柜位置坐标')
short_address = Column(VARCHAR(45))
address = Column(VARCHAR(191), comment='机柜位置')
place_id = Column(INTEGER(10), nullable=False)
mch_platform = Column(INTEGER(11), nullable=False, server_default=text("'1'"), comment='1随身玩 2晓见文旅')
type = Column(TINYINT(3), nullable=False, server_default=text("'1'"), comment='机柜类型1正常')
hatch_number = Column(TINYINT(3), nullable=False, server_default=text("'60'"), comment='机柜的仓口数量')
created_at = Column(TIMESTAMP, nullable=False, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, nullable=False, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class MachineProduction(Base):
__tablename__ = 'machine_production'
id = Column(INTEGER(10), primary_key=True)
machine_id = Column(INTEGER(10), nullable=False)
production_id = Column(INTEGER(10), nullable=False)
hatch_no = Column(INTEGER(10), nullable=False)
status = Column(TINYINT(1), nullable=False, comment='状态: 1整除-1删除')
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class Place(Base):
__tablename__ = 'place'
id = Column(INTEGER(10), primary_key=True)
place_name = Column(VARCHAR(191), nullable=False, index=True, comment='场所名')
main_title = Column(VARCHAR(191), nullable=False, comment='主标题')
vic_title = Column(VARCHAR(191), nullable=False, comment='副标题')
img = Column(VARCHAR(191), comment='展示界面的图片')
logo = Column(VARCHAR(191), nullable=False, comment='微型头像')
address = Column(VARCHAR(255), nullable=False, server_default=text("''"))
position = Column(String(20, 'utf8mb4_unicode_ci'))
open_time = Column(VARCHAR(191), nullable=False, comment='开始时间')
close_time = Column(VARCHAR(191), nullable=False, comment='结束时间')
open_week = Column(VARCHAR(255), nullable=False, server_default=text("''"))
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class Production(Base):
__tablename__ = 'production'
id = Column(INTEGER(10), primary_key=True)
name = Column(String(100, 'utf8mb4_unicode_ci'), nullable=False, index=True, comment='商品名称')
titile = Column(String(200, 'utf8mb4_unicode_ci'), nullable=False, comment='商品标题')
brand_id = Column(INTEGER(10), nullable=False, comment='品牌ID')
cate_id = Column(INTEGER(10), nullable=False, comment='分类ID')
price = Column(INTEGER(10), nullable=False, comment='价格')
original_price = Column(INTEGER(10), nullable=False, comment='商品原价')
tags = Column(String(255, 'utf8mb4_unicode_ci'), nullable=False, comment='商品标签')
content = Column(Text(collation='utf8mb4_unicode_ci'), nullable=False, comment='商品内容')
summary = Column(Text(collation='utf8mb4_unicode_ci'), nullable=False, comment='商品描述')
status = Column(TINYINT(1), nullable=False, comment='状态: 1整除-1删除')
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class Rent(Base):
__tablename__ = 'rent'
id = Column(INTEGER(10), primary_key=True)
rent_no = Column(VARCHAR(40), nullable=False, index=True, comment='租借单号')
machine_id = Column(INTEGER(10), nullable=False, index=True, comment='机柜id')
user_id = Column(INTEGER(10), nullable=False, index=True, comment='用户id')
place_id = Column(INTEGER(10), nullable=False, index=True, comment='场所id')
total = Column(INTEGER(10), server_default=text("'0'"), comment='应收金额')
real_total = Column(INTEGER(10), server_default=text("'0'"), comment='实收金额')
agent_total = Column(INTEGER(10), server_default=text("'0'"), comment='给代理商看的收入')
back_money = Column(INTEGER(10), nullable=False, server_default=text("'0'"), comment='退款金额')
is_pay = Column(TINYINT(3), nullable=False, server_default=text("'0'"), comment='是否支付')
rent_type = Column(TINYINT(3), nullable=False, server_default=text("'1'"), comment='租借类型1现场租借2预约3nfc租借')
mch_platform = Column(INTEGER(1), nullable=False, server_default=text("'1'"), comment='1待定')
add_time = Column(TIMESTAMP, nullable=False, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
comment='下单时间')
pay_time = Column(TIMESTAMP, comment='支付时间')
is_take = Column(TINYINT(3), nullable=False, server_default=text("'0'"), comment='是否取货')
take_time = Column(TIMESTAMP, comment='取货时间')
is_over = Column(TINYINT(3), nullable=False, server_default=text("'0'"), comment='是否完结')
is_cancel = Column(TINYINT(3), nullable=False, server_default=text("'0'"), comment='是否取消交易')
refund_no = Column(VARCHAR(191), comment='退款单号')
expire_handle = Column(TINYINT(3), nullable=False, server_default=text("'0'"), comment='是否做过期处理')
prepay_id = Column(VARCHAR(191), comment='微信支付prepay_id')
over_time = Column(TIMESTAMP, comment='订单完结时间')
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class SalePlan(Base):
__tablename__ = 'sale_plan'
id = Column(INTEGER(10), primary_key=True)
name = Column(String(100, 'utf8mb4_unicode_ci'), nullable=False, comment='方案名称')
titile = Column(String(200, 'utf8mb4_unicode_ci'), nullable=False, comment='方案标题')
status = Column(TINYINT(1), nullable=False, comment='状态: 1整除-1删除')
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class SalePlanMachine(Base):
__tablename__ = 'sale_plan_machine'
id = Column(INTEGER(10), primary_key=True)
plan_id = Column(INTEGER(10), nullable=False)
machine_id = Column(INTEGER(10), nullable=False)
status = Column(TINYINT(1), nullable=False, comment='状态: 1正常 -1删除')
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class SalePlanProduction(Base):
__tablename__ = 'sale_plan_production'
id = Column(INTEGER(10), primary_key=True)
plan_id = Column(INTEGER(10), nullable=False)
production_id = Column(INTEGER(10), nullable=False)
index = Column(INTEGER(10), nullable=False)
status = Column(TINYINT(1), nullable=False, comment='状态: 1整除-1删除')
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class TallymanAccount(Base):
__tablename__ = 'tallyman_account'
id = Column(INTEGER(10), primary_key=True, unique=True)
user_no = Column(String(25, 'utf8mb4_unicode_ci'), nullable=False, unique=True)
user_name = Column(String(255, 'utf8mb4_unicode_ci'), nullable=False)
phone = Column(String(191, 'utf8mb4_unicode_ci'), nullable=False, unique=True)
level = Column(INTEGER(1), nullable=False, comment='1:补货员')
status = Column(INTEGER(1), nullable=False, comment='1:正常 2:删除')
password = Column(String(255, 'utf8mb4_unicode_ci'))
comment = Column(String(255, 'utf8mb4_unicode_ci'))
last_login = Column(DateTime)
expire_time = Column(DateTime)
created_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class TallymanMachine(Base):
__tablename__ = 'tallyman_machine'
__table_args__ = (
Index('index4tallymachine_user_machine_unique', 'user_id', 'machine_id', unique=True),
)
id = Column(INTEGER(10), primary_key=True)
user_id = Column(INTEGER(10), nullable=False, index=True)
machine_id = Column(INTEGER(10), nullable=False, index=True)
status = Column(INTEGER(1), nullable=False, index=True, comment='1:正常 -1:删除')
created_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
class WxUser(Base):
__tablename__ = 'wx_user'
__table_args__ = {'comment': '微信用户表'}
id = Column(INTEGER(10), primary_key=True)
openid = Column(String(40, 'utf8mb4_unicode_ci'), index=True, comment='微信支付宝公众平台openID')
unionid = Column(String(40, 'utf8mb4_unicode_ci'), comment='微信支付宝unionid')
platform = Column(TINYINT(4), nullable=False, server_default=text("'0'"), comment='平台')
phone = Column(String(40, 'utf8mb4_unicode_ci'), index=True, comment='手机号')
language = Column(String(40, 'utf8mb4_unicode_ci'), comment='语种')
nick_name = Column(String(40, 'utf8mb4_unicode_ci'), comment='昵称')
gender = Column(TINYINT(4), nullable=False, server_default=text("'0'"), comment='性别 0:未知、1:男、2:女')
avatar_url = Column(String(191, 'utf8mb4_unicode_ci'), comment='头像')
city = Column(String(45, 'utf8mb4_unicode_ci'))
province = Column(String(45, 'utf8mb4_unicode_ci'))
country = Column(String(45, 'utf8mb4_unicode_ci'))
status = Column(TINYINT(4), nullable=False, comment='状态0停用1正常')
last_login_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
comment='上次登录时间')
created_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"))
updated_at = Column(TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
@author:Aeolus
@file: __init__.py
@function:
@modify:
"""
from flask import Flask
from flask_cors import CORS
from flask_log_request_id import RequestID
from dotenv import load_dotenv
from models.base_model import db
from utils.my_redis_cache import redis_client
from utils.mylogger import set_logger
def create_app(config_name):
from config.env_path_config import env_path
load_dotenv(dotenv_path=env_path, verbose=True, override=True)
set_logger()
app = Flask("sukang24h")
from config.app_config import config
app.config.from_object(config[config_name])
CORS(app)
db.init_app(app)
redis_client.init_app(app)
RequestID(app)
from utils.middlewares import jwt_authentication, log_enter_interface, log_out_interface, close_db_session, \
get_platform, all_options_pass
app.before_request(log_enter_interface)
app.before_request(all_options_pass)
app.before_request(get_platform)
app.before_request(jwt_authentication)
app.after_request(log_out_interface)
app.after_request(close_db_session)
# todo register blueprint
from myapps.sukang24h.api import register_sukang_blueprint
register_sukang_blueprint(app)
return app
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: __init__.py.py
"""
from flask import Flask
from myapps.sukang24h.api.wx_auth import wx_auth_route
def register_sukang_blueprint(app: Flask):
prefix = "/sukang"
app.register_blueprint(wx_auth_route, url_prefix=prefix + "/wx_auth")
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: wx_auth.py
"""
import logging
import time
from flask import Blueprint, request, g, jsonify
from wechatpy.crypto import WeChatWxaCrypto
from config.commen_config import WX_SESSION_KEY
from config.wechat_config import platform_appid_config_dict, platform_appid_config_list
from models.base_model import db
from models.models import WxUser
from service.wechat_service import WxMPService
from utils.error_code import WX_LOGIN_DATA_ERROR, WX_LOGIN_CODE_ERROR, PHONE_NOT_BINDING_ERROR, TOKEN_EXPIRE_ERROR
from utils.jwt_util import generate_jwt
from utils.my_redis_cache import redis_client
from utils.my_response import BaseResponse
logger = logging.getLogger(__name__)
wx_auth_route = Blueprint('wx_auth', __name__)
@wx_auth_route.route('/test', methods=['POST'])
def my_test():
a = {'error_code': '0', 'error_message': 'Success'}
return jsonify(a)
@wx_auth_route.route('/login_wx_mp', methods=['POST'])
def mini_login():
"""
小程序登陆接口
:return:
"""
req = request.get_json()
platform = req.get('platform')
code = req.get('code')
encrypted_data = req.get('encryptedData')
iv = req.get('iv')
if not code or not encrypted_data or not iv or not platform:
return jsonify(WX_LOGIN_DATA_ERROR)
app_id = platform_appid_config_dict.get(platform)
logger.info(platform_appid_config_dict)
wx_client = WxMPService(platform)
user_session = wx_client.code_to_session(code)
logger.info(user_session)
if user_session is None:
logger.info(WX_LOGIN_CODE_ERROR)
return jsonify(WX_LOGIN_CODE_ERROR)
try:
user_info = WeChatWxaCrypto(user_session["session_key"], iv, app_id).decrypt_message(encrypted_data)
except:
try:
session_key = redis_client.get(WX_SESSION_KEY + user_session["openid"])
user_info = WeChatWxaCrypto(session_key, iv, app_id).decrypt_message(encrypted_data)
except:
return BaseResponse(error_code=500, error_message='user info decrypt error')
is_new_user = 0
wx_user_model = WxUser.query.filter_by(openid=user_session["openid"]).first()
if not wx_user_model:
wx_user_model = WxUser()
wx_user_model.status = 1
is_new_user = 1
wx_user_model.mini_program_open_id = user_session["openid"]
if user_session.get("unionid", None):
wx_user_model.unionid = user_session["unionid"]
wx_user_model.nick_name = user_info["nickName"]
wx_user_model.gender = user_info["gender"]
wx_user_model.language = user_info["language"]
wx_user_model.avatar_url = user_info["avatarUrl"]
wx_user_model.city = user_info["city"]
wx_user_model.province = user_info["province"]
wx_user_model.country = user_info["country"]
# wx_user_model.platform = 1
wx_user_model.platform = platform_appid_config_list.index(app_id)
db.session.add(wx_user_model)
db.session.commit()
token = generate_jwt(payload={"user_id": wx_user_model.id}, expiry=time.time() + 24 * 60 * 60)
return jsonify({
'customer_id': wx_user_model.id,
'token': token,
'error_code': 0,
"is_new_user": is_new_user
})
@wx_auth_route.route('/checkPhone', methods=["POST"])
def check_phone():
"""
验证手机号是否登陆过接口
:return:
"""
if not g.user.phone:
return jsonify(PHONE_NOT_BINDING_ERROR)
return BaseResponse(data={'phone_number': g.user.phone})
@wx_auth_route.route('/bindPhone', methods=['POST'])
def bind_phone():
'''
绑定手机号接口
:return:
'''
req = request.get_json()
encrypted_data = req.get('encryptedData', '')
iv = req.get('iv', '')
if not encrypted_data or not iv:
logger.info(WX_LOGIN_DATA_ERROR)
return BaseResponse(**WX_LOGIN_DATA_ERROR)
user_info = WxUser.query.filter_by(id=g.user.id).first()
if not user_info:
return BaseResponse(**WX_LOGIN_DATA_ERROR)
try:
session_key = redis_client.get(WX_SESSION_KEY + user_info.mini_program_open_id)
session_key = str(session_key, encoding='utf-8')
except Exception:
session_key = None
if not session_key:
return jsonify(TOKEN_EXPIRE_ERROR)
try:
app_id = platform_appid_config_list[g.user.platform]
phone_info = WeChatWxaCrypto(session_key, iv, app_id).decrypt_message(
encrypted_data)
except Exception as e:
logger.error(e)
return BaseResponse(error_code=500, error_message='phone info decrypt error')
user_info.phone = phone_info['phoneNumber']
db.session.add(user_info)
db.session.commit()
return BaseResponse()
[[package]]
name = "certifi"
version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "charset-normalizer"
version = "2.0.6"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.5.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.0.1"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "flask"
version = "2.0.2"
description = "A simple framework for building complex web applications."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
click = ">=7.1.2"
itsdangerous = ">=2.0"
Jinja2 = ">=3.0"
Werkzeug = ">=2.0"
[package.extras]
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
[[package]]
name = "flask-cors"
version = "3.0.10"
description = "A Flask extension adding a decorator for CORS support"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
Flask = ">=0.9"
Six = "*"
[[package]]
name = "flask-log-request-id"
version = "0.10.1"
description = "Flask extension that can parse and handle multiple types of request-id sent by request processors like Amazon ELB, Heroku or any multi-tier infrastructure as the one used for microservices."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
Flask = ">=0.8"
[package.extras]
test = ["nose", "flake8", "mock (==2.0.0)", "coverage (>=4.5.4,<4.6.0)", "celery (>=4.3.0,<4.4.0)"]
[[package]]
name = "flask-redis"
version = "0.4.0"
description = "A nice way to use Redis in your Flask app"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
Flask = ">=0.8"
redis = ">=2.7.6"
[package.extras]
dev = ["coverage", "pytest", "pytest-mock", "pre-commit"]
tests = ["coverage", "pytest", "pytest-mock"]
[[package]]
name = "flask-sqlalchemy"
version = "2.5.1"
description = "Adds SQLAlchemy support to your Flask application."
category = "main"
optional = false
python-versions = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*"
[package.dependencies]
Flask = ">=0.10"
SQLAlchemy = ">=0.8.0"
[[package]]
name = "greenlet"
version = "1.1.2"
description = "Lightweight in-process concurrent programming"
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
[package.extras]
docs = ["sphinx"]
[[package]]
name = "idna"
version = "3.2"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "4.8.1"
description = "Read metadata from Python packages"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
perf = ["ipython"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[[package]]
name = "itsdangerous"
version = "2.0.1"
description = "Safely pass data to untrusted environments and back."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "jinja2"
version = "3.0.2"
description = "A very fast and expressive template engine."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "markupsafe"
version = "2.0.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "optionaldict"
version = "0.1.2"
description = "A dict-like object that ignore NoneType values for Python"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pycryptodome"
version = "3.11.0"
description = "Cryptographic library for Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pyjwt"
version = "2.2.0"
description = "JSON Web Token implementation in Python"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
crypto = ["cryptography (>=3.3.1)"]
dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"]
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
[[package]]
name = "pymysql"
version = "1.0.2"
description = "Pure Python MySQL Driver"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
ed25519 = ["PyNaCl (>=1.4.0)"]
rsa = ["cryptography"]
[[package]]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
[package.dependencies]
six = ">=1.5"
[[package]]
name = "python-dotenv"
version = "0.19.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
python-versions = ">=3.5"
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
name = "redis"
version = "3.5.3"
description = "Python client for Redis key-value store"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
hiredis = ["hiredis (>=0.1.3)"]
[[package]]
name = "requests"
version = "2.26.0"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "sqlalchemy"
version = "1.4.25"
description = "Database Abstraction Library"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
[package.dependencies]
greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
aiomysql = ["greenlet (!=0.4.17)", "aiomysql"]
aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.0)"]
mariadb_connector = ["mariadb (>=1.0.1)"]
mssql = ["pyodbc"]
mssql_pymssql = ["pymssql"]
mssql_pyodbc = ["pyodbc"]
mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"]
mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"]
mysql_connector = ["mysql-connector-python"]
oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"]
postgresql_pg8000 = ["pg8000 (>=1.16.6)"]
postgresql_psycopg2binary = ["psycopg2-binary"]
postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql (<1)", "pymysql"]
sqlcipher = ["sqlcipher3-binary"]
[[package]]
name = "typing-extensions"
version = "3.10.0.2"
description = "Backported and Experimental Type Hints for Python 3.5+"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "urllib3"
version = "1.26.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "wechatpy"
version = "1.8.15"
description = "WeChat SDK for Python"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
optionaldict = ">=0.1.0"
python-dateutil = ">=2.5.2"
requests = ">=2.4.3"
six = ">=1.8.0"
xmltodict = ">=0.11.0"
[package.extras]
cryptography = ["cryptography"]
pycrypto = ["pycryptodome"]
[[package]]
name = "werkzeug"
version = "2.0.2"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
watchdog = ["watchdog"]
[[package]]
name = "xmltodict"
version = "0.12.0"
description = "Makes working with XML feel like you are working with JSON"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "zipp"
version = "3.6.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "34c65929396d55ce62fcc01154b0fc321bb78e1435e3012d46b7dfd28fcedcc0"
[metadata.files]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"},
{file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
]
click = [
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
flask = [
{file = "Flask-2.0.2-py3-none-any.whl", hash = "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a"},
{file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"},
]
flask-cors = [
{file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"},
{file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"},
]
flask-log-request-id = [
{file = "Flask-Log-Request-ID-0.10.1.tar.gz", hash = "sha256:d537a1af3776308e69435ea609230f4fb7ef162fbc4bf268d7089f0b0e1851f4"},
{file = "Flask_Log_Request_ID-0.10.1-py3-none-any.whl", hash = "sha256:04b2ab0d7eca13a816155d16dc5e5dee6a0b285c62ec2c2330394c946ff418ce"},
]
flask-redis = [
{file = "flask-redis-0.4.0.tar.gz", hash = "sha256:e1fccc11e7ea35c2a4d68c0b9aa58226a098e45e834d615c7b6c4928b01ddd6c"},
{file = "flask_redis-0.4.0-py2.py3-none-any.whl", hash = "sha256:8d79eef4eb1217095edab603acc52f935b983ae4b7655ee7c82c0dfd87315d17"},
]
flask-sqlalchemy = [
{file = "Flask-SQLAlchemy-2.5.1.tar.gz", hash = "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912"},
{file = "Flask_SQLAlchemy-2.5.1-py2.py3-none-any.whl", hash = "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390"},
]
greenlet = [
{file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"},
{file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"},
{file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"},
{file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"},
{file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"},
{file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"},
{file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"},
{file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"},
{file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"},
{file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"},
{file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"},
{file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"},
{file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"},
{file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"},
{file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"},
{file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"},
{file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"},
{file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"},
{file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"},
{file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"},
{file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"},
{file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"},
{file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"},
{file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"},
{file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"},
{file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"},
{file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"},
{file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"},
{file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"},
{file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"},
{file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"},
{file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"},
]
idna = [
{file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
]
importlib-metadata = [
{file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
{file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
]
itsdangerous = [
{file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"},
{file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"},
]
jinja2 = [
{file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"},
{file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"},
]
markupsafe = [
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
{file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
{file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
{file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
{file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
{file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
{file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
{file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
{file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
]
optionaldict = [
{file = "optionaldict-0.1.2-py2.py3-none-any.whl", hash = "sha256:278d9b79b3d6a55b1b8ff5f9017bff6383a33426348d09e699ff50162bbcdab7"},
{file = "optionaldict-0.1.2.tar.gz", hash = "sha256:175b62ee8259af703def42e0b44b413ea55e7b0732456cb2fd5cbbc998605966"},
]
pycryptodome = [
{file = "pycryptodome-3.11.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ffd0cac13ff41f2d15ed39dc6ba1d2ad88dd2905d656c33d8235852f5d6151fd"},
{file = "pycryptodome-3.11.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:ead516e03dfe062aefeafe4a29445a6449b0fc43bc8cb30194b2754917a63798"},
{file = "pycryptodome-3.11.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4ce6b09547bf2c7cede3a017f79502eaed3e819c13cdb3cb357aea1b004e4cc6"},
{file = "pycryptodome-3.11.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:014c758af7fa38cab85b357a496b76f4fc9dda1f731eb28358d66fef7ad4a3e1"},
{file = "pycryptodome-3.11.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a843350d08c3d22f6c09c2f17f020d8dcfa59496165d7425a3fba0045543dda7"},
{file = "pycryptodome-3.11.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:53989477044be41fa4a63da09d5038c2a34b2f4554cfea2e3933b17186ee9e19"},
{file = "pycryptodome-3.11.0-cp27-cp27m-win32.whl", hash = "sha256:f9bad2220b80b4ed74f089db012ab5ab5419143a33fad6c8aedcc2a9341eac70"},
{file = "pycryptodome-3.11.0-cp27-cp27m-win_amd64.whl", hash = "sha256:3c7ed5b07274535979c730daf5817db5e983ea80b04c22579eee8da4ca3ae4f8"},
{file = "pycryptodome-3.11.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:8f3a60926be78422e662b0d0b18351b426ce27657101c8a50bad80300de6a701"},
{file = "pycryptodome-3.11.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:fce7e22d96030b35345637c563246c24d4513bd3b413e1c40293114837ab8912"},
{file = "pycryptodome-3.11.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:bc3c61ff92efdcc14af4a7b81da71d849c9acee51d8fd8ac9841a7620140d6c6"},
{file = "pycryptodome-3.11.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:b33c9b3d1327d821e28e9cc3a6512c14f8b17570ddb4cfb9a52247ed0fcc5d8b"},
{file = "pycryptodome-3.11.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:75e78360d1dd6d02eb288fd8275bb4d147d6e3f5337935c096d11dba1fa84748"},
{file = "pycryptodome-3.11.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:621a90147a5e255fdc2a0fec2d56626b76b5d72ea9e60164c9a5a8976d45b0c9"},
{file = "pycryptodome-3.11.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:0ca7a6b4fc1f9fafe990b95c8cda89099797e2cfbf40e55607f2f2f5a3355dcb"},
{file = "pycryptodome-3.11.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:b59bf823cfafde8ef1105d8984f26d1694dff165adb7198b12e3e068d7999b15"},
{file = "pycryptodome-3.11.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:ce81b9c6aaa0f920e2ab05eb2b9f4ccd102e3016b2f37125593b16a83a4b0cc2"},
{file = "pycryptodome-3.11.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:ae29fcd56152f417bfba50a36a56a7a5f9fb74ff80bab98704cac704de6568ab"},
{file = "pycryptodome-3.11.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:ae31cb874f6f0cedbed457c6374e7e54d7ed45c1a4e11a65a9c80968da90a650"},
{file = "pycryptodome-3.11.0-cp35-abi3-win32.whl", hash = "sha256:6db1f9fa1f52226621905f004278ce7bd90c8f5363ffd5d7ab3755363d98549a"},
{file = "pycryptodome-3.11.0-cp35-abi3-win_amd64.whl", hash = "sha256:d7e5f6f692421e5219aa3b545eb0cffd832cd589a4b9dcd4a5eb4260e2c0d68a"},
{file = "pycryptodome-3.11.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:da796e9221dda61a0019d01742337eb8a322de8598b678a4344ca0a436380315"},
{file = "pycryptodome-3.11.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:ed45ef92d21db33685b789de2c015e9d9a18a74760a8df1fc152faee88cdf741"},
{file = "pycryptodome-3.11.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4169ed515742425ff21e4bd3fabbb6994ffb64434472fb72230019bdfa36b939"},
{file = "pycryptodome-3.11.0-pp27-pypy_73-win32.whl", hash = "sha256:f19edd42368e9057c39492947bb99570dc927123e210008f2af7cf9b505c6892"},
{file = "pycryptodome-3.11.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06162fcfed2f9deee8383fd59eaeabc7b7ffc3af50d3fad4000032deb8f700b0"},
{file = "pycryptodome-3.11.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:6eda8a3157c91ba60b26a07bedd6c44ab8bda6cd79b6b5ea9744ba62c39b7b1e"},
{file = "pycryptodome-3.11.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ff701fc283412e651eaab4319b3cd4eaa0827e94569cd37ee9075d5c05fe655"},
{file = "pycryptodome-3.11.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:2a4bcc8a9977fee0979079cd33a9e9f0d3ddba5660d35ffe874cf84f1dd399d2"},
{file = "pycryptodome-3.11.0.tar.gz", hash = "sha256:428096bbf7a77e207f418dfd4d7c284df8ade81d2dc80f010e92753a3e406ad0"},
]
pyjwt = [
{file = "PyJWT-2.2.0-py3-none-any.whl", hash = "sha256:b0ed5824c8ecc5362e540c65dc6247567db130c4226670bf7699aec92fb4dae1"},
{file = "PyJWT-2.2.0.tar.gz", hash = "sha256:a0b9a3b4e5ca5517cac9f1a6e9cd30bf1aa80be74fcdf4e28eded582ecfcfbae"},
]
pymysql = [
{file = "PyMySQL-1.0.2-py3-none-any.whl", hash = "sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641"},
{file = "PyMySQL-1.0.2.tar.gz", hash = "sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
]
python-dotenv = [
{file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
{file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
]
redis = [
{file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
{file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
]
requests = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
sqlalchemy = [
{file = "SQLAlchemy-1.4.25-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:a36ea43919e51b0de0c0bc52bcfdad7683f6ea9fb81b340cdabb9df0e045e0f7"},
{file = "SQLAlchemy-1.4.25-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:75cd5d48389a7635393ff5a9214b90695c06b3d74912109c3b00ce7392b69c6c"},
{file = "SQLAlchemy-1.4.25-cp27-cp27m-win32.whl", hash = "sha256:16ef07e102d2d4f974ba9b0d4ac46345a411ad20ad988b3654d59ff08e553b1c"},
{file = "SQLAlchemy-1.4.25-cp27-cp27m-win_amd64.whl", hash = "sha256:a79abdb404d9256afb8aeaa0d3a4bc7d3b6d8b66103d8b0f2f91febd3909976e"},
{file = "SQLAlchemy-1.4.25-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7ad59e2e16578b6c1a2873e4888134112365605b08a6067dd91e899e026efa1c"},
{file = "SQLAlchemy-1.4.25-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a505ecc0642f52e7c65afb02cc6181377d833b7df0994ecde15943b18d0fa89c"},
{file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a28fe28c359835f3be20c89efd517b35e8f97dbb2ca09c6cf0d9ac07f62d7ef6"},
{file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:41a916d815a3a23cb7fff8d11ad0c9b93369ac074e91e428075e088fe57d5358"},
{file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:842c49dd584aedd75c2ee05f6c950730c3ffcddd21c5824ed0f820808387e1e3"},
{file = "SQLAlchemy-1.4.25-cp36-cp36m-win32.whl", hash = "sha256:6b602e3351f59f3999e9fb8b87e5b95cb2faab6a6ecdb482382ac6fdfbee5266"},
{file = "SQLAlchemy-1.4.25-cp36-cp36m-win_amd64.whl", hash = "sha256:6400b22e4e41cc27623a9a75630b7719579cd9a3a2027bcf16ad5aaa9a7806c0"},
{file = "SQLAlchemy-1.4.25-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:dd4ed12a775f2cde4519f4267d3601990a97d8ecde5c944ab06bfd6e8e8ea177"},
{file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b7778a205f956755e05721eebf9f11a6ac18b2409bff5db53ce5fe7ede79831"},
{file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:08d9396a2a38e672133266b31ed39b2b1f2b5ec712b5bff5e08033970563316a"},
{file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e93978993a2ad0af43f132be3ea8805f56b2f2cd223403ec28d3e7d5c6d39ed1"},
{file = "SQLAlchemy-1.4.25-cp37-cp37m-win32.whl", hash = "sha256:0566a6e90951590c0307c75f9176597c88ef4be2724958ca1d28e8ae05ec8822"},
{file = "SQLAlchemy-1.4.25-cp37-cp37m-win_amd64.whl", hash = "sha256:0b08a53e40b34205acfeb5328b832f44437956d673a6c09fce55c66ab0e54916"},
{file = "SQLAlchemy-1.4.25-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:33a1e86abad782e90976de36150d910748b58e02cd7d35680d441f9a76806c18"},
{file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ed67aae8cde4d32aacbdba4f7f38183d14443b714498eada5e5a7a37769c0b7"},
{file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ebd69365717becaa1b618220a3df97f7c08aa68e759491de516d1c3667bba54"},
{file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0cd2d5c7ea96d3230cb20acac3d89de3b593339c1447b4d64bfcf4eac1110"},
{file = "SQLAlchemy-1.4.25-cp38-cp38-win32.whl", hash = "sha256:c211e8ec81522ce87b0b39f0cf0712c998d4305a030459a0e115a2b3dc71598f"},
{file = "SQLAlchemy-1.4.25-cp38-cp38-win_amd64.whl", hash = "sha256:9a1df8c93a0dd9cef0839917f0c6c49f46c75810cf8852be49884da4a7de3c59"},
{file = "SQLAlchemy-1.4.25-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:1b38db2417b9f7005d6ceba7ce2a526bf10e3f6f635c0f163e6ed6a42b5b62b2"},
{file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e37621b37c73b034997b5116678862f38ee70e5a054821c7b19d0e55df270dec"},
{file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:91cd87d1de0111eaca11ccc3d31af441c753fa2bc22df72e5009cfb0a1af5b03"},
{file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90fe429285b171bcc252e21515703bdc2a4721008d1f13aa5b7150336f8a8493"},
{file = "SQLAlchemy-1.4.25-cp39-cp39-win32.whl", hash = "sha256:6003771ea597346ab1e97f2f58405c6cacbf6a308af3d28a9201a643c0ac7bb3"},
{file = "SQLAlchemy-1.4.25-cp39-cp39-win_amd64.whl", hash = "sha256:9ebe49c3960aa2219292ea2e5df6acdc425fc828f2f3d50b4cfae1692bcb5f02"},
{file = "SQLAlchemy-1.4.25.tar.gz", hash = "sha256:1adf3d25e2e33afbcd48cfad8076f9378793be43e7fec3e4334306cac6bec138"},
]
typing-extensions = [
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
urllib3 = [
{file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
{file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
]
wechatpy = [
{file = "wechatpy-1.8.15-py2.py3-none-any.whl", hash = "sha256:0ac7bd23725cc6cbcfcc45cc4864c2afb9e9b630a2fa6763b0fbed9fe714d5f6"},
{file = "wechatpy-1.8.15.tar.gz", hash = "sha256:0724347ea9ede4f14b59acefd6e41c9baae25e822df204ece3eeba3d9e1dfd03"},
]
werkzeug = [
{file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"},
{file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"},
]
xmltodict = [
{file = "xmltodict-0.12.0-py2.py3-none-any.whl", hash = "sha256:8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051"},
{file = "xmltodict-0.12.0.tar.gz", hash = "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21"},
]
zipp = [
{file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
{file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
]
[tool.poetry]
name = "sukang24h"
version = "0.1.0"
description = ""
authors = ["冯佳佳 <478124129@qq.com>"]
[tool.poetry.dependencies]
python = "^3.7"
Flask = "^2.0.2"
Flask-Cors = "^3.0.10"
Flask-Log-Request-ID = "^0.10.1"
python-dotenv = "^0.19.0"
SQLAlchemy = "^1.4.25"
Flask-SQLAlchemy = "^2.5.1"
PyMySQL = "^1.0.2"
wechatpy = "^1.8.15"
flask-redis = "^0.4.0"
PyJWT = "^2.2.0"
pycryptodome = "^3.11.0"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: __init__.py.py
"""
# -*- coding: utf-8 -*-
import smtplib
from email.header import Header
from email.mime.text import MIMEText
from email.utils import formataddr
from config.commen_config import EMAIL_CONFIG
class EmailService:
def __init__(self):
self.from_addr = EMAIL_CONFIG['KEFU_EMAIL']
self.password = EMAIL_CONFIG['PASSWORD']
self.smtp_server = EMAIL_CONFIG['SMTP_SERVER']
def send_email(self, to_addr, rent, fp_dm, fp_hm, pdf_url):
content = '''
<html><body><b>尊敬的用户您好:</b> <br><p>感谢您使用灰晓兔!</p>
<p>灰晓兔已经为您开具订单{rent}的电子普通发票,发票数量共计1张,如下:</p>
<p>发票代码:{fp_dm},发票号码:{fp_hm},您可以点击“<a href="{pdf_url}">电子普通发票下载</a>”获取该发票文件;</p>
<p>电子普通发票是税务机关人口的有效收付款凭证,与纸质发票具有同等法律效力,可用于报销入账等。</p>
<p>如果您有任何疑问或建议,请联系18068402080。</p>
'''.format(rent=rent, fp_dm=fp_dm, fp_hm=fp_hm, pdf_url=pdf_url)
msg = MIMEText(content, 'html', 'utf-8')
lam_format_addr = lambda name, addr: formataddr((Header(name, 'utf-8').encode(), addr))
msg['From'] = lam_format_addr(EMAIL_CONFIG['KEFU_NAME'], self.from_addr)
msg['To'] = lam_format_addr(to_addr, to_addr)
msg['Subject'] = Header(EMAIL_CONFIG['SUBJECT'], 'utf-8').encode()
server = smtplib.SMTP_SSL(self.smtp_server, 465)
server.login(self.from_addr, self.password)
server.sendmail(self.from_addr, [to_addr], msg.as_string())
server.quit()
# -*- coding: utf-8 -*-
import datetime
import hashlib
import random
import time
from sqlalchemy.exc import SQLAlchemyError
from utils.Helper.Helper import get_format_date
from models.base_model import db
from models.feedback_models import Feedback, FeedbackImage
class FeedbackService():
@staticmethod
def getReplay(customer_id):
sql = '''
select feedbacks.id,feedbacks.remark, feedbacks.`status`, feedbacks.reply, feedbacks.created_at, feedback_images.url from feedbacks
right join feedback_images on feedback_images.feedback_id = feedbacks.id where feedbacks.customer_id = '{customer_id}'
'''.format(customer_id=customer_id)
info = db.session.execute(sql)
return info
@staticmethod
def create(customer_id, remark, telephone, pics):
feedback = FeedbackService.createFeedbackStub(customer_id, remark, telephone)
if feedback:
try:
db.session.add(feedback)
db.session.commit()
except SQLAlchemyError as e:
raise e
if len(pics) > 0:
for item in pics:
feedback_imgs = FeedbackService.createFeedbackImage(feedback.id, item)
if feedback_imgs:
try:
db.session.add(feedback_imgs)
db.session.commit()
except SQLAlchemyError as e:
raise e
@staticmethod
def createFeedbackStub(customer_id, remark, telephone):
feedback = Feedback()
feedback.customer_id = customer_id
feedback.remark = remark
feedback.telephone = telephone
feedback.status = 0
feedback.created_at = datetime.datetime.now()
feedback.updated_at = datetime.datetime.now()
return feedback
@staticmethod
def createFeedbackImage(feedback_id, pic):
feedbackImage = FeedbackImage()
feedbackImage.url = pic
feedbackImage.feedback_id = feedback_id
feedbackImage.created_at = datetime.datetime.now()
feedbackImage.updated_at = datetime.datetime.now()
return feedbackImage
@staticmethod
def guid():
timestamp = int(time.time())
ranstr = random.randint(9999, 9999999999)
return FeedbackService.MD5(str(timestamp) + str(ranstr)) + FeedbackService.MD5(str(ranstr))[0:8]
@staticmethod
def formReplayInfo(data):
da = []
for item in data:
cur_data = {}
cur_data['id'] = item.id
cur_data['created_at'] = get_format_date(item.created_at)
cur_data['remark'] = item.remark
cur_data['status'] = '未处理' if item.status == 0 else '已处理'
cur_data['reply'] = '暂无回复' if item.reply is None else item.reply
urls = []
urls.append(item.url)
cur_data['urls'] = urls
da.append(cur_data)
return da
@staticmethod
def MD5(info):
m = hashlib.md5()
m.update(info.encode("utf-8"))
return m.hexdigest()
# -*- coding: utf-8 -*-
import random
import string
from qcloudsms_py import SmsSingleSender, SmsMultiSender
from qcloudsms_py.httpclient import HTTPError
from config.commen_config import SMS_CONFIG
from utils.my_redis_cache import redis_client
class SMSService():
def __init__(self):
self.appid = SMS_CONFIG['app_id']
self.appKey = SMS_CONFIG['app_key']
try:
self.ssender = SmsSingleSender(self.appid, self.appKey)
self.msender = SmsMultiSender(self.appid, self.appKey)
except Exception as e:
print(e)
def create_code(self, length=4):
'''
生成验证码
:param length:
:return:
'''
verification = []
letters = string.digits
for i in range(length):
letter = letters[random.randint(0, 9)]
verification.append(letter)
return "".join(verification)
def make_code(self, phoneNumber):
'''
设置验证码300秒有效
:param phoneNumber:
:return:
'''
code = self.create_code(4)
redis_client.set('V_C' + str(phoneNumber), code, 300)
return code
def phoneSendCode(self, phoneNumber, tempId, sign='灰兔智能'):
'''
发送验证码
:param phoneNumber:
:param tempId:
:param sign:
:return:
'''
try:
result = self.ssender.send(0, 86, phoneNumber, tempId, sign=sign, extend="", ext='')
return result
except HTTPError as e:
print(e)
except Exception as e:
print(e)
return
def phoneSendCode(self, phoneNumber, tempId, sign='灰兔智能'):
'''
发送验证码
:param phoneNumber:
:param tempId:
:param sign:
:return:
'''
code = self.make_code(phoneNumber)
params = [code]
try:
result = self.ssender.send_with_param(86, phoneNumber, tempId, params, sign=sign, extend="", ext='')
return result
except HTTPError as e:
print(e)
except Exception as e:
print(e)
return
def phoneSendCodeWithContent(self, phoneNumber, tempId, Content, sign='灰兔智能'):
'''
发送验证码
:param phoneNumber:
:param tempId:
:param sign:
:return:
'''
try:
if isinstance(phoneNumber, list):
result = self.msender.send_with_param(86, phoneNumber, tempId, Content, sign=sign, extend="", ext='')
else:
result = self.ssender.send_with_param(86, phoneNumber, tempId, Content, sign=sign, extend="", ext='')
return result
except HTTPError as e:
print(e)
except Exception as e:
print(e)
return
def verificate(self, phoneNumber, code):
'''
判断验证码,-1验证码过期 -2验证码错误 0匹配
:param phoneNumber:
:param code:
:return:
'''
ver = redis_client.get('V_C' + str(phoneNumber))
if ver is None:
return -1
else:
if ver == str(code).encode('utf-8'):
return 0
else:
return -2
def phoneSendTips(self, user_name, spot_name, mac_no, num, from_phone, to_phone, sign='灰兔智能'):
params = [user_name, spot_name, mac_no, num, from_phone]
try:
result = self.ssender.send_with_param(86, to_phone, 766214, params, sign=sign, extend="", ext='')
return result
except HTTPError as e:
print(e)
except Exception as e:
print(e)
return
\ No newline at end of file
# -*- coding: utf-8 -*-
import hashlib
import json
import random
import string
import requests
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, SignatureExpired, BadSignature
from utils.jwt_util import generate_jwt, verify_jwt
from models.user_models import CustomerModel as User
class UserService():
@staticmethod
def geneSalt(length=16):
keylist = [random.choice((string.ascii_letters + string.digits)) for i in range(length)]
return ("".join(keylist))
@staticmethod
def generate_auth_token(id, expiration=604800):
"""
生成用户接口权限Token
:param id: 用户ID
:param expiration: 过期时间,默认用7*24*60*60
:return:
"""
result = generate_jwt(payload={"user_id": id}, expiry=expiration)
return result
@staticmethod
def checkAuthCode(token=None):
auth_info = token.split("#")
if len(auth_info) != 2:
return False
try:
user_info = User.query.filter_by(id=auth_info[1]).first()
except Exception:
return False
if user_info is None:
return False
if auth_info[0] != UserService.geneAuthCode(user_info):
return False
if user_info.status != 1:
return False
return user_info
@staticmethod
def verify_auth_token(token):
result = verify_jwt(token)
return result
if __name__ == '__main__':
print(UserService().generate_auth_token(21, 60000000))
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
class UtilService(object):
@staticmethod
def dict_to_xml(dict_data):
'''
dict to xml
:param dict_data:
:return:
'''
xml = ["<xml>"]
for k, v in dict_data.items():
xml.append("<{0}>{1}</{0}>".format(k, v))
xml.append("</xml>")
return "".join(xml)
@staticmethod
def xml_to_dict(xml_data):
'''
xml to dict
:param xml_data:
:return:
'''
xml_dict = {}
root = ET.fromstring(xml_data)
for child in root:
xml_dict[child.tag] = child.text
return xml_dict
# -*- coding: utf-8 -*-
import datetime
import hashlib, requests, uuid, json
import xml.etree.ElementTree as ET
from requests.exceptions import HTTPError
from wechatpy import WeChatClient
from wechatpy.client.api import WeChatWxa
from wechatpy.pay import WeChatPay, calculate_signature
import logging
from wechatpy.session.redisstorage import RedisStorage
from config.commen_config import WX_SESSION_KEY
from config.wechat_config import pay_config_dict, platform_config_dict
from utils.my_redis_cache import redis_client
logger = logging.getLogger(__name__)
class WeChatPayService(WeChatPay):
"""
pass
"""
def __init__(self, app_id, config_name):
self.app_id = app_id
self.config = pay_config_dict[config_name]
mch_id = self.config["mch_id"]
pay_key = self.config["pay_key"]
cert_path = self.config["cert_path"]
key_path = self.config["key_path"]
super(WeChatPayService, self).__init__(app_id, pay_key, mch_id, mch_cert=cert_path, mch_key=key_path)
def unifiedorder(self, pay_data):
"""
:param pay_data:
:return:
"""
notify_url = self.config["domain"] + self.config["callback_url"]
logger.debug(notify_url)
pay_data["nonce_str"] = self.get_nonce_str()
result = self.order.create(notify_url=notify_url, **pay_data)
prepay_id = result.get('prepay_id')
pay_sign_data = {
'appId': self.app_id,
'timeStamp': pay_data.get('timeStamp'),
'nonceStr': pay_data.get('nonce_str'),
'package': 'prepay_id={0}'.format(prepay_id),
'signType': 'MD5',
}
pay_sign = calculate_signature(pay_sign_data, self.config["pay_key"])
pay_sign_data.pop('appId')
pay_sign_data['paySign'] = pay_sign
pay_sign_data['prepay_id'] = prepay_id
pay_sign_data['out_trade_no'] = pay_data.get('out_trade_no')
# TODO 加上时间,金额,个数
pay_sign_data['rent_time'] = datetime.datetime.fromtimestamp(int(pay_data.get('timeStamp'))).strftime(
'%Y-%m-%d %H:%M:%S')
pay_sign_data['total_fee'] = pay_data["total_fee"]
pay_sign_data["number"] = pay_data["attach"]["number"]
return pay_sign_data
def do_refund(self, refund_data):
"""
:param refund_data:
:return:
"""
for i in range(3):
try:
result = self.refund.apply(**refund_data)
return_code = result['return_code']
result_code = result.get('result_code')
if return_code != 'SUCCESS' or result_code != 'SUCCESS':
continue
else:
if result.get("err_code", None):
continue
return result
except Exception as e:
logger.info(e)
continue
@staticmethod
def get_nonce_str():
'''
获取随机字符串
:return:
'''
return str(uuid.uuid4()).replace('-', '')
def create_sign(self, pay_data, pay_key):
'''
生成签名
:return:
'''
stringA = '&'.join(["{0}={1}".format(k, pay_data.get(k)) for k in sorted(pay_data)])
stringSignTemp = '{0}&key={1}'.format(stringA, pay_key)
logger.info(stringSignTemp)
sign = hashlib.md5(stringSignTemp.encode("utf-8")).hexdigest()
return sign.upper()
def verify_pay_callbak_sign(self, callback_data):
"""
验证签名方法
:param callback_data: 微信回调的xml解析后的数据
:return:
"""
if callback_data['result_code'] != 'SUCCESS':
return False
sign = callback_data['sign']
callback_data.pop('sign')
gene_sign = calculate_signature(callback_data, self.config["pay_key"])
if sign != gene_sign:
return False
return True
def query_refund_info(self, refund_data):
result = self.refund.query(**refund_data)
return result
class WxMPService(WeChatClient):
def __init__(self, config_name):
config = platform_config_dict[config_name]
app_id = config["app_id"]
app_secret = config["app_secret"]
session_interface = RedisStorage(
redis_client,
prefix="wx"
)
super(WxMPService, self).__init__(app_id, app_secret, session=session_interface)
def code_to_session(self, code):
res = self.wxa.code_to_session(code)
open_id = res["openid"]
session_key = res["session_key"]
try:
old_session_key = redis_client.get(WX_SESSION_KEY + open_id)
except:
old_session_key = None
if old_session_key:
res["session_key"] = str(old_session_key, encoding='utf-8')
try:
redis_client.set(WX_SESSION_KEY + open_id, session_key)
except Exception as e:
raise e
return res
if __name__ == '__main__':
target_wechat = WeChatPayService()
# -*- coding: utf-8 -*-
from utils.Helper.Helper import get_format_date
from models.guide_record_models import GuideRecordModel
from models.spot_models import Spot
class WeGuideService():
@staticmethod
def getMyRecord(customer_id, page, limit):
record_list = \
GuideRecordModel.query.filter(GuideRecordModel.customer_id == customer_id and GuideRecordModel.is_pay == 1).order_by(
GuideRecordModel.pay_time.desc()).all()[page - 1: limit]
return record_list
@staticmethod
def formatWeGuideRecordModel(weguide_info):
data = []
for item in weguide_info:
cur_data = {}
cur_data['address'] = Spot.query.filter_by(id=item.spot_id).first().spotname
cur_data['guide_no'] = item.guide_no
cur_data['total'] = item.total
cur_data['real_total'] = item.real_total
cur_data['pay_time'] = get_format_date(item.pay_time)
data.append(cur_data)
return data
# -*- coding: utf-8 -*-
import datetime
import time
import requests
import json
import wechatpy
from wechatpy import WeChatClient
from wechatpy.messages import ImageMessage
import logging
from wechatpy.session.redisstorage import RedisStorage
from config.commen_config import WX_ACCESS_TOKEN_EXPIRE, WX_ACCESS_TOKEN
from config.wechat_config import oa_id_config, platform_appid_config_list, oa_id_mini_program_dict, SXXJ_OA_CONFIG, \
SXXJ_OA_TO_XT_MINI_PROGRAM_MAC_LIST, XT_CONFIG
from models.base_model import db
from models.machine_models import Machine
from models.user_models import CustomerModel
from utils.my_redis_cache import redis_client
logger = logging.getLogger(__name__)
class WXOAService(WeChatClient):
"""
微信公众号服务
"""
def __init__(self, config):
session_interface = RedisStorage(
redis_client,
prefix="wx"
)
super(WXOAService, self).__init__(config["app_id"], config["app_secret"], session=session_interface)
self.platform = platform_appid_config_list.index(self.appid)
self.oa_id = config["oa_id"]
@staticmethod
def get_wxCode_token(oa_id):
"""
:param oa_id:
:return:
"""
try:
access_token_expire = redis_client.get(WX_ACCESS_TOKEN_EXPIRE + "" + oa_id)
access_token = redis_client.get(WX_ACCESS_TOKEN + "" + oa_id)
if access_token_expire and access_token:
access_token_expire = int(float(str(access_token_expire, encoding="utf-8")))
# access_token有效时间2小时(7200秒),提前5分钟进行过期处理
tmp_time = access_token_expire - int(time.time())
if tmp_time < 300:
pass
else:
return str(access_token, encoding="utf-8")
oa_config = WXOAService.get_wx_oa_config_by_oa_id(oa_id)
if oa_config is None:
return None
params = {"grant_type": "client_credential",
"appid": oa_config["app_id"],
"secret": oa_config["app_secret"]
}
url = 'https://api.weixin.qq.com/cgi-bin/token'
req = requests.get(url, params=params)
res = json.loads(req.text)
access_token = res["access_token"]
access_token_expire = res["expires_in"]
redis_client.set(WX_ACCESS_TOKEN + "" + oa_id, access_token)
redis_client.set(WX_ACCESS_TOKEN_EXPIRE + "" + oa_id, int(time.time() + int(access_token_expire)))
return access_token
except Exception as e:
print(e)
return None
@staticmethod
def get_wx_oa_config_by_oa_id(oa_id):
"""
:param oa_id:
:return:
"""
return oa_id_config.get(oa_id, None)
def event_handler(self, message):
if message.event.startswith("subscribe"):
to_user = message.target
from_user = message.source
create_time = message.create_time
customer_info = CustomerModel.query.filter_by(openid=from_user, status=1).first()
if not customer_info:
# 用户不存在,绑定用户
model_user = CustomerModel()
model_user.openid = from_user
model_user.platform = self.platform
model_user.status = 1
db.session.add(model_user)
db.session.commit()
if message.event == "subscribe_scan":
mac_no = message.scene_id
ticket = message.ticket
machine = Machine.query.filter_by(mac_no=mac_no).first()
if machine:
self.send_rent_template(to_user, from_user, mac_no, create_time, machine)
return_data = None
return return_data
return_data = """
<xml>
<ToUserName><![CDATA[{toUser}]]></ToUserName>
<FromUserName><![CDATA[{fromUser}]]></FromUserName>
<CreateTime>{createTime}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{content}]]></Content>
</xml>
""".format(toUser=from_user, fromUser=to_user, createTime=int(time.time()),
content="二维码信息不正确,无法正常使用,可联系客服电话400-110-7981进行咨询")
return return_data
elif message.event == 'unsubscribe':
return None
elif message.event == "scan":
to_user = message.target
from_user = message.source
create_time = message.create_time
mac_no = message.scene_id
ticket = message.ticket
if mac_no and ticket:
machine = Machine.query.filter_by(mac_no=mac_no).first()
if machine:
self.send_rent_template(to_user, from_user, mac_no, create_time, machine)
return_data = None
return return_data
return_data = """
<xml>
<ToUserName><![CDATA[{toUser}]]></ToUserName>
<FromUserName><![CDATA[{fromUser}]]></FromUserName>
<CreateTime>{createTime}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{content}]]></Content>
</xml>
""".format(toUser=from_user, fromUser=to_user, createTime=int(time.time()),
content="二维码信息不正确,无法正常使用,可联系客服电话400-110-7981进行咨询")
return return_data
elif message.event == "templatesendjobfinish":
return ""
else:
return None
@classmethod
def send_welcome_template(cls, oa_id, open_id, mac_no, createtime, machine):
token = cls.get_wxCode_token(oa_id)
url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={}".format(token)
post_data = {
"touser": open_id,
"miniprogram": {
"appid": "wxa66199e581b19d51",
"pagepath": "pages/Index/Index?machine_no={}".format(mac_no)
},
"data": {
"first": {
"value": "欢迎扫码使用灰兔智能导游讲解器",
},
"keyword1": {
"value": "{}".format(
datetime.datetime.fromtimestamp(int(createtime)).strftime("%Y-%m-%d %H:%M:%S")),
},
"keyword2": {
"value": machine.short_address if machine.short_address else "",
},
"remark": {
"value": "点击详情进入小程序租借使用",
}
}
}
if oa_id == "gh_598e6bc39f09":
post_data["template_id"] = "4pNc9IknD3ilNWMt3AJHxYOloFQzFhtwsvcrufZUtEU"
elif oa_id == "gh_c3bea8443020":
post_data["template_id"] = "ysPWD-ZobNas37byJWH-NvOjnJ-p5cAIuVGYGEvTPHw"
else:
return None
post_data = json.dumps(post_data, ensure_ascii=False)
result = requests.post(url=url, data=post_data.encode('utf-8'), verify=None)
logger.info(result.text)
@classmethod
def send_scan_template(cls, oa_id, open_id, mac_no, createtime, machine):
"""
:param oa_id:
:param open_id:
:param mac_no:
:param createtime:
:param machine:
:return:
"""
token = cls.get_wxCode_token(oa_id)
url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={}".format(token)
post_data = {
"touser": open_id,
"miniprogram": {
"appid": "wxa66199e581b19d51",
"pagepath": "pages/Index/Index?machine_no={}".format(mac_no)
},
"data": {
"first": {
"value": "欢迎扫码使用灰兔智能导游讲解器",
},
"keyword1": {
"value": "{}".format(
datetime.datetime.fromtimestamp(int(createtime)).strftime("%Y-%m-%d %H:%M:%S")),
},
"keyword2": {
"value": machine.short_address if machine.short_address else "",
},
"remark": {
"value": "点击详情进入小程序租借使用",
}
}
}
if oa_id == "gh_598e6bc39f09":
post_data["template_id"] = "4pNc9IknD3ilNWMt3AJHxYOloFQzFhtwsvcrufZUtEU"
elif oa_id == "gh_c3bea8443020":
post_data["template_id"] = "ysPWD-ZobNas37byJWH-NvOjnJ-p5cAIuVGYGEvTPHw"
else:
return None
post_data = json.dumps(post_data, ensure_ascii=False)
result = requests.post(url=url, data=post_data.encode('utf-8'), verify=None)
logger.info(result.text)
def get_media_list(self, media_type):
result = self.material.batchget(media_type=media_type)
return result
def push_image_message(self, msg):
from wechatpy.replies import ImageReply
# ImageMessage
result = self.material.batchget()
reply = ImageReply(message=msg)
reply.media_id = 'image media id'
# 转换成 XML
xml = reply.render()
def send_rent_message(self, to_user, from_user, mac_no, create_time, machine):
content = """您终于来了,晓兔在此恭候多时,感谢关注晓兔
欢迎使用灰兔智能讲解器,游玩怎能少了讲解陪伴,来听我聊聊景点故事吧。
你可以通过我:
1.联系客服人员
2.高端定制游:会议、招待、住宿、婚礼、园林包场、皆可定制
————————————————————————
想了解详情小伙伴可扫码联系客服进行咨询。
<a href="http://www.qq.com" data-miniprogram-appid="wxa66199e581b19d51" data-miniprogram-path="pages/Index/Index?machine_no={mac_id}">点击跳小程序</a>
""".format(mac_id=mac_no)
result = self.message.send_text(from_user, content)
logger.info(result)
def send_rent_template(self, to_user, from_user, mac_no, create_time, machine):
"""
:param to_user:
:param from_user:
:param mac_no:
:param create_time:
:param machine:
:return:
"""
color = "#CD0000"
mini_program_data = oa_id_mini_program_dict[self.oa_id]
if self.oa_id == SXXJ_OA_CONFIG["oa_id"] and mac_no in SXXJ_OA_TO_XT_MINI_PROGRAM_MAC_LIST:
mini_program_data["app_id"] = XT_CONFIG["app_id"]
miniprogram = {
"appid": mini_program_data["app_id"],
"pagepath": "pages/Index/Index?machine_no={}".format(mac_no)
}
data = {
"first": {
"value": "欢迎扫码使用智能导游讲解器"
},
"keyword1": {
"value": "{}".format(
create_time.strftime("%Y-%m-%d %H:%M:%S")),
},
"keyword2": {
"value": machine.short_address if machine.short_address else "",
},
"remark": {
"value": "点击详情进入小程序租借使用",
"color": color
}
}
if self.oa_id == "gh_598e6bc39f09":
template_id = "4pNc9IknD3ilNWMt3AJHxYOloFQzFhtwsvcrufZUtEU"
elif self.oa_id == "gh_c3bea8443020":
template_id = "ysPWD-ZobNas37byJWH-NvOjnJ-p5cAIuVGYGEvTPHw"
elif self.oa_id == "gh_b2a7d67e5686":
template_id = "1gCjOrtWM66gMkyKyeuS_kdempehLye0DGutxx_GDyM" # 随心晓见
else:
template_id = ""
result = self.message.send_template(from_user, template_id, data, mini_program=miniprogram)
logger.info(result)
def send_rent_mini_program(self, to_user, from_user, mac_no, create_time, machine):
mini_program_data = oa_id_mini_program_dict[self.oa_id]
if self.oa_id == SXXJ_OA_CONFIG["oa_id"] and mac_no in SXXJ_OA_TO_XT_MINI_PROGRAM_MAC_LIST:
mini_program_data["app_id"] = XT_CONFIG["app_id"]
mini_program = {
"title": "欢迎使用灰兔智能",
"appid": mini_program_data["app_id"],
"pagepath": "pages/Index/Index?machine_no={}".format(mac_no),
"thumb_media_id": mini_program_data["thumb_media_id"],
}
result = self.message.send_mini_program_page(from_user, mini_program)
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
@author:Aeolus
"""
import os
import logging
from myapps.sukang24h import create_app
logger = logging.getLogger(__name__)
app = create_app(os.getenv('RUN_ENV', 'prod'))
logger.info("run server")
if __name__ == '__main__':
app.run('127.0.0.1', 8893, debug=True)
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: error_code.py
"""
# BASE_RESPONSE = {
# "error_code": "0",
# "error_message": "Success"
# }
# 用户相关 10开头
TOKEN_NOT_VALID_ERROR = {
"error_code": "1001",
"error_message": "无效的token"
}
TOKEN_NOT_PROVIDED_ERROR = {
"error_code": "1002",
"error_message": "token未提供"
}
TOKEN_EXPIRE_ERROR = {
"error_code": "1003",
"error_message": "token超时"
}
PHONE_NOT_BINDING_ERROR = {
"error_code": "1004",
"error_message": "未绑定手机号"
}
PHONE_NOT_NULL_ERROR = {
"error_code": "1005",
"error_message": "手机号为空"
}
PHONE_NOT_VALID_ERROR = {
"error_code": "1006",
"error_message": "无效的手机号"
}
USER_ALREADY_REGISTER_ERROR = {
"error_code": "1007",
"error_message": "用户已注册"
}
VERIFICATION_CODE_NULL_ERROR = {
"error_code": "1008",
"error_message": "验证码为空"
}
VERIFICATION_CODE_INVALID_ERROR = {
"error_code": "1009",
"error_message": "验证码已失效"
}
VERIFICATION_CODE_ERROR = {
"error_code": "1010",
"error_message": "验证码错误"
}
PASSWORD_ERROR = {
"error_code": "1011",
"error_message": "账号或密码错误"
}
## 微信登陆相关
WX_LOGIN_DATA_ERROR = {
"error_code": 3001,
"error_message": "微信登录数据错误"
}
WX_LOGIN_CODE_ERROR = {
"error_code": 3002,
"error_message": "微信登录code值错误"
}
WX_OPENID_NOT_GET_ERROR = {
"error_code": 3003,
"error_message": "微信OpenId获取失败,请刷新重试"
}
### 微信支付相关
WE_MINIAPP_PAY_FAIL = {
"error_code": 3101,
"error_message": "小程序下单失败"
}
### 消息推送相关
WXBizMsgCrypt_OK = {
"error_code": 0,
"error_message": "WXBizMsgCrypt_OK"
}
WXBizMsgCrypt_ValidateSignature_Error = {
"error_code": 4001,
"error_message": "验证签名错误"
}
WXBizMsgCrypt_ParseXml_Error = {
"error_code": 4002,
"error_message": "解析xml错误"
}
WXBizMsgCrypt_ComputeSignature_Error = {
"error_code": 4003,
"error_message": "计算签名错误"
}
WXBizMsgCrypt_IllegalAesKey = {
"error_code": 4004,
"error_message": "Aes key非法错误"
}
WXBizMsgCrypt_ValidateAppid_Error = {
"error_code": 4005,
"error_message": "appid错误"
}
WXBizMsgCrypt_EncryptAES_Error = {
"error_code": 4006,
"error_message": "aes加密错误"
}
WXBizMsgCrypt_DecryptAES_Error = {
"error_code": 4007,
"error_message": "aes解密错误"
}
WXBizMsgCrypt_IllegalBuffer = {
"error_code": 4008,
"error_message": "illegal buffer"
}
WXBizMsgCrypt_EncodeBase64_Error = {
"error_code": 4009,
"error_message": "base64加密错误"
}
WXBizMsgCrypt_DecodeBase64_Error = {
"error_code": 4010,
"error_message": "base64解密错误"
}
WXBizMsgCrypt_GenReturnXml_Error = {
"error_code": 4011,
"error_message": "gen return xml error"
}
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@time: 2021/03/30
@file: jwt_util.py
@function:
@modify:
"""
import jwt
from flask import current_app
def generate_jwt(payload, expiry, secret=None):
"""
生成jwt
:param payload: dict 载荷
:param expiry: datetime 有效期
:param secret: 密钥
:return: jwt
"""
_payload = {'exp': expiry}
_payload.update(payload)
if not secret:
secret = current_app.config['SECRET_KEY']
token = jwt.encode(_payload, secret, algorithm='HS256')
return token
def verify_jwt(token, secret=None):
"""
检验jwt
:param token: jwt
:param secret: 密钥
:return: dict: payload
"""
if not secret:
secret = current_app.config['SECRET_KEY']
try:
payload = jwt.decode(token, secret, algorithms=['HS256'])
except jwt.PyJWTError:
payload = None
return payload
if __name__ == '__main__':
import time
from config.env_path_config import env_path
from dotenv import load_dotenv
load_dotenv(dotenv_path=env_path, verbose=True, override=True)
import os
SECRET_KEY = os.getenv('SECRET_KEY')
token = generate_jwt({"user_id": 1}, time.time() + 6000, SECRET_KEY)
print(token)
# for i in range(10):
# result = verify_jwt(token, 'secret')
# print(result)
# print(time.time())
# time.sleep(1)
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@time: 2021/03/26
@file: middlewares.py
@function:
@modify:
"""
import logging
from flask import g, request, url_for, current_app, make_response, jsonify
from config.wechat_config import platform_config_list
from models.models import WxUser
from utils.error_code import TOKEN_NOT_VALID_ERROR
from utils.my_response import BaseResponse
from utils.jwt_util import verify_jwt
logger = logging.getLogger(__name__)
def log_enter_interface():
"""
日志打印进入接口
:return:
"""
logger.info("######################### 进入 {} 接口 ################################ ".format(request.path))
def log_out_interface(environ):
"""
日志打印退出接口
:return:
"""
logger.info("######################### 退出 {} 接口 ################################\n".format(request.path))
return environ
def close_db_session(environ):
from models.base_model import db
db.session.close()
return environ
"""用户认证机制==>每次请求前获取并校验token"""
"@myapps.before_request 不使@调用装饰器 在 init文件直接装饰"
def jwt_authentication():
"""
1.获取请求头Authorization中的token
2.判断是否以 Bearer开头
3.使用jwt模块进行校验
4.判断校验结果,成功就提取token中的载荷信息,赋值给g对象保存
"""
if current_app.name == "sukang24h":
NO_AUTH_CHECK_URL = [url_for('wx_auth.my_test'),
url_for('wx_auth.mini_login'),
# url_for('rent.wx_pay_callback'),
]
else:
NO_AUTH_CHECK_URL = []
if request.path not in NO_AUTH_CHECK_URL:
token = request.headers.get('Authorization')
# "校验token"
payload = verify_jwt(token)
# "判断token的校验结果"
if payload:
# "获取载荷中的信息赋值给g对象"
user_id = payload.get('user_id')
if not user_id:
return BaseResponse(**TOKEN_NOT_VALID_ERROR)
g.user = WxUser.query.filter_by(id=user_id).first()
else:
return BaseResponse(**TOKEN_NOT_VALID_ERROR)
def get_platform():
"""
:return:
"""
g.platform = request.headers.get('platform', "sukang24h")
def all_options_pass():
"""
:return:
"""
if request.method == "OPTIONS":
headers = {'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers':
'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , platform',
}
return make_response((jsonify({'error_code': 0}), 200, headers))
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: my_redis_cache.py
"""
from flask_redis import FlaskRedis
redis_client = FlaskRedis(config_prefix='TENCENT_REDIS')
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@time: 2021/04/27
@file: my_response.py
@function:
@modify:
"""
from flask import Response
from flask.json import dumps
class BaseResponse(Response):
def __init__(self, data=None, error_code=0, error_message='Success', *args, **kwargs):
if data is not None:
result = dumps(dict(data=data, error_code=error_code, error_message=error_message, *args, **kwargs))
else:
result = dumps(dict(error_code=error_code, error_message=error_message, *args, **kwargs))
Response.__init__(self, result, mimetype='application/json')
import logging
from flask_log_request_id import RequestIDLogFilter
def set_logger():
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(
logging.Formatter(
"%(asctime)s - %(filename)s - %(funcName)s -[line:%(lineno)d] - %(levelname)s - request_id=%(request_id)s: %(message)s",
"%Y-%m-%d %H:%M:%S"))
console_handler.addFilter(RequestIDLogFilter())
logging.getLogger().setLevel(logging.DEBUG)
logging.getLogger().addHandler(console_handler)
import base64
import json
from Crypto.Cipher import AES
class WXBizDataCrypt:
def __init__(self, appId, sessionKey):
self.appId = appId
self.sessionKey = sessionKey
def decrypt(self, encryptedData, iv):
sessionKey = base64.decodebytes(bytes(self.sessionKey, encoding='utf8'))
encryptedData = base64.decodebytes(bytes(encryptedData, encoding='utf8'))
iv = base64.decodebytes(bytes(iv, encoding='utf8'))
cipher = AES.new(sessionKey, AES.MODE_CBC, iv)
des_str = cipher.decrypt(encryptedData)
print("==================================")
print(des_str)
des_str = self._unpad(des_str)
print(des_str)
des_str = str(des_str,encoding='utf-8')
decrypted = json.loads(des_str)
if decrypted['watermark']['appid'] != self.appId:
raise Exception('Invalid Buffer')
return decrypted
def _unpad(self, s):
return s[:-ord(s[len(s) - 1:])]
if __name__ == '__main__':
appId = 'wx3185fb4a3633beb0'
sessionKey='S7CMDfC6jXJKSaWKanG8oQ=='
encryptedData='E7LZhvK7mOcaYsv9xcAfsBN9eSbzFh9FyMtFJ0zsFB0M62zRJ0cosZWksUujUR5WYUmNoIfIJnTIF8gRskxxbFU3fm5X7z4ChZecMSaFM65aEK1suRUD1U0ubB7mOwBBlY4ftdPT5kRwWgXKVkM4VAkYGN8A4fjWE93yGtjzxXs9dypQkCLSNWs6Kw5USEzjhtDZnptVy+lHF5fTXRuzoCstW2Cto4YI3G9hmnS64QuWjRteSqIgh8GN1zEPN0dROJjaWBjqraBCt/BfMsk4HBeL4PA75K8WdqVgKGfQ7/rnmPFOsNXWfajx9jl7XcrfoPaaPL1DmIJ1BlQne2GuLFtzZ3O4/8cdVQ9Lb0N/3kFAzjgzNFNLSYj2VNctmWyLdWi8hH90yslvrODIhMzIsuux2GIAfp0rQd/iVIVvtd7PXBOCe5iZ7aaqD0b0mLF4CmsuBpl8Eh20ZHkYw2SqO0x9uFrS/gy1vwtkmsTpcDw='
iv = 'DQcmcXyQkU+VKqb2mKmasQ=='
pc = WXBizDataCrypt(appId, sessionKey)
pc.decrypt(encryptedData, iv)
#
# -*- coding: utf-8 -*-
import base64
import hashlib
import random
import socket
import string
import struct
import time
from Crypto.Cipher import AES
import xml.etree.cElementTree as ET
from utils.error_code.wx_error import WXBizMsgCrypt_OK, WXBizMsgCrypt_ParseXml_Error, WXBizMsgCrypt_EncryptAES_Error, \
WXBizMsgCrypt_DecryptAES_Error, WXBizMsgCrypt_IllegalBuffer, WXBizMsgCrypt_ValidateAppid_Error, \
WXBizMsgCrypt_ValidateSignature_Error
class FormatException(Exception):
pass
def throw_exception(message, exception_class=FormatException):
raise exception_class(message)
class SHA1:
"""计算公众平台的消息签名接口"""
def getSHA1(self, token, timestamp, nonce, encrypt):
""" 用SHA1算法生成安全签名
@param token: 票据
@param timestamp: 时间戳
@param encrypt: 密文
@param nonce: 随机字符串
@return: 安全签名
"""
try:
sortlist = [token, timestamp, nonce, encrypt]
sortlist.sort()
sha = hashlib.sha1()
sha.update(("".join(sortlist)).encode('utf-8'))
return 0, sha.hexdigest()
except Exception as e:
print(e)
return -40003, None
class XMLParse:
"""提供提取消息格式中的密文及生成回复消息格式的接口"""
# xml消息模板
AES_TEXT_RESPONSE_TEMPLATE = """<xml>
<Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
<MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature>
<TimeStamp>%(timestamp)s</TimeStamp>
<Nonce><![CDATA[%(nonce)s]]></Nonce>
</xml>"""
def extract(self, xmltext):
""" 提取出xml数据包中的加密消息
@param xmltext: 待提取的xml字符串
@return: 提取出的加密消息字符串
"""
try:
xml_tree = ET.fromstring(xmltext)
encrypt = xml_tree.find("Encrypt")
touser_name = xml_tree.find("ToUserName")
return WXBizMsgCrypt_OK['error_code'], encrypt.text, touser_name.text
except Exception as e:
print(e)
return WXBizMsgCrypt_ParseXml_Error['error_code'], None, None
def generate(self, encrypt, signature, timestamp, nonce):
""" 生成xml消息
@param encrypt: 加密后的消息密文
@param signature: 安全签名
@param timestamp: 时间戳
@param nonce: 随机字符串
@return: 生成的xml字符串
"""
resp_dict = {
'msg_encrypt': encrypt,
'msg_signaturet': signature,
'timestamp': timestamp,
'nonce': nonce,
}
resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict
return resp_xml
class PKCS7Encoder():
"""提供基于PKCS7算法的加解密接口"""
block_size = 32
def encode(self, text):
""" 对需要加密的明文进行填充补位
@param text: 需要进行填充补位操作的明文
@return: 补齐明文字符串
"""
text_length = len(text)
# 计算需要填充的位数
amount_to_pad = self.block_size - (text_length % self.block_size)
if amount_to_pad == 0:
amount_to_pad = self.block_size
# 获得补位所用的字符
pad = chr(amount_to_pad)
return text + pad * amount_to_pad
def decode(self, decrypted):
""" 删除解密后明文的补位字符
@param decrypted: 解密后的明文
@return: 删除补位字符后的明文
"""
pad = ord(decrypted[-1])
if pad < 1 or pad > 32:
pad = 0
return decrypted[:-pad]
class Prpcrypt(object):
"""提供接收和推送给公众平台消息的加解密接口"""
def __init__(self, key):
# self.key = base64.b64decode(key+"=")
self.key = key
# 设置加解密模式为AES的CBC模式
self.mode = AES.MODE_CBC
def encrypt(self, text, appid):
""" 对明文进行加密
@param text: 需要加密的明文
@return: 加密得到的字符串
"""
# 16位随机字符串添加到明文开头
text = self.get_random_str() + str(struct.pack("I", socket.htonl(len(text)))) + text + appid
# 使用自定义的填充方式对明文进行补位填充
pkcs7 = PKCS7Encoder()
text = pkcs7.encode(text)
# 加密
cryptor = AES.new(self.key, self.mode, self.key[:16])
try:
ciphertext = cryptor.encrypt(text)
# 使用BASE64对加密后的字符串进行编码
return WXBizMsgCrypt_OK['error_code'], base64.b64encode(ciphertext)
except Exception as e:
print(e)
return WXBizMsgCrypt_EncryptAES_Error['error_code'], None
def decrypt(self, text, appid):
""" 对解密后的明文进行补位删除
@param text: 密文
@return: 删除填充补位后的明文
"""
try:
cryptor = AES.new(self.key, self.mode, self.key[:16])
# 使用BASE64对密文进行解码,然后AES-CBC解密
plain_text = cryptor.decrypt(base64.b64decode(text))
except Exception as e:
print(e)
return WXBizMsgCrypt_DecryptAES_Error['error_code'], None
try:
pad = ord(plain_text[len(plain_text) - 1:])
# 去掉补位字符串
# pkcs7 = PKCS7Encoder()
# plain_text = pkcs7.encode(plain_text)
# 去除16位随机字符串
content = plain_text[16:-pad]
xml_len = socket.ntohl(struct.unpack("I", content[: 4])[0])
xml_content = str(content[4: xml_len + 4], encoding='utf-8')
from_appid = str(content[xml_len + 4:], encoding='utf-8')
except Exception as e:
print(e)
return WXBizMsgCrypt_IllegalBuffer['error_code'], None
if from_appid != appid:
return WXBizMsgCrypt_ValidateAppid_Error['error_code'], None
return WXBizMsgCrypt_OK['error_code'], xml_content
def get_random_str(self):
""" 随机生成16位字符串
@return: 16位字符串
"""
rule = string.ascii_letters + string.digits
str = random.sample(rule, 16)
return "".join(str)
class WXBizMsgCrypt(object):
# 构造函数
# @param sToken: 公众平台上,开发者设置的Token
# @param sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey
# @param sAppId: 企业号的AppId
def __init__(self, sToken, sEncodingAESKey, sAppId):
try:
self.key = base64.b64decode(sEncodingAESKey + "=")
assert len(self.key) == 32
except:
throw_exception("[error]: EncodingAESKey unvalid !", FormatException)
self.token = sToken
self.appid = sAppId
def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
# 将公众号回复用户的消息加密打包
# @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
# @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间
# @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
# sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
# return:成功0,sEncryptMsg,失败返回对应的错误码None
pc = Prpcrypt(self.key)
ret, encrypt = pc.encrypt(sReplyMsg, self.appid)
if ret != 0:
return ret, None
if timestamp is None:
timestamp = str(int(time.time()))
# 生成安全签名
sha1 = SHA1()
ret, signature = sha1.getSHA1(self.token, timestamp, sNonce, encrypt)
if ret != 0:
return ret, None
xmlParse = XMLParse()
return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)
def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):
# 检验消息的真实性,并且获取解密后的明文
# @param sMsgSignature: 签名串,对应URL参数的msg_signature
# @param sTimeStamp: 时间戳,对应URL参数的timestamp
# @param sNonce: 随机串,对应URL参数的nonce
# @param sPostData: 密文,对应POST请求的数据
# xml_content: 解密后的原文,当return返回0时有效
# @return: 成功0,失败返回对应的错误码
# 验证安全签名
xmlParse = XMLParse()
encrypt = sPostData["Encrypt"]
touser_name = sPostData["ToUserName"]
sha1 = SHA1()
ret, signature = sha1.getSHA1(self.token, sTimeStamp, sNonce, encrypt)
if ret != 0:
return ret, None
if not signature == sMsgSignature:
return WXBizMsgCrypt_ValidateSignature_Error['error_code'], None
pc = Prpcrypt(self.key)
ret, xml_content = pc.decrypt(encrypt, self.appid)
return ret, xml_content
#!usr/bin/.env python
# -*- coding:utf-8 _*-
"""
@version:
author:Aeolus
@file: __init__.py.py
"""
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment