class DepositNotify(Resource): method_decorators = [limiter.limit("1/second")] @ns.expect(OrderTransfer) def post(self): """ best pay 交易通知 :return: """ form, error = BestPayNotifyForm().request_validate() if error: return error.as_response() # 判断订单号是否存在 不存在 或状态不为Init 则返回无效的订单号 order_id = form.tx_id.data order = DepositTransactionCtl.get_order_by_order_id(order_id=order_id) if not order or order.state.name != OrderStateEnum.INIT.name: return InvalidOrderIdError().as_response() # 收款银行卡号 # 发起人姓名 # 充值金额 # 备注 # 充值时间 # 状态 if not OrderTransferLog.insert_transfer_log( order_id=order_id, amount='{:.2f}'.format(float(form.amount.data)), in_account=form.card_number.data, out_name=form.user_name.data): return InvalidOrderIdError(message="新增转账信息有误").as_response() return ResponseSuccess().as_response()
def get_cashier_decorators(limit_cond="1/second", auth=True): decs = [] if limit_cond: decs.append(limiter.limit(limit_cond)) if auth: decs.append(cashier_auth.login_required) return decs
class AuthUsername(Resource): method_decorators = [ limiter.limit("1/second"), ] @ns.expect(MobileNumber) @ns.marshal_with(ResponseSuccess.gen_doc(api)) def post(self): """ 检查手机号是否已经注册 """ form, error = MobileRegisterCheckForm().request_validate() if error: return error.as_response() if not MerchantFeeConfig.query_latest_one(query_fields=dict( merchant=form.merchant.data, payment_way=PayTypeEnum.DEPOSIT, )): return MerchantConfigDepositError().as_response() if not MerchantFeeConfig.query_latest_one(query_fields=dict( merchant=form.merchant.data, payment_way=PayTypeEnum.WITHDRAW, )): return MerchantConfigWithdrawError().as_response() return ResponseSuccess().as_response()
class SMSCodeAuthentication(Resource): method_decorators = [limiter.limit("1/second"), ] # 期待客户端请求的数据模型,使用expect来装饰 @ns.expect(MobileAuthCode) # 给客户端返回的的响应数据模型,使用marshal_with来装饰 @ns.marshal_with(ResponseSuccess.gen_doc(api)) def post(self): """ 验证短信动态验证码 """ # 验证表单手机号及验证码格式验证 form, error = AuthCodeForm.request_validate() if error: return error.as_response() # 判断验证码是否过期 if AuthCodeGenerator(form.number.data).is_expired(form.auth_code.data): return AuthCodeExpiredError().as_response() # 判断验证码是否正确 if not AuthCodeGenerator(form.number.data).verify_code(form.auth_code.data): return AuthCodeError().as_response() return ResponseSuccess().as_response()
class ForgetPasswordVerifyAuth(Resource): method_decorators = [ limiter.limit("1/second"), ] # 请求数据格式 @ns.expect(MobileAuthCode) # 相应数据格式 @ns.marshal_with(ResponseSuccess.gen_doc(api)) def post(self): """ 忘记密码 --验证验证码 :return: """ # 用户手机号格式验证 form, error = AuthCodeTrueForm().request_validate() if error: return error.as_response() # 判断验证码是否过期 if AuthCodeGenerator(form.number.data).is_expired(form.auth_code.data): return AuthCodeExpiredError().as_response() # 判断验证码是否正确 if not AuthCodeGenerator(form.number.data).verify_code( form.auth_code.data): return AuthCodeError().as_response() return ResponseSuccess().as_response()
class ClientLogin(Resource): method_decorators = [check_ip_in_white_list(ADMIN_IP_WHITE_LIST), limiter.limit("1/second")] @ns.expect(AdminAccountLogin) @ns.marshal_with(AdminLoginResponse.gen_doc(api)) def post(self): """ 后台用户登录 :return: """ form, error = AuthLoginForm.request_validate() if error: return error.as_response() user = AdminUser.query_user(account=form.account.data) if not user: return LoginAccountError().as_response() # 验证用户名密码是否正确 if not AdminUser.verify_login(account=form.account.data, password=form.password.data): return LoginPasswordError().as_response() # 验证成功后,调用login_user,会在session中记录已经登录 user = AdminUser.query_user(account=form.account.data) # 记录登录状态 token = AdminLoginToken.generate_token(user.uid) # current_app.logger.debug('login ok, path: %s', request.path) return AdminLoginResponse(bs_data=dict(token=token)).as_response()
class RateLimitCheck(Resource): method_decorators = [limiter.limit("1/second")] def get(self): """ 限速测试 :return: """ return 'ok'
def get_admin_decorators(ip_check=True, limit_cond="1/second", auth=True): decs = [] if ip_check: decs.append(check_ip_in_white_list(ADMIN_IP_WHITE_LIST)) if limit_cond: decs.append(limiter.limit(limit_cond)) if auth: decs.append(admin_auth.login_required) return decs
class UserFlagResource(Resource): method_decorators = [ check_ip_in_white_list(ADMIN_IP_WHITE_LIST), limiter.limit("1/second") ] def get(self): """ 给用户绑定信息 :return: """ if not request.args: return ResponseSuccess( message="参数规则:?merchant=test&account=861891111&flag=VIP" ).as_response() try: merchant = MerchantEnum.from_name(request.args['merchant']) except: return ResponseSuccess(message="请输入正确的商户名称,有效的商户名称包括:%s" % MerchantEnum.get_names()).as_response() try: account = request.args['account'] account = '+' + account.strip('+').strip() if not PhoneNumberParser.is_valid_number(account): raise except: return ResponseSuccess( message="请输入正确的用户手机号码,必须有完整区号,不填+号,如:8613812349999" ).as_response() try: flag = AccountFlagEnum.from_name(request.args['flag']) except: return ResponseSuccess( message="标签错误,有效的标签包括: %s" % AccountFlagEnum.get_name_list()).as_response() user = User.query_user(merchant, account=account) if not user: return ResponseSuccess(message="手机号码未注册").as_response() User.update_user_flag(merchant, flag, account=account) user = User.query_user(merchant, account=account) bs_data = dict( account=account, uid=user.uid, flag=user.flag.desc, is_auth=user.is_official_auth, cache_flag=UserFlagCache(user.uid).get_flag().name, ) return ResponseSuccess(bs_data=bs_data).as_response()
class UserFlagResource(Resource): method_decorators = [ check_ip_in_white_list(ADMIN_IP_WHITE_LIST), limiter.limit("1/second") ] def get(self): """ 给用户绑定信息 :return: """ if not request.args: return ResponseSuccess( message= "参数规则:?merchant=test&account=861891111&perm=DEPOSIT|BINDCARD" ).as_response() try: merchant = MerchantEnum.from_name(request.args['merchant']) except: return ResponseSuccess(message="请输入正确的商户名称,有效的商户名称包括:%s" % MerchantEnum.get_names()).as_response() try: account = request.args['account'] account = '+' + account.strip('+').strip() if not PhoneNumberParser.is_valid_number(account): raise except: return ResponseSuccess( message="请输入正确的用户手机号码,必须有完整区号,不填+号,如:8613812349999" ).as_response() try: perms = request.args['perm'].split('|') perms = [UserPermissionEnum.from_name(perm) for perm in perms] except: return ResponseSuccess( message="标签权限,有效的权限包括: %s" % UserPermissionEnum.get_name_list()).as_response() user = User.query_user(merchant, account=account) if not user: return ResponseSuccess(message="手机号码未注册").as_response() User.update_user_permission(merchant, perms, account=account) user = User.query_user(merchant, account=account) bs_data = dict( account=account, uid=user.uid, perms=user.permission_names, ) return ResponseSuccess(bs_data=bs_data).as_response()
def setup_for_api(api): import warnings warnings.filterwarnings( "ignore", message="Multiple schemas resolved to the name " ) api.spec.components.security_scheme( 'bearerAuth', {'type': 'http', 'scheme': 'bearer', 'bearerFormat': 'JWT'} ) limiter.limit('20/day;10/hour;5/minute')(api_user_bp) limiter.limit('50/day;30/hour;5/minute')(api_expense_bp) @jwt.token_in_blocklist_loader def check_if_token_in_blocklist(jwt_headers, jwt_payload): if jwt_payload['type'] == 'refresh': return False jti = jwt_payload['jti'] return jti in block_list
class UserRegisterAccount(Resource): method_decorators = [ limiter.limit("1/second"), ] # 期待客户端请求数据模型, 用response 来装饰 @ns.expect(PassWordAuthCode) @ns.marshal_with(ResponseSuccess.gen_doc(api)) def post(self): """ 手机号码注册 :return: """ # 验证 手机号 验证码 密码格式 form, error = PasswordForm().request_validate() if error: return error.as_response() # 判断验证码是否过期 if AuthCodeGenerator(form.number.data).is_expired(form.auth_code.data): return AuthCodeExpiredError().as_response() # 判断验证码是否正确 if not AuthCodeGenerator(form.number.data).verify_code( form.auth_code.data): return AuthCodeError().as_response() if not MerchantFeeConfig.query_latest_one(query_fields=dict( merchant=form.merchant.data, payment_way=PayTypeEnum.DEPOSIT, )): return MerchantConfigDepositError().as_response() if not MerchantFeeConfig.query_latest_one(query_fields=dict( merchant=form.merchant.data, payment_way=PayTypeEnum.WITHDRAW, )): return MerchantConfigWithdrawError().as_response() User.register_account( merchant=form.merchant.data, account=form.number.data, ac_type=AccountTypeEnum.MOBILE, login_pwd=form.password.data, ) return ResponseSuccess().as_response()
class ForgetPasswordGetAuth(Resource): method_decorators = [ limiter.limit("1/10"), ] # 请求数据格式 @ns.expect(MobileNumber) # 相应数据格式 @ns.marshal_with(ResponseSuccess.gen_doc(api)) def post(self): """ 忘记密码 --验证手机号,获取验证码 :return: """ # 用户手机号格式验证 form, error = MobileRegisterTrueCheckForm().request_validate() if error: return error.as_response() # 首先获取当日是否已发送过验证码 if AuthCodeLimiter(form.number.data).is_limited(): return AuthCodeTimesLimitError().as_response() # 生成验证码 code = AuthCodeGenerator(form.number.data).generate_code() current_app.logger.info('code generated success, code: %s', code) # print('code: %s' % code) # 将验证码以短信方式发送到用户手机 try: if not current_app.config['DEBUG']: from app.services.celery.sms import async_send_auth_code async_send_auth_code.delay(phone=form.number.data, code=code) except: current_app.config['SENTRY_DSN'] and current_app.logger.fatal( traceback.format_exc()) # 验证码发送成功后,发送次数+1 AuthCodeLimiter(form.number.data).incr_times() return ResponseSuccess().as_response()
class DomainCheck(Resource): method_decorators = [ check_ip_in_white_list(ADMIN_IP_WHITE_LIST), limiter.limit("1/second") ] def get(self): """ 通道配置查询 :return: """ deposit_channels = ChannelListHelper.get_config_channels( PayTypeEnum.DEPOSIT) withdraw_channels = ChannelListHelper.get_config_channels( PayTypeEnum.WITHDRAW) bs_data = dict( deposit_channels=[x.short_description for x in deposit_channels], withdraw_channels=[x.short_description for x in withdraw_channels], ) return ResponseSuccess(bs_data=bs_data).as_response()
class ClientRegister(Resource): method_decorators = [ check_ip_in_white_list(MERCHANT_ADMIN_IP_LIST), limiter.limit("1/second") ] @ns.expect(MerchantRegister) @ns.marshal_with(ResponseSuccess.gen_doc(api)) def post(self): """ 商户后台 注册 """ form, error = MerchantLoginForm().request_validate() if error: return error.as_response() merchant_enum = form.account.data user = MerchantUser.register_account(mid=merchant_enum.value, account=merchant_enum.name, password=form.password.data) if user: return ResponseSuccess().as_response()
class ForgetPasswordReset(Resource): method_decorators = [ limiter.limit("1/second"), ] # 请求数据格式 @ns.expect(PassWordAuthCode) # 相应数据格式 @ns.marshal_with(ResponseSuccess.gen_doc(api)) def post(self): """ 忘记密码 --设置新密码 :return: """ # 用户手机号格式验证 form, error = PasswordTrueForm().request_validate() if error: return error.as_response() # 判断验证码是否过期 if AuthCodeGenerator(form.number.data).is_expired(form.auth_code.data): return AuthCodeExpiredError().as_response() # 判断验证码是否正确 if not AuthCodeGenerator(form.number.data).verify_code( form.auth_code.data): return AuthCodeError().as_response() User.reset_password(form.merchant.data, account=form.number.data, login_pwd=form.password.data) UserPasswordLimitCache(form.number.data).delete_cache() User.update_user_state(merchant=form.merchant.data, account=form.number.data, state=AccountStateEnum.ACTIVE) return ResponseSuccess().as_response()
class SMSCodeGenerator(Resource): # 验证码的发送要限速 method_decorators = [limiter.limit("1/10")] # 期待客户端请求的数据模型,使用expect来装饰 @ns.expect(MobileNumber) # 给客户端返回的的响应数据模型,使用marshal_with来装饰 @ns.marshal_with(ResponseSuccess.gen_doc(api)) def post(self): """ 获取短信动态验证码 """ # 用户手机号格式验证 form, error = MobileRegisterCheckForm().request_validate() if error: return error.as_response() # 首先获取当日是否已发送过验证码 if AuthCodeLimiter(form.number.data).is_limited(): return AuthCodeTimesLimitError().as_response() # 生成验证码 code = AuthCodeGenerator(form.number.data).generate_code() # current_app.logger.info('code generated success, code: %s', code) # 将验证码以短信方式发送到用户手机 try: if not current_app.config['DEBUG'] or current_app.config['TESTING']: from app.services.celery.sms import async_send_auth_code async_send_auth_code.delay(phone=form.number.data, code=code) except: current_app.config['SENTRY_DSN'] and current_app.logger.fatal(traceback.format_exc()) # 验证码发送成功后,发送次数+1 AuthCodeLimiter(form.number.data).incr_times() return ResponseSuccess().as_response()
class ClientLogin(Resource): method_decorators = [ limiter.limit("1/second"), ] @ns.expect(ClientAuthLogin) @ns.marshal_with(ResponseSuccessLogin.gen_doc(api)) def post(self): """ 用户登陆获取token :return: """ # 验证登陆表单是否正确 form, error = LoginForm().request_validate() if error: return error.as_response() if not MerchantFeeConfig.query_latest_one(query_fields=dict( merchant=form.merchant.data, payment_way=PayTypeEnum.DEPOSIT, )): return MerchantConfigDepositError().as_response() if not MerchantFeeConfig.query_latest_one(query_fields=dict( merchant=form.merchant.data, payment_way=PayTypeEnum.WITHDRAW, )): return MerchantConfigWithdrawError().as_response() # 验证手机号是否已注册 user_info = User.query_user(form.merchant.data, account=form.number.data) if not user_info: return AccountNotExistError().as_response() if user_info.state == AccountStateEnum.INACTIVE: return DisableUserError().as_response() # 验证用户名 密码是否正确 if not User.verify_login(merchant=form.merchant.data, account=form.number.data, password=form.password.data): UserPasswordLimitCache(mobile_number=form.number.data).incr_times() # 获取密码输入错误次数是否达到上限 if UserPasswordLimitCache( mobile_number=form.number.data).is_limited(): User.update_user_state(form.merchant.data, account=form.number.data, state=AccountStateEnum.INACTIVE) return OriPasswordError().as_response() return LoginPasswordError().as_response() UserPasswordLimitCache(mobile_number=form.number.data).delete_cache() # 生成token 返回给客户端 token = UserLoginToken.generate_token( uid=user_info.uid, merchant=form.merchant.data.value) # 显示用户名 bind_info = UserBindInfo.query_bind_by_uid(user_info.uid) if bind_info: bind_name = bind_info.name else: bind_name = PhoneNumberParser.hide_number(user_info.account) return ResponseSuccessLogin(bs_data=dict( token=token, service_url=SERVICE_URL, permissions=user_info.permission_names, bind_name=bind_name, user_flag=user_info.flag.name, )).as_response()
class DomainCheck(Resource): method_decorators = [ check_ip_in_white_list(ADMIN_IP_WHITE_LIST), limiter.limit("1/second") ] def get(self): """ 商户配置查询 :return: """ if not request.args: return ResponseSuccess(message="参数规则:?merchant=test").as_response() try: merchant = MerchantEnum.from_name(request.args['merchant']) except: return ResponseSuccess(message="请输入正确的商户名称,有效的商户名称包括:%s" % MerchantEnum.get_names()).as_response() merchant_info = MerchantInfo.query_merchant(merchant) if not merchant_info: return ResponseSuccess(message="未创建商户").as_response() bs_data = dict( balance=dict( balance_total=str(merchant_info.balance_total), balance_available=str(merchant_info.balance_available), balance_income=str(merchant_info.balance_income), balance_frozen=str(merchant_info.balance_frozen), ), merchant=merchant.name, domains=MerchantDomainConfig.get_domains(merchant), # db=DBEnum(merchant.name).get_db_name(), ) deposit_fees = MerchantFeeConfig.query_active_configs( query_fields=dict( merchant=merchant, payment_way=PayTypeEnum.DEPOSIT, )) deposit_fees = MerchantFeeConfig.filter_latest_items(deposit_fees) if not deposit_fees: return MerchantConfigDepositError(bs_data=bs_data).as_response() bs_data['deposit_fees'] = [x.short_description for x in deposit_fees] withdraw_fees = MerchantFeeConfig.query_latest_one(query_fields=dict( merchant=merchant, payment_way=PayTypeEnum.WITHDRAW, )) if not withdraw_fees: return MerchantConfigWithdrawError(bs_data=bs_data).as_response() bs_data['withdraw_fees'] = withdraw_fees.short_description channels = ChannelListHelper.get_available_channels( merchant, PayTypeEnum.DEPOSIT) bs_data['deposit_channels'] = [x.short_description for x in channels] channels = ChannelListHelper.get_available_channels( merchant, PayTypeEnum.WITHDRAW) bs_data['withdraw_channels'] = [x.short_description for x in channels] return ResponseSuccess(bs_data=bs_data).as_response()
:param token: :return: """ # 初始化g对象的error属性 g.error = None rst = MerchantLoginToken.verify_token(token) if isinstance(rst, (APIException, )): # token 验证失败 g.error = rst return False # 账户被封处理 user = MerchantUser.query_user(mid=rst['uid']) if not user: # 用户不存在 g.error = AccountNotExistError() return False g.user = user return True # merchant office 装饰器 merchant_decorators = [ check_ip_in_white_list(MERCHANT_ADMIN_IP_LIST), merchant_auth.login_required, limiter.limit("1/second") ]
class UserBindResource(Resource): method_decorators = [ check_ip_in_white_list(ADMIN_IP_WHITE_LIST), limiter.limit("1/second") ] def get(self): """ 给用户绑定信息 :return: """ if not request.args: return ResponseSuccess( message="参数规则:?merchant=test&account=861891111&name=大王&unbind=" ).as_response() try: merchant = MerchantEnum.from_name(request.args['merchant']) except: return ResponseSuccess(message="请输入正确的商户名称,有效的商户名称包括:%s" % MerchantEnum.get_names()).as_response() try: account = request.args['account'] account = '+' + account.strip('+').strip() if not PhoneNumberParser.is_valid_number(account): raise except: return ResponseSuccess( message="请输入正确的用户手机号码,必须有完整区号,不填+号,如:8613812349999" ).as_response() try: name = request.args['name'] except: return ResponseSuccess(message="绑定名称必填").as_response() user = User.query_user(merchant, account=account) if not user: return ResponseSuccess(message="手机号码未注册").as_response() bind_info = UserBindInfo.query_bind_by_uid(user.uid) if request.args.get('unbind'): if not bind_info: return ResponseSuccess(message="未绑定任何别名,无需解绑").as_response() if UserBindInfo.unbind_account(user.uid): return ResponseSuccess(message="解绑成功").as_response() else: return ResponseSuccess(message="解绑失败").as_response() else: if not bind_info: if UserBindInfo.bind_account(user.uid, merchant, account=user.account, name=name): msg = "绑定成功" else: msg = "绑定失败" return ResponseSuccess(message=msg).as_response() else: msg = "无需重复绑定,已经绑定名称:%s" % bind_info.name bind_info = UserBindInfo.query_bind_by_uid(user.uid) bs_data = dict( name=bind_info.name, account=bind_info.account, uid=user.uid, ) return ResponseSuccess(bs_data=bs_data, message=msg).as_response()