def register_zhiwang_account(user_id): identity = Identity.get(user_id) local_account = Account.get(user_id) zhiwang_account = ZhiwangAccount.get_by_local(user_id) if not local_account.mobile: raise MissingMobilePhoneError if not identity: raise MissingIdentityError if zhiwang_account: return zhiwang_account try: response = zhiwang.user_create(user_id, identity.person_ricn, identity.person_name, local_account.mobile) except RemoteError as e: raise MismatchUserError(u'绑定账号失败: %s,如有问题,请联系客服' % e.args[1]) else: if not response.is_new: if not current_app.debug: rsyslog.send( u'您的身份信息(%s,%s,%s,%s) 已经被注册过,如有问题,请联系客服' % (user_id, identity.person_ricn, identity.person_name, local_account.mobile), 'zhiwang_dup_register') return ZhiwangAccount.bind(user_id, response.zw_user_code)
def test_assign_channel(self): assert self.user.channel is None self.user.assign_channel_via_tag('west') assert self.user.channel == self.channel user = Account.get(self.user.id_) assert user.channel == self.channel
def j_forgot_password(): if g.user: return jsonify(r=False), 403 error = '' if request.method == 'POST': # TODO 设置依据IP可能会有校园网访问的问题 l = Limit.get(LIMIT.FORGOT_PASSWORD % request.remote_addr) if l.is_limited(): abort(429) l.touch() alias = request.form.get('alias') try: alias_type = validate_reset_password_asker(alias) user = Account.get_by_alias(alias) except AccountAliasValidationError as e: error = unicode(e) else: if alias_type == ACCOUNT_REG_TYPE.EMAIL: send_reset_password_mail(user) return jsonify(r=True, type='email', alias=alias) elif alias_type == ACCOUNT_REG_TYPE.MOBILE: return jsonify(r=True, type='mobile', alias=alias) return jsonify(r=False, error=error)
def token_dump(user_alias, workdir=None): """Dumps API token from database.""" account = Account.get_by_alias(user_alias) if not account: return bcolors.fail('user %r not found' % user_alias) workdir = os.path.expanduser(workdir or '~/.guihua') if not os.path.exists(workdir): os.makedirs(workdir) yixin_account = YixinAccount.get_by_local(account.id_) if not yixin_account: return bcolors.fail('%r need to bind yixin account' % user_alias) jsonfile_location = os.path.join(workdir, 'solar-yixin-token.json') if os.path.exists(jsonfile_location): with open(jsonfile_location) as jsonfile: data = json.load(jsonfile) if not isinstance(data, dict): return bcolors.fail('unexpected data') else: data = {} data[user_alias] = { 'user_alias': user_alias, 'yixin_account': yixin_account.p2p_account, 'yixin_token': yixin_account.p2p_token, } with open(jsonfile_location, 'w') as jsonfile: json.dump(data, jsonfile, indent=4) bcolors.success('success: %s %s' % (user_alias, yixin_account.p2p_account))
def register_mobile(self, user_mobile=None, user_password=None, user_nickname=None): self.browser.visit(self.url_for('accounts.login.login')) self.browser.find_by_css('.js-to-register').click() assert self.browser.is_element_visible_by_css('.btn-register-submit') mobile = user_mobile or '159%08d' % random.randint(1, 99999999) password = user_password or 'haoguihua123' assert self.browser.is_element_visible_by_css('.captcha-img') session = self.peep_session() captcha_code = digits_captcha.get(session['cap_secret']) self.browser.fill('mobile', mobile) self.browser.fill('captcha', captcha_code) self.browser.find_by_css('.captcha.btn').click() time.sleep(1) button_text = self.browser.find_by_css('.captcha.btn').text assert button_text.endswith(u'后重新发送') # 获取手机注册验证码 user = Account.get_by_alias(mobile) result = db.execute( 'select verify_code from user_verify ' 'where user_id=%s and code_type=%s', (user.id_, VERIFY_CODE_TYPE.REG_MOBILE)) self.browser.fill('verify_code', str(result[0][0])) self.browser.find_by_css('#reg-password').fill(password) self.browser.find_by_css('.btn-register-submit').click() return mobile, password
def add_account(self, email=None, mobile=None, password=None, name=None, status=0): from core.models.user.consts import ACCOUNT_REG_TYPE from core.models.user.account import Account from core.models.utils import pwd_hash from core.models.utils import randbytes if not email and not mobile: email = '*****@*****.**' alias_type = ACCOUNT_REG_TYPE.MOBILE \ if mobile else ACCOUNT_REG_TYPE.EMAIL alias = mobile or email password = password or 'test' salt = randbytes(4) passwd_hash = pwd_hash(salt, password) name = name or 'test' return Account.add(alias, passwd_hash, salt, name, reg_type=alias_type, status=status)
def register_account(user_id): identity = Identity.get(user_id) local_account = Account.get(user_id) if not local_account.mobile: raise MissingMobilePhoneError if not identity: raise MissingIdentityError vendor = Vendor.get_by_name(Provider.sxb) sxb_account = SxbAccount.get_by_local(vendor.id_, user_id) if sxb_account: return sxb_account try: response = sxb.create_account(user_id=user_id, person_name=identity.person_name, id_card_no=identity.person_ricn, mobile=local_account.mobile) except BusinessError as e: raise MismatchUserError(u'%s,如有问题,请联系客服' % e) else: if not response.is_new: if not current_app.debug: rsyslog.send( u'您的身份信息(%s,%s,%s,%s) 已经被注册过,如有问题,请联系客服' % (user_id, identity.person_ricn, identity.person_name, local_account.mobile), 'sxb_dup_register') return SxbAccount.bind(vendor.id_, user_id, response.user_id)
def reset_password(): """重置密码(发送短信验证码) 要使用本接口, 客户端必须有权以 ``user_info`` 作为 scope. :request: :class:`.ResetPasswordSchema` :response: :class:`.UserSchema` :reqheader X-Client-ID: OAuth 2.0 Client ID :reqheader X-Client-Secret: OAuth 2.0 Client Secret :status 403: 账号不存在 :status 200: 已发出验证码 """ reset_password_schema = ResetPasswordSchema(strict=True) user_schema = UserSchema(strict=True) result = reset_password_schema.load(request.get_json(force=True)) mobile_phone = result.data['mobile_phone'] sms_limiter.raise_for_exceeded( key=mobile_phone, message='{granularity}内只能发送{amount}次验证码,请稍后再试') user = Account.get_by_alias(mobile_phone) if user: sms = ShortMessage.create(mobile_phone, forgot_password_sms, user_id=user.id_) sms.send() sms_limiter.hit(key=mobile_phone) return jsonify(success=True, data=user_schema.dump(user).data) else: abort(403, u'账号不存在')
def reset_password_verify(): """重置密码(验证新密码) 要使用本接口, 客户端必须有权以 ``user_info`` 作为 scope. :request: :class:`.SetNewPasswordSchema` :response: :class:`.UserSchema` :reqheader X-Client-ID: OAuth 2.0 Client ID :reqheader X-Client-Secret: OAuth 2.0 Client Secret :status 403: 重置密码失败 :status 200: 重置密码成功 """ set_new_password_schema = SetNewPasswordSchema(strict=True) user_schema = UserSchema(strict=True) result = set_new_password_schema.load(request.get_json(force=True)) user = Account.get(result.data['uid']) if not user: abort(403, u'账号不存在') confirmed_code = VerifyVoucher(result.data['uid']) if result.data['confirmed_code'] != confirmed_code.voucher: abort(403, u'未验证身份或手机') if change_password(result.data['uid'], result.data['new_password']): confirmed_code.disable() return jsonify(success=True, data=user_schema.dump(user).data) abort(403, u'修改密码失败')
def reset_for_mail_user(user_id, code): error = '' user = Account.get(user_id) if not user: return redirect(url_for('.reset_failed')) try: # 当post时才删除验证码 v = Verify.validate(user.id_, code, VERIFY_CODE_TYPE.FORGOT_PASSWORD_EMAIL) if request.method == 'POST': v.delete() except VerifyCodeException as e: return redirect(url_for('.reset_failed')) if request.method == 'POST': # 校验密码是否合法一致 new_password = request.form.get('new-password') confirmed_password = request.form.get('confirmed-password') try: reset_password(user, new_password, confirmed_password) except PasswordValidationError as e: error = unicode(e) else: return redirect(url_for('.reset_success')) return render_template('accounts/reset_mail_user_password.html', error=error)
def bind(cls, account_id, p2p_account, p2p_token, commit=True): """Creates new binding relationship and cancels all existent.""" cls.check_before_binding(account_id) params = (account_id, p2p_account, p2p_token) if not Account.get(account_id): raise NotFoundError(account_id, Account) existent = cls.get_by_local(account_id) if existent: cls.unbind(account_id, commit=False) if cls.get_by_remote(p2p_account) or cls.get_by_p2p_token(p2p_token): raise RemoteAccountUsedError(p2p_account) sql = ('insert into {.table_name} (account_id, p2p_account, p2p_token)' 'values (%s, %s, %s)').format(cls) db.execute(sql, params) if commit: db.commit() cls.clear_cache(account_id) return cls.get_by_local(account_id)
def forgot(): error = '' alias = '' if request.method == 'POST': # TODO 设置依据IP可能会有校园网访问的问题 l = Limit.get(LIMIT.FORGOT_PASSWORD % request.remote_addr) if l.is_limited(): abort(429) l.touch() alias = request.form.get('alias') try: alias_type = validate_reset_password_asker(alias) user = Account.get_by_alias(alias) except AccountAliasValidationError as e: error = unicode(e) else: if alias_type == ACCOUNT_REG_TYPE.EMAIL: send_reset_password_mail(user) return render_template( 'accounts/forgot_password_mail_sent.html', alias=alias) elif alias_type == ACCOUNT_REG_TYPE.MOBILE: return render_template( 'accounts/reset_mobile_user_password.html', mobile=alias) return render_template('accounts/forgot_password.html', alias=alias, error=error)
def register_xm_account(user_id): identity = Identity.get(user_id) local_account = Account.get(user_id) xm_account = XMAccount.get_by_local(user_id) if not local_account.mobile: raise MissingMobilePhoneError if not identity: raise MissingIdentityError if xm_account: return xm_account try: response = xinmi.create_account(user_id=user_id, person_name=identity.person_name, person_ricn=identity.person_ricn, mobile=local_account.mobile) except BusinessError as e: raise MismatchUserError(u'%s,如有问题,请联系客服' % e) else: if not response.is_new: if not current_app.debug: rsyslog.send( u'您的身份信息(%s,%s,%s,%s) 已经被注册过,如有问题,请联系客服' % (user_id, identity.person_ricn, identity.person_name, local_account.mobile), 'xm_dup_register') return XMAccount.bind(user_id, response.user_id)
def test_bind_mobile_alias(self): # 1. 手机用户尝试绑定 mobile = '13200000000' account = self.add_account(mobile=mobile, status=ACCOUNT_STATUS.NORMAL) with raises(BindError): confirm_bind_without_check(account.id, '13211111111') # 2. 邮箱用户绑定一个从未尝试过任何注册的手机号 email = '*****@*****.**' mobile = '13222222222' account = self.add_account(email=email, status=ACCOUNT_STATUS.NORMAL) confirm_bind_without_check(account.id, mobile) self.assertEqual(account.email, email) self.assertEqual(account.mobile, mobile) # 3. 邮箱用户绑定一个之前注册但未通过验证的手机号 mobile = '13233333333' account = self.add_account(mobile=mobile) account_id = account.id email = '*****@*****.**' account = self.add_account(email=email) confirm_bind_without_check(account.id, mobile) failed_account = Account.get(account_id) self.assertTrue(failed_account.is_failed_account()) self.assertEqual(len(failed_account.reg_alias), 0) self.assertEqual(account.email, email) self.assertEqual(account.mobile, mobile)
def main(): user = Account.get_by_alias(EMAIL) if not user: user = register_without_confirm(EMAIL, 'testtest', ACCOUNT_REG_TYPE.EMAIL) bcolors.run(repr(user), key='zhiwang') # 绑定身份证和手机 user.add_alias(MOBILE_PHONE, ACCOUNT_REG_TYPE.MOBILE) identity = Identity.save(user.id_, PERSON_NAME, PERSON_RICN) bcolors.run(repr(identity), key='zhiwang') # 绑定指旺帐号 ZhiwangAccount.bind(user.id_, ZHIWANG_TOKEN) bcolors.run(repr(ZhiwangAccount.get_by_local(user.id_)), key='zhiwang') # 绑定银行卡 bankcards = BankCardManager(user.id_) with patch('core.models.profile.bankcard.DEBUG', True): bankcard = bankcards.create_or_update( mobile_phone=user.mobile, card_number=BANKCARD_NO, bank_id=BANKCARD_BANK, city_id=BANKCARD_DIVISION[:4] + u'00', province_id=BANKCARD_DIVISION[:2] + u'0000', local_bank_name=u'') bcolors.run(repr(bankcard), key='zhiwang') bcolors.run('success: %s' % EMAIL, key='zhiwang')
def change_user_password(): """ 修改密码(验证新密码) 要使用本接口, 客户端必须有权以 ``user_info`` 作为 scope. :request: :class:`.ChangePasswordSchema` :response: :class:`.UserSchema` :reqheader X-Client-ID: OAuth 2.0 Client ID :reqheader X-Client-Secret: OAuth 2.0 Client Secret :status 403: 修改密码失败 :status 200: 修改密码成功 """ change_password_schema = ChangePasswordSchema(strict=True) user_schema = UserSchema(strict=True) result = change_password_schema.load(request.get_json(force=True)) user = Account.get(request.oauth.user.id_) if not user: abort(403, u'账号不存在') if not user.verify_password(result.data['old_password']): abort(403, '原密码输入有误') if user.verify_password(result.data['new_password']): abort(403, '新密码与原密码一致,请重新输入') if change_password(user.id_, result.data['new_password']): return jsonify(success=True, data=user_schema.dump(user).data) else: abort(403, u'修改密码失败')
def create(cls, user_id, kind, properties=None): assert isinstance(kind, NotificationKind) assert properties is None or isinstance(properties, dict) # 校验参数 user = Account.get(user_id) if not user: raise ValueError('invalid user id') if kind.is_once_only: id_list = cls.get_id_list_by_user_and_kind(user.id_, kind.id_) if id_list: return cls.get(id_list[0]) sql = ('insert into {.table_name} (user_id, kind_id, is_read, ' 'creation_time) values (%s, %s, %s, %s)').format(cls) params = (user_id, kind.id_, False, datetime.now()) id_ = db.execute(sql, params) db.commit() instance = cls.get(id_) instance.properties = properties or {} # 单播消息则提交并加入推送队列 cls.clear_cache_by_user(user.id_) cls.clear_cache_by_user_and_kind(user.id_, kind.id_) # 由推送控制中心来记录和完成推送 if kind.allow_push: mq_notification_push.produce(str(id_)) return instance
def test_forgot_password_for_email_account(self): # as email can't be used to register any more, we use the # auto - generated [email protected] for test email = '*****@*****.**' browser = self.browser browser.visit(self.url_for('accounts.password.forgot')) browser.find_by_css('input[name="alias"]').fill(email) self._submit() assert browser.is_element_present_by_css('a.js-btn-resend-forgot') # 获取邮箱更改密码的url地址 user = Account.get_by_alias(email) result = db.execute('select verify_code from user_verify ' 'where user_id=%s and code_type=%s', (user.id, VERIFY_CODE_TYPE.FORGOT_PASSWORD_EMAIL)) browser.visit(self.url_for( 'accounts.password.reset_for_mail_user', user_id=user.id_, code=str(result[0][0]), _external=True)) assert browser.is_element_present_by_css('a.confirm-password-submit') browser.find_by_css('input[name="new-password"]').fill('testtest') browser.find_by_css('input[name="confirmed-password"]').fill('testtest') browser.find_by_css('a.confirm-password-submit').click() assert browser.is_element_present_by_css('.verification-box') assert browser.is_text_present('密码修改成功')
def validate_reset_password_asker(alias): if not alias: raise AccountAliasValidationError() reg_type = get_reg_type_from_alias(alias) if reg_type == ACCOUNT_REG_TYPE.EMAIL: if validate_email(alias) != errors.err_ok: raise UnsupportedAliasError() if alias.strip().endswith(INSECURE_EMAIL_DOMAINS): raise InsecureEmailError() elif reg_type == ACCOUNT_REG_TYPE.MOBILE: if validate_phone(alias) != errors.err_ok: raise UnsupportedAliasError() else: raise UnsupportedAliasError() user = Account.get_by_alias(alias) if not user: raise AccountNotFoundError() if user.status != ACCOUNT_STATUS.NORMAL: raise AccountInactiveError() return reg_type
def test_forgot_password_for_mobile_account(self): # register first mobile, password = self.register_mobile() # logout then self.logout() # get verify code for 1th try browser = self.browser browser.visit(self.url_for('accounts.password.forgot')) browser.find_by_css('input[name="alias"]').fill(mobile) self._submit() assert browser.is_element_present_by_css('a.js-btn-getcode') browser.find_by_css('a.js-btn-getcode').click() time.sleep(1) # 获取手机验证码 user = Account.get_by_alias(mobile) result = db.execute('select verify_code from user_verify ' 'where user_id=%s and code_type=%s', (user.id, VERIFY_CODE_TYPE.FORGOT_PASSWORD_MOBILE)) browser.find_by_css('input[name="code"]').fill(str(result[0][0])) browser.find_by_css('input[name="new-password"]').fill('testtest1234') browser.find_by_css('input[name="password"]').fill('testtest1234') browser.find_by_css('a.confirm-password-submit').click() assert browser.is_element_present_by_css('.verification-box') assert browser.is_text_present('密码修改成功')
def check_before_adding(cls, service_id, user_id, order_amount): yixin_service = YixinService.get(service_id) yixin_account = YixinAccount.get_by_local(user_id) user = Account.get(user_id) # checks the related entities if not yixin_service: raise NotFoundError(service_id, YixinService) if not user: raise NotFoundError(user_id, Account) if not yixin_account: raise UnboundAccountError(user_id) # checks the identity if not has_real_identity(user): raise InvalidIdentityError # checks available if yixin_service.sell_out: raise SellOutError(yixin_service.uuid) if yixin_service.take_down: raise TakeDownError(yixin_service.uuid) # checks the amount type if not isinstance(order_amount, decimal.Decimal): raise TypeError('order_amount must be decimal') # checks the amount range amount_range = (yixin_service.invest_min_amount, yixin_service.invest_max_amount) if (order_amount.is_nan() or order_amount < 0 or order_amount < yixin_service.invest_min_amount or order_amount > yixin_service.invest_max_amount): raise OutOfRangeError(order_amount, amount_range)
def check_before_adding(cls, user_id, product_id, bankcard_id, amount): product = PlaceboProduct.get(product_id) bankcard = BankCard.get(bankcard_id) user = Account.get(user_id) # 检查关联对象 for attr_name, attr in [('product_id', product), ('bankcard_id', bankcard), ('user_id', user)]: if not attr: raise ValueError('invalid %s' % attr_name) # 检查身份认证 if not has_real_identity(user): raise InvalidIdentityError() # 检查产品是否可售 if not product.strategy.target(user_id): raise InvalidProductError(product_id) # 策略拒绝 if not product.in_stock: raise OffShelfError(product_id) # 下架 # 检查金额范围 if amount is None and product.min_amount == product.max_amount: amount = product.min_amount if (amount.is_nan() or amount < product.min_amount or amount > product.max_amount): raise OutOfRangeError(amount, (product.min_amount, product.max_amount)) return user, product, bankcard, amount
def add(cls, account_id): if not Account.get(account_id): raise NotFoundError(account_id, Account) existent = cls.get(account_id) if existent: # 临时做法,弥补之前未添加创建时间的错误 if existent.creation_time is None: supply_sql = ('update {.table_name} set creation_time=%s' 'where account_id=%s').format(cls) supply_params = (datetime.now(), account_id) db.execute(supply_sql, supply_params) db.commit() cls.clear_cache(account_id) return existent sql = ('insert into {.table_name} (account_id, creation_time) ' 'values (%s, %s) ' 'on duplicate key update account_id = account_id').format(cls) params = (account_id, datetime.now()) db.execute(sql, params) db.commit() from core.models.hoard.profile import HoardProfile # if user hasn't hoard profile if not HoardProfile.get(account_id): add_savings_users() cls.clear_cache(account_id) return cls.get(account_id)
def add(cls, account_id): """Creates a new profile for specific account and return it. If a profile exists, it will be return directly. :param account_id: the primary key of local account. :returns: the created (or existent) profile. """ if not Account.get(account_id): raise NotFoundError(account_id, Account) existent = cls.get(account_id) if existent: return existent sql = ('insert into {.table_name} (account_id) values (%s) ' 'on duplicate key update account_id = %s').format(cls) params = (account_id, account_id) db.execute(sql, params) db.commit() from core.models.hoard.zhiwang.profile import ZhiwangProfile from core.models.hoard.xinmi.profile import XMProfile # if user hasn't zhiwang and xm profile if not ZhiwangProfile.get(account_id) and not XMProfile.get( account_id): add_savings_users() cls.clear_cache(account_id) cls.clear_cache_for_account_ids() return cls.get(account_id)
def test_assign_blackhole(self): assert self.user.channel is None self.user.assign_channel_via_tag('nothing') assert self.user.channel is None user = Account.get(self.user.id_) assert user.channel is None
def restore(cls, id_, user_id): if not Account.get(user_id): raise ValueError('invalid user %r' % user_id) instance = cls(id_, user_id, None, None, None, None) new_card = cls.get_by_card_number(instance.card_number) if new_card: raise BankCardChanged(new_card.id_) card_number_sha1 = calculate_checksum(instance.card_number) bank_id_sha1 = calculate_checksum(instance.bank_id) sql = ('insert into {.table_name} (id, user_id, card_number_sha1,' ' bank_id_sha1, status) ' 'values (%s, %s, %s, %s, %s)').format(cls) params = (id_, user_id, card_number_sha1, bank_id_sha1, cls.Status.active.value) db.execute(sql, params) db.commit() bankcard = cls.get(id_) rsyslog.send('\t'.join([ str(id_), str(user_id), str(bankcard.card_number), str(bankcard.bank_id), str(bankcard.mobile_phone), str(bankcard.province_id), str(bankcard.city_id), str(bankcard.local_bank_name), ]), tag='restore_bankcard') return bankcard
def reset_mobile_user_password(): ''' /j/account/reset_mobile_user_password ''' # 校验手机号是否对应已存在的用户 mobile = request.form.get('mobile') user = Account.get_by_alias(mobile) if not user: return jsonify(r=False, error=u'该手机号尚未注册好规划') # 校验验证码是否正确 code = request.form.get('code') try: v = Verify.validate(user.id_, code, VERIFY_CODE_TYPE.FORGOT_PASSWORD_MOBILE) v.delete() except VerifyCodeException as e: return jsonify(r=False, error=unicode(e)) # 校验密码是否合法一致 new_password = request.form.get('new_password') confirmed_password = request.form.get('confirmed_password') try: reset_password(user, new_password, confirmed_password) except PasswordValidationError as e: return jsonify(r=False, error=unicode(e)) else: return jsonify(r=True)
def main(): user = Account.get_by_alias(EMAIL) ZhiwangAccount.unbind(user.id_) try: register_zhiwang_account(user.id_) zhiwang_account = ZhiwangAccount.get_by_local(user.id_) except (MismatchUserError, RepeatlyRegisterError) as e: bcolors.fail(e.args[0], key='zhiwang_code') return bcolors.run('The new zhiwang code is %s' % zhiwang_account.zhiwang_id, key='zhiwang_code') with open(ADD_ZHIWANG_FILE, 'r') as f: source = RE_ZHIWANG_CODE.sub( "ZHIWANG_TOKEN = u'%s'" % zhiwang_account.zhiwang_id.encode('ascii'), f.read()) with open(ADD_ZHIWANG_FILE, 'w') as f: f.write(source) bcolors.run('%s is changed. PLEASE COMMIT IT AND OPEN A MERGE REQUEST.' % ADD_ZHIWANG_FILE, key='zhiwang_code') if '--commit' in sys.argv: subprocess.check_call( ['git', 'commit', '-m', COMMIT_MSG, '--', ADD_ZHIWANG_FILE])
def validate_mobile(self, field): if errors.err_ok != validate_phone(field.data): raise validators.StopValidation(message=u'手机号错误') user = Account.get_by_alias(field.data) if user and not user.need_verify(): raise validators.StopValidation(message=u'手机号已被注册 试试直接登录吧')
def hook_up(cls, user_id, device_id, device_platform, device_app_version): """用户激活(注销后登录或时机触发)、新建(初次登录)、覆盖(设备发生新用户登录)绑定关系""" assert isinstance(device_platform, Platform) assert isinstance(device_app_version, SetuptoolsVersion) current_user = Account.get(user_id) if not current_user: raise ValueError('invalid user id %s' % user_id) if device_platform not in [Platform.ios, Platform.android]: raise ValueError('unsupported platform %s' % device_platform) binding = DeviceBinding.get_by_device(device_id) if binding: # 更新设备装载APP的版本信息 binding.update_app_version(device_app_version) # 如果设备曾进行过注册 if binding.user_id == current_user.id_: # 如果已有绑定用户与当前请求用户一致,认定为*用户激活*(注销后又登录) # 1. 激活设备绑定关系 binding.activate() # 2. 为所有用户的设备同步用户标签 cls(binding.user_id).synchronize_tags() else: # 如果已有绑定用户与当前请求用户不一致,认定为*覆盖登录* cls.switch_device_user(binding, current_user) else: # 如果设备从未注册 cls.register_user_device( current_user, device_id, device_platform, device_app_version)