class UserRight(models.Model): """ 用户可读资源权限表(记录加密资源可读性) """ user = models.ForeignKey( 'User.User', on_delete=models.CASCADE, ) res = models.ForeignKey( 'Resource.Resource', on_delete=models.CASCADE, ) verify_time = models.FloatField(default=0, ) @classmethod def update(cls, user: User, res: Resource): try: right = cls.get_right(user, res) right.verify_time = datetime.datetime.now().timestamp() right.save() except E as e: if e.eis(ResourceError.RESOURCE_NOT_FOUND): try: right = cls( user=user, res=res, verify_time=datetime.datetime.now().timestamp()) right.save() except Exception: raise ResourceError.CREATE_RIGHT else: return e return right @classmethod def get_right(cls, user, res): try: right = cls.objects.get(user=user, res=res) except cls.DoesNotExist: raise ResourceError.RIGHT_NOT_FOUND return right @classmethod def verify(cls, user: User, res: Resource): if not user: return False try: right = cls.get_right(user, res) except E: return False if right.verify_time > res.vk_change_time: return True else: right.delete() return False
class User(models.Model): """ 用户类 根超级用户id=1 """ ROOT_ID = 1 username = models.CharField( max_length=32, min_length=3, unique=True, blank=True, null=True, default=None, ) password = models.CharField( max_length=32, min_length=6, ) salt = models.CharField( max_length=10, default=None, ) pwd_change_time = models.FloatField( null=True, blank=True, default=0, ) inviter = models.ForeignKey('User.User', on_delete=models.CASCADE, default=None, null=True) invite_code = models.CharField( default=None, max_length=255, blank=True, null=True, ) enter_room = models.BooleanField(default=False) def is_ancestor(self, user: '******'): while user: if self == user: return True user = user.inviter return False @staticmethod def _valid_username(username): """验证用户名合法""" if username[0] not in string.ascii_lowercase + string.ascii_uppercase: raise UserError.INVALID_USERNAME_FIRST valid_chars = '^[A-Za-z0-9_]{3,32}$' if re.match(valid_chars, username) is None: raise UserError.INVALID_USERNAME @staticmethod def _valid_password(password): """验证密码合法""" valid_chars = '^[A-Za-z0-9!@#$%^&*()_+-=,.?;:]{6,16}$' if re.match(valid_chars, password) is None: raise UserError.INVALID_PASSWORD @staticmethod def hash_password(raw_password, salt=None): if not salt: salt = get_random_string(length=6) hash_password = User._hash(raw_password + salt) return salt, hash_password @classmethod def exist_with_username(cls, username): try: cls.objects.get(username=username) except cls.DoesNotExist: return raise UserError.USERNAME_EXIST @classmethod def inviterjsonArr(cls, data): rData = [] for item in data: item.__dict__.pop("_state") rData.append(item.__dict__) return rData @classmethod def return_inviter(cls, invite_code): try: cls.objects.get(invite_code=invite_code) except cls.DoesNotExist: raise UserError.NOT_FOUND_COD else: user = User.objects.get(invite_code=invite_code) print(user.username) return user @classmethod def create_invite(cls, username, password, invite_code): """ 创建用户(有邀请码) :param username: 用户名 :param password: 密码 :param invite_code: 邀请码 :return: Ret对象,错误返回错误代码,成功返回用户对象 """ salt, hashed_password = User.hash_password(password) User.exist_with_username(username) # User.exist_with_invitecode(invite_code) # print(invite_code) inviter = User.return_inviter(invite_code) # print(inviter.username) try: user = cls(username=username, password=hashed_password, salt=salt, inviter=inviter, invite_code=username + "666") user.save() except Exception: raise UserError.CREATE_USER return user @classmethod def create(cls, username, password): """ 创建用户 :param username: 用户名 :param password: 密码 :param invite_code: 邀请码 :return: Ret对象,错误返回错误代码,成功返回用户对象 """ cls.validator(locals()) salt, hashed_password = User.hash_password(password) User.exist_with_username(username) try: user = cls( username=username, password=hashed_password, salt=salt, ) user.save() except Exception: raise UserError.CREATE_USER return user def change_password(self, password, old_password): """修改密码""" self.validator(locals()) if self.password != User._hash(old_password): raise UserError.PASSWORD self.salt, self.password = User.hash_password(password) import datetime self.pwd_change_time = datetime.datetime.now().timestamp() self.save() @staticmethod def _hash(s): import hashlib md5_ = hashlib.md5() md5_.update(s.encode()) return md5_.hexdigest() @staticmethod def get_user_by_username(username): """根据用户名获取用户对象""" try: user = User.objects.get(username=username) except User.DoesNotExist: raise UserError.NOT_FOUND_USER return user @staticmethod def get_user_by_id(user_id): """根据用户ID获取用户对象""" try: user = User.objects.get(pk=user_id) except User.DoesNotExist: raise UserError.NOT_FOUND_USER return user def entered_room(self): self.enter_room = True self.save() def leave_room(self): print("######leave the room") self.enter_room = False self.save() @classmethod def authenticate(cls, username, password): """验证用户名和密码是否匹配""" cls.validator(locals()) try: user = User.objects.get(username=username) except User.DoesNotExist as err: raise UserError.NOT_FOUND_USER salt, hashed_password = User.hash_password(password, user.salt) if hashed_password == user.password: return user raise UserError.PASSWORD def is_beinviter(self, inviter): if self.username == inviter: return True else: inviters = User.objects.filter(inviter__exact=self.username) if len(inviters) > 0: for invite in inviters: # print(inviter.username) return invite.is_beinviter(inviter) else: return False def d(self): return self.dictor('pk->uid', 'username', 'inviter', 'enter_room') def d_invite(self): return self.dictor('pk->id', 'username') def d_username(self): return self.dictor('username') def _readable_inviter(self): if self.inviter: return self.inviter.d_base() def d_base(self): return self.dictor('pk->id', 'username')
class User(models.Model): """ 用户类 根超级用户id=1 """ ROOT_ID = 1 username = models.CharField( max_length=32, min_length=3, unique=True, blank=True, null=True, default=None, ) password = models.CharField( max_length=32, min_length=6, ) salt = models.CharField( max_length=10, default=None, ) pwd_change_time = models.FloatField( null=True, blank=True, default=0, ) nickname = models.CharField( max_length=10, default=None, ) phone = models.CharField( default=None, unique=True, max_length=20, ) meat_quantity = models.IntegerField( # 已发布任务数目 default=0, ) def is_ancestor(self, user: '******'): while user: if self == user: return True user = user.inviter return False @staticmethod def _valid_username(username): """验证用户名合法""" if username[0] not in string.ascii_lowercase + string.ascii_uppercase: raise UserError.INVALID_USERNAME_FIRST valid_chars = '^[A-Za-z0-9_]{3,32}$' if re.match(valid_chars, username) is None: raise UserError.INVALID_USERNAME @staticmethod def _valid_password(password): """验证密码合法""" valid_chars = '^[A-Za-z0-9!@#$%^&*()_+-=,.?;:]{6,16}$' if re.match(valid_chars, password) is None: raise UserError.INVALID_PASSWORD @staticmethod def hash_password(raw_password, salt=None): if not salt: salt = get_random_string(length=6) hash_password = User._hash(raw_password + salt) return salt, hash_password @classmethod def exist_with_username(cls, username): try: cls.objects.get(username=username) except cls.DoesNotExist: return raise UserError.USERNAME_EXIST @classmethod def get_by_phone(cls, phone): """根据手机号获取用户对象""" try: cls.objects.get(phone=phone) raise UserError.PHONE_REGISTERED except cls.DoesNotExist: pass @classmethod def inviterjsonArr(cls, data): rData = [] for item in data: item.__dict__.pop("_state") rData.append(item.__dict__) return rData @classmethod def create(cls, phone, username, password, nickname): """ 创建用户 :param phone: 手机号 :param username: 用户名 :param password: 密码 :param nickname: 昵称 :return: Ret对象,错误返回错误代码,成功返回用户对象 """ cls.validator(locals()) salt, hashed_password = User.hash_password(password) User.exist_with_username(username) try: user = cls(username=username, password=hashed_password, salt=salt, nickname=nickname, phone=phone) user.save() except Exception as err: raise UserError.CREATE_USER(debug_message=err) return user def add_meat_quantity(self): try: self.meat_quantity = self.meat_quantity + 1 self.save() except Exception: raise UserError.ADD_MEAT_QUANTITY def reduce_meat_quantity(self): try: self.meat_quantity = self.meat_quantity - 1 self.save() except Exception: raise UserError.ADD_MEAT_QUANTITY def change_password(self, password, old_password): """修改密码""" self.validator(locals()) if self.password != User._hash(old_password): raise UserError.PASSWORD self.salt, self.password = User.hash_password(password) import datetime self.pwd_change_time = datetime.datetime.now().timestamp() self.save() @staticmethod def _hash(s): import hashlib md5_ = hashlib.md5() md5_.update(s.encode()) return md5_.hexdigest() @staticmethod def get_user_by_username(username): """根据用户名获取用户对象""" try: user = User.objects.get(username=username) except User.DoesNotExist: raise UserError.NOT_FOUND_USER return user @staticmethod def get_user_by_id(user_id): """根据用户ID获取用户对象""" try: user = User.objects.get(pk=user_id) except User.DoesNotExist: raise UserError.NOT_FOUND_USER return user @classmethod def authenticate(cls, username, password): """验证用户名和密码是否匹配""" cls.validator(locals()) try: user = User.objects.get(username=username) except User.DoesNotExist as err: raise UserError.NOT_FOUND_USER salt, hashed_password = User.hash_password(password, user.salt) if hashed_password == user.password: return user raise UserError.PASSWORD def change_talked(self): print(bool(1 - self.talked)) self.talked = bool(1 - self.talked) self.save() def d(self): return self.dictor('pk->uid', 'username', 'nickname') def d_base(self): return self.dictor('pk->id', 'username')
class Resource(models.Model): """ 资源类 根资源文件夹id=1 一旦新增用户,就在根目录创建一个属于新增用户的文件夹 """ ROOT_ID = 1 rname = models.CharField( verbose_name='resource name', max_length=256, ) rtype = models.IntegerField( verbose_name='file or folder', choices=RtypeChoice.list(), ) rsize = models.IntegerField(default=0, ) sub_type = models.IntegerField( verbose_name='sub type', choices=StypeChoice.list(), default=StypeChoice.FOLDER.value, ) mime = models.CharField( verbose_name='资源类型', null=True, default=True, max_length=100, ) description = models.TextField( verbose_name='description in Markdown', null=True, blank=True, default=None, ) cover = models.CharField( null=True, blank=True, default=None, max_length=1024, ) owner = models.ForeignKey( User, on_delete=models.CASCADE, ) parent = models.ForeignKey( 'Resource', null=True, blank=True, default=0, on_delete=models.CASCADE, ) dlpath = models.CharField( verbose_name='download relative path to res.6-79.cn', max_length=1024, default=None, null=True, blank=True, ) status = models.IntegerField( choices=StatusChoice.list(), verbose_name='加密状态 0公开 1仅自己可见 2需要密码', default=StatusChoice.PUBLIC.value, ) visit_key = models.CharField( max_length=16, min_length=3, verbose_name='当status为2时有效', ) vk_change_time = models.FloatField( null=True, blank=True, default=0, ) create_time = models.DateTimeField() dlcount = models.IntegerField( verbose_name='download number', default=0, ) res_str_id = models.CharField( verbose_name='唯一随机资源ID,弃用res_id', default=None, null=True, blank=True, max_length=6, unique=True, ) right_bubble = models.NullBooleanField( verbose_name='读取权限向上冒泡', default=True, ) cover_type = models.IntegerField( choices=CoverChoice.list(), verbose_name='封面类型 0 上传图片 1 与父资源相同 2 与指定资源相同 3 外部URI链接', default=CoverChoice.RANDOM.value, null=0, blank=0, ) @classmethod def get_unique_id(cls): while True: res_str_id = get_random_string(length=6) try: cls.get_by_id(res_str_id) except E as ret: if ret.eis(ResourceError.RESOURCE_NOT_FOUND): return res_str_id @staticmethod def _valid_rname(rname): """验证rname属性""" invalid_chars = '\\/*:\'"|<>?' for char in invalid_chars: if char in rname: raise ResourceError.INVALID_RNAME @staticmethod def _valid_res_parent(parent): """验证parent属性""" if not isinstance(parent, Resource): raise BaseError.STRANGE if parent.rtype != RtypeChoice.FOLDER.value: raise ResourceError.FILE_PARENT @classmethod def create_abstract(cls, rname, rtype, desc, user, parent, dlpath, rsize, sub_type, mime): crt_time = datetime.datetime.now() return cls( rname=rname, rtype=rtype, mime=mime, description=desc, cover=None, cover_type=CoverChoice.SELF.value if sub_type == StypeChoice.IMAGE.value else CoverChoice.RANDOM.value, owner=user, parent=parent, dlpath=dlpath, status=StatusChoice.PRIVATE.value, visit_key=get_random_string(length=4), create_time=crt_time, vk_change_time=crt_time.timestamp(), rsize=rsize, sub_type=sub_type, res_str_id=cls.get_unique_id(), dlcount=0, right_bubble=True, ) @classmethod def create_file(cls, rname, user, res_parent, dlpath, rsize, sub_type, mime): """ 创建文件对象 :param mime: 七牛返回的资源类型 :param rname: 文件名 :param user: 所属用户 :param res_parent: 所属目录 :param dlpath: 七牛存储的key :param rsize: 文件大小 :param sub_type: 文件分类 :return: Ret对象,错误返回错误代码,成功返回文件对象 """ try: res = cls.create_abstract( rname=rname, rtype=RtypeChoice.FILE.value, desc=None, user=user, parent=res_parent, dlpath=dlpath, rsize=rsize, sub_type=sub_type, mime=mime, ) res.save() except Exception: raise ResourceError.CREATE_FILE return res @classmethod def create_folder(cls, rname, user, res_parent, desc=None): """ 创建文件夹对象 :param rname: 文件夹名 :param user: 所属用户 :param res_parent: 所属目录 :param desc: 描述说明 :return: Ret对象,错误返回错误代码,成功返回文件夹对象 """ try: res = cls.create_abstract( rname=rname, rtype=RtypeChoice.FOLDER.value, desc=desc, user=user, parent=res_parent, dlpath=None, rsize=0, sub_type=StypeChoice.FOLDER.value, mime=None, ) res.save() except Exception: raise ResourceError.CREATE_FOLDER return res @classmethod def create_link(cls, rname, user, res_parent, dlpath): """ 创建链接对象 :param rname: 链接名称 :param user: 所属用户 :param res_parent: 所在目录 :param dlpath: 链接地址 :return: Ret对象,错误返回错误代码,成功返回链接对象 """ try: res = cls.create_abstract( rname=rname, rtype=RtypeChoice.LINK, desc=None, user=user, parent=res_parent, dlpath=dlpath, rsize=0, sub_type=StypeChoice.LINK.value, mime=None, ) res.save() except Exception: raise ResourceError.CREATE_LINK return res """ 查询方法 """ @classmethod def get_by_id(cls, res_str_id): try: res = cls.objects.get(res_str_id=res_str_id) except cls.DoesNotExist: raise ResourceError.RESOURCE_NOT_FOUND return res @classmethod def get_by_pk(cls, res_id): """根据资源id获取资源对象""" try: res = cls.objects.get(pk=res_id) except cls.DoesNotExist: raise ResourceError.RESOURCE_NOT_FOUND return res def belong(self, user): """判断资源是否属于用户""" return self.owner.pk == user.pk def is_home(self): return self.parent.pk == Resource.ROOT_ID def get_cover_urls(self): """获取封面链接""" res = self cover = None while res.pk != Resource.ROOT_ID: if res.cover_type == CoverChoice.PARENT.value: res = res.parent elif res.cover_type == CoverChoice.RESOURCE.value: try: res = Resource.get_by_id(res.cover) except E: return None, None if not res.belong(self.owner): return None, None else: cover = res.cover break if res.cover_type == CoverChoice.SELF.value: if res.sub_type == StypeChoice.IMAGE.value: from Base.qn_manager import qn_res_manager return (qn_res_manager.get_resource_url(res.dlpath), qn_res_manager.get_resource_url("%s-small" % res.dlpath)) if cover is None: return None, None if res.cover_type == CoverChoice.UPLOAD.value: from Base.qn_manager import qn_res_manager return (qn_res_manager.get_resource_url(cover), qn_res_manager.get_resource_url("%s-small" % cover)) else: return cover, cover """ 字典方法 """ def _readable_owner(self): return self.owner.d() def _readable_create_time(self): return self.create_time.timestamp() def _readable_visit_key(self): return self.visit_key if self.status == StatusChoice.PROTECT.value else None def _readable_is_home(self): return self.is_home() def _readable_secure_env(self): if self.pk == Resource.ROOT_ID or \ not self.right_bubble or \ self.status == StatusChoice.PUBLIC.value: return True res = self.parent while res.pk != Resource.ROOT_ID: if res.status == StatusChoice.PUBLIC.value: return res.rname if not res.right_bubble: break res = res.parent return True def _readable_raw_cover(self): return self.cover def _readable_parent_str_id(self): return self.parent.res_str_id def d(self): dict_ = self.dictify('res_str_id', 'rname', 'rtype', 'rsize', 'sub_type', 'description', 'cover_type', 'owner', 'parent_str_id', 'status', 'create_time', 'dlcount', 'visit_key', 'is_home', 'right_bubble', 'secure_env', 'raw_cover') cover_urls = self.get_cover_urls() dict_.update(dict( cover=cover_urls[0], cover_small=cover_urls[1], )) return dict_ def d_base(self): dict_ = self.dictify('status', 'is_home', 'owner', 'create_time', 'right_bubble') cover_urls = self.get_cover_urls() dict_.update(dict( cover=cover_urls[0], cover_small=cover_urls[1], )) return dict_ def d_child(self): dict_ = self.dictify('res_str_id', 'rname', 'rtype', 'status', 'create_time', 'sub_type', 'dlcount') cover_urls = self.get_cover_urls() dict_.update(dict(cover_small=cover_urls[1], )) return dict_ def d_layer(self): child_list = Resource.objects.filter(parent=self).dict( Resource.d_child) return dict( info=self.d(), child_list=child_list, ) def d_child_selector(self): return self.dictify('res_str_id', 'rname', 'rtype', 'sub_type') def d_selector_layer(self): child_list = Resource.objects.filter(parent=self).dict( Resource.d_child_selector) info = self.dictify('is_home', 'res_str_id', 'rname', 'parent_str_id') return dict( info=info, child_list=child_list, ) """ 查询方法 """ @classmethod def get_root_folder(cls, user): """获取当前用户的根目录""" try: res = cls.objects.get(owner=user, parent=1, rtype=RtypeChoice.FOLDER.value) except cls.DoesNotExist: raise ResourceError.GET_ROOT_FOLDER return res def readable(self, user, visit_key): """判断当前资源是否被当前用户可读""" res = self while res.pk != Resource.ROOT_ID: if res.owner == user or res.status == StatusChoice.PUBLIC.value: return True if res.status == StatusChoice.PROTECT.value and res.visit_key == visit_key: if user: UserRight.update(user, res) return True if res.status == StatusChoice.PROTECT.value and UserRight.verify( user, res): return True if not res.right_bubble: break res = res.parent visit_key = None return False def get_dl_url(self): """获取当前资源的下载链接""" self.dlcount += 1 self.save() if self.rtype == RtypeChoice.LINK: return self.dlpath from Base.qn_manager import qn_res_manager return qn_res_manager.get_resource_url(self.dlpath) def get_visit_key(self): """获取当前资源的访问密码""" if self.status == StatusChoice.PROTECT.value: return self.visit_key return None """ 修改方法 """ def modify_rname(self, rname): key = self.dlpath new_key = '%s/%s' % (key[:key.rfind('/')], rname) from Base.qn_manager import qn_res_manager qn_res_manager.move_res(key, new_key) self.rname = rname self.dlpath = new_key self.save() def modify_info(self, rname, description, status, visit_key, right_bubble, parent): """ 修改资源属性 :param rname: 资源名称 :param description: 资源介绍 :param status: 资源分享类型(公开、私有、加密) :param visit_key: 资源加密密钥 :param right_bubble: 资源读取权限是否向上查询 :param parent: 移动后的新父目录 :return: Ret对象,错误返回错误代码,成功返回资源对象 """ if rname is None: rname = self.rname if description is None: description = self.description or '' if status is None: status = self.status if visit_key is None: visit_key = self.visit_key if right_bubble is None: right_bubble = self.right_bubble if parent is None: parent = self.parent if self.rname != rname: if self.rtype == RtypeChoice.FILE.value: self.modify_rname(rname) else: self.rname = rname self.description = description self.status = status self.right_bubble = right_bubble self.parent = parent if status == StatusChoice.PROTECT.value: if self.visit_key != visit_key: self.visit_key = visit_key self.vk_change_time = datetime.datetime.now().timestamp() self.save() def modify_cover(self, cover, cover_type): """修改资源封面""" from Base.qn_manager import qn_res_manager if self.cover_type == CoverChoice.UPLOAD.value: try: qn_res_manager.delete_res(self.cover) except: pass self.cover = cover self.cover_type = cover_type self.save() return self def is_empty(self): """ 资源是否为空(针对文件夹资源) """ res_list = Resource.objects.filter(parent=self) if res_list: return False return True def remove(self): """ 删除资源 """ if self.rtype == RtypeChoice.FOLDER.value: if not self.is_empty(): raise ResourceError.REQUIRE_EMPTY_FOLDER from Base.qn_manager import qn_res_manager if self.cover and self.cover_type == CoverChoice.UPLOAD.value: qn_res_manager.delete_res(self.cover) self.cover = None self.save() if self.rtype == RtypeChoice.FILE.value: qn_res_manager.delete_res(self.dlpath) self.delete()
class UserApp(models.Model): """用户应用类""" user = models.ForeignKey( 'User.User', on_delete=models.CASCADE, ) app = models.ForeignKey( 'App.App', on_delete=models.CASCADE, ) user_app_id = models.CharField( max_length=16, verbose_name='用户在这个app下的唯一ID', unique=True, ) bind = models.BooleanField( default=False, verbose_name='用户是否绑定应用', ) last_auth_code_time = models.CharField( default=None, verbose_name='上一次申请auth_code的时间,防止被多次使用', max_length=20, ) frequent_score = models.FloatField( verbose_name='频繁访问分数,按分值排序为常用应用', default=0, ) last_score_changed_time = models.CharField( default=None, verbose_name='上一次分数变化的时间', max_length=20, ) mark = models.PositiveSmallIntegerField( verbose_name='此用户的打分,0表示没打分', default=0, ) def _readable_rebind(self): return float(self.last_auth_code_time) < self.app.field_change_time def d(self): return self.dictify('bind', 'mark', 'rebind', 'user_app_id') @classmethod def get_by_user_app(cls, user, app): try: return cls.objects.get(user=user, app=app) except Exception: raise AppError.USER_APP_NOT_FOUND @classmethod def get_by_id(cls, user_app_id, check_bind=False): try: user_app = cls.objects.get(user_app_id=user_app_id) except Exception: raise AppError.USER_APP_NOT_FOUND if check_bind and not user_app.bind: raise AppError.APP_UNBINDED return user_app @classmethod def get_unique_id(cls): while True: user_app_id = get_random_string(length=8) try: cls.get_by_id(user_app_id) except E as e: if e.eis(AppError.USER_APP_NOT_FOUND): return user_app_id @classmethod def do_bind(cls, user, app): if app.is_user_full(): raise AppError.USER_FULL premise_list = app.check_premise(user) for premise in premise_list: error = E.sid2e[premise['check']['identifier']] if not error.ok: raise error crt_timestamp = datetime.datetime.now().timestamp() try: user_app = cls.get_by_user_app(user, app) user_app.bind = True user_app.last_auth_code_time = crt_timestamp user_app.frequent_score += 1 user_app.last_score_changed_time = crt_timestamp user_app.save() except E as e: if e.eis(AppError.USER_APP_NOT_FOUND): try: user_app = cls( user=user, app=app, user_app_id=cls.get_unique_id(), bind=True, last_auth_code_time=crt_timestamp, frequent_score=1, last_score_changed_time=crt_timestamp, ) user_app.save() user_app.app.user_num += 1 user_app.app.save() except Exception as err: raise AppError.BIND_USER_APP(debug_message=err) else: raise e return JWT.encrypt(dict( user_app_id=user_app.user_app_id, type=JWType.AUTH_CODE, ctime=crt_timestamp ), replace=False, expire_second=5 * 60) @classmethod def check_bind(cls, user, app): try: user_app = cls.get_by_user_app(user, app) return user_app.bind except Exception: return False @classmethod def refresh_frequent_score(cls): from Config.models import Config crt_date = datetime.datetime.now().date() crt_time = datetime.datetime.now().timestamp() last_date = Config.get_value_by_key(CI.LAST_RE_FREQ_SCORE_DATE) last_date = datetime.datetime.strptime(last_date, '%Y-%m-%d').date() if last_date >= crt_date: raise AppError.SCORE_REFRESHED from OAuth.views import OAUTH_TOKEN_EXPIRE_TIME Config.update_value(CI.LAST_RE_FREQ_SCORE_DATE, crt_date.strftime('%Y-%m-%d')) for user_app in cls.objects.all(): if crt_time - float( user_app.last_auth_code_time) > OAUTH_TOKEN_EXPIRE_TIME + 24 * 60 * 60: if crt_time - float(user_app.last_score_changed_time) > OAUTH_TOKEN_EXPIRE_TIME: user_app.frequent_score /= 2 user_app.last_score_changed_time = crt_time user_app.save() def do_mark(self, mark): if mark < 1 or mark > 5: raise AppError.MARK original_mark = self.mark self.mark = mark self.save() mark_list = list(map(int, self.app.mark.split('-'))) if 5 >= original_mark > 0: mark_list[original_mark - 1] -= 1 mark_list[mark - 1] += 1 self.app.mark = '-'.join(map(str, mark_list)) self.app.save()
class App(models.Model): R_USER = '******' R_OWNER = 'owner' R_NONE = 'none' R_LIST = [R_USER, R_OWNER, R_NONE] name = models.CharField( verbose_name='应用名称', max_length=32, min_length=2, unique=True, ) id = models.CharField( verbose_name='应用唯一ID', max_length=32, primary_key=True, ) secret = models.CharField( verbose_name='应用密钥', max_length=32, ) redirect_uri = models.URLField( verbose_name='应用跳转URI', max_length=512, ) test_redirect_uri = models.URLField( verbose_name='测试环境下的应用跳转URI', max_length=512, default=None, ) scopes = models.ManyToManyField( 'Scope', default=None, ) premises = models.ManyToManyField( 'Premise', default=None, ) owner = models.ForeignKey( 'User.User', on_delete=models.CASCADE, db_index=True, ) field_change_time = models.FloatField( null=True, blank=True, default=0, ) desc = models.CharField( max_length=32, default=None, ) logo = models.CharField( default=None, null=True, blank=True, max_length=1024, ) mark = models.SlugField( default='0-0-0-0-0', verbose_name='1-5分评分人数', ) info = models.TextField( default=None, verbose_name='应用介绍信息', null=True, ) user_num = models.IntegerField( default=0, verbose_name='用户人数', ) create_time = models.DateTimeField( default=None, ) max_user_num = models.IntegerField( default=0, verbose_name='最多注册用户' ) @classmethod def get_by_name(cls, name): try: return cls.objects.get(name=name) except cls.DoesNotExist: raise AppError.APP_NOT_FOUND @classmethod def exist_with_name(cls, name): try: cls.objects.get(name=name) except cls.DoesNotExist: return raise AppError.EXIST_APP_NAME @classmethod def get_by_id(cls, app_id): try: return cls.objects.get(pk=app_id) except cls.DoesNotExist: raise AppError.APP_NOT_FOUND @classmethod def get_unique_app_id(cls): while True: app_id = get_random_string(length=8) try: cls.get_by_id(app_id) except E as e: if e.eis(AppError.APP_NOT_FOUND): return app_id @classmethod def create(cls, name, desc, redirect_uri, test_redirect_uri, scopes, premises, owner): cls.exist_with_name(name) try: crt_time = datetime.datetime.now() app = cls( name=name, desc=desc, id=cls.get_unique_app_id(), secret=get_random_string(length=32), redirect_uri=redirect_uri, test_redirect_uri=test_redirect_uri, owner=owner, field_change_time=datetime.datetime.now().timestamp(), info=None, create_time=crt_time, ) app.save() app.scopes.add(*scopes) app.premises.add(*premises) app.save() except Exception as err: raise AppError.CREATE_APP(debug_message=err) return app def modify_test_redirect_uri(self, test_redirect_uri): self.test_redirect_uri = test_redirect_uri self.save() def modify(self, name, desc, info, redirect_uri, scopes, premises, max_user_num): """修改应用信息""" self.name = name self.desc = desc self.info = info self.redirect_uri = redirect_uri self.max_user_num = max_user_num for scope in self.scopes.all(): self.scopes.remove(scope) self.scopes.add(*scopes) for premise in self.premises.all(): self.premises.remove(premise) self.premises.add(*premises) self.field_change_time = datetime.datetime.now().timestamp() try: self.save() except Exception as err: raise AppError.MODIFY_APP(debug_message=err) def _readable_app_name(self): return self.name def _readable_app_id(self): return self.id def _readable_app_desc(self): return self.desc def _readable_logo(self, small=True): if self.logo is None: return None from Base.qn import qn_public_manager key = "%s-small" % self.logo if small else self.logo return qn_public_manager.get_resource_url(key) def _readable_create_time(self): return self.create_time.timestamp() def _readable_app_info(self): return self.info def _readable_owner(self): return self.owner.d_base() def _readable_mark(self): return list(map(int, self.mark.split('-'))) def mark_as_list(self): return self._readable_mark() def _readable_scopes(self): scopes = self.scopes.all() return list(map(lambda s: s.d(), scopes)) def _readable_premises(self): premises = self.premises.all() return list(map(lambda p: p.d(), premises)) def d(self): return self.dictify( 'app_name', 'app_id', 'app_desc', 'app_info', 'user_num', ('logo', False), 'redirect_uri', 'create_time', 'owner', 'mark', 'scopes', 'premises', 'test_redirect_uri', 'max_user_num') def d_user(self, user): dict_ = self.d() dict_.update(dict(premises=self.check_premise(user))) return dict_ def d_base(self): return self.dictify('app_name', 'app_id', 'logo', 'app_desc', 'user_num', 'create_time') def modify_logo(self, logo): """修改应用logo""" from Base.qn import qn_public_manager if self.logo: qn_public_manager.delete_res(self.logo) self.logo = logo self.save() def is_user_full(self): if self.max_user_num == 0: return False return self.max_user_num >= self.user_num def belong(self, user): return self.owner == user def authentication(self, app_secret): return self.secret == app_secret def check_premise(self, user): premises = [] for premise in self.premises.all(): checker = Premise.get_checker(premise.name) if checker and callable(checker): try: checker(user) raise BaseError.OK except E as e: error = e else: error = PremiseCheckerError.CHECKER_NOT_FOUND p_dict = premise.d() p_dict['check'] = dict( identifier=error.identifier, msg=error.message, ) premises.append(p_dict) return premises @classmethod def list(cls): return cls.objects.all().dict(cls.d_base)
class User(models.Model): """ 用户类 根超级用户id=1 """ ROOT_ID = 1 VERIFY_STATUS_UNVERIFIED = 0 VERIFY_STATUS_UNDER_AUTO = 1 VERIFY_STATUS_UNDER_MANUAL = 2 VERIFY_STATUS_DONE = 3 VERIFY_STATUS_TUPLE = ( (VERIFY_STATUS_UNVERIFIED, '没有认证'), (VERIFY_STATUS_UNDER_AUTO, '自动认证阶段'), (VERIFY_STATUS_UNDER_MANUAL, '人工认证阶段'), (VERIFY_STATUS_DONE, '成功认证'), ) VERIFY_CHINA = 0 VERIFY_ABROAD = 1 VERIFY_TUPLE = ( (VERIFY_CHINA, '中国大陆身份证认证'), (VERIFY_ABROAD, '其他地区身份认证'), ) user_str_id = models.CharField( verbose_name='唯一随机用户ID', default=None, null=True, blank=True, max_length=32, unique=True, ) qitian = models.CharField( default=None, unique=True, max_length=20, min_length=4, ) phone = models.CharField( default=None, unique=True, max_length=20, ) password = models.CharField( max_length=32, min_length=6, ) salt = models.CharField( max_length=10, default=None, ) pwd_change_time = models.FloatField( null=True, blank=True, default=0, ) avatar = models.CharField( default=None, null=True, blank=True, max_length=1024, ) nickname = models.CharField( max_length=10, default=None, ) description = models.CharField( max_length=20, default=None, blank=True, null=True, ) qitian_modify_time = models.IntegerField( verbose_name='齐天号被修改的次数', help_text='一般只能修改一次', default=0, ) birthday = models.DateField( verbose_name='生日', default=None, null=True, ) email = models.EmailField( verbose_name='邮箱', default=None, null=True, ) verify_status = models.SmallIntegerField( verbose_name='是否通过实名认证', default=0, choices=VERIFY_STATUS_TUPLE, ) real_verify_type = models.SmallIntegerField( verbose_name='实名认证类型', default=None, null=True, ) real_name = models.CharField( verbose_name='真实姓名', default=None, max_length=32, null=True, ) male = models.NullBooleanField( verbose_name='是否为男性', default=None, null=True, ) idcard = models.CharField( verbose_name='身份证号', default=None, max_length=18, choices=VERIFY_TUPLE, null=True, ) card_image_front = models.CharField( verbose_name='身份证正面照', max_length=1024, default=None, null=True, ) card_image_back = models.CharField( verbose_name='身份证背面照', max_length=1024, default=None, null=True, ) is_dev = models.BooleanField( verbose_name='是否开发者', default=False, ) @classmethod def get_unique_id(cls): while True: user_str_id = get_random_string(length=6) try: cls.get_by_str_id(user_str_id) except E as e: if e.eis(UserError.USER_NOT_FOUND): return user_str_id @classmethod def get_unique_qitian(cls): while True: qitian_id = get_random_string(length=8) try: cls.get_by_qitian(qitian_id) except E as e: if e.eis(UserError.USER_NOT_FOUND): return qitian_id @staticmethod def _valid_qitian(qitian): """验证齐天号合法""" valid_chars = '^[A-Za-z0-9_]{4,20}$' if re.match(valid_chars, qitian) is None: raise UserError.INVALID_QITIAN @staticmethod def _valid_password(password): """验证密码合法""" valid_chars = '^[A-Za-z0-9!@#$%^&*()_+-=,.?;:]{6,16}$' if re.match(valid_chars, password) is None: raise UserError.INVALID_PASSWORD @staticmethod def _valid_birthday(birthday): """验证生日是否合法""" import datetime if birthday > datetime.datetime.now().date(): raise UserError.BIRTHDAY_FORMAT @staticmethod def hash_password(raw_password, salt=None): if not salt: salt = get_random_string(length=6) hash_password = User._hash(raw_password + salt) return salt, hash_password @classmethod def create(cls, phone, password): salt, hashed_password = User.hash_password(password) User.exist_with_phone(phone) try: user = cls( qitian=cls.get_unique_qitian(), phone=phone, password=hashed_password, salt=salt, avatar=None, nickname='', description=None, qitian_modify_time=0, user_str_id=cls.get_unique_id(), birthday=None, verify_status=cls.VERIFY_STATUS_UNVERIFIED, is_dev=False, ) user.save() except Exception as err: raise UserError.CREATE_USER(debug_message=err) return user def modify_password(self, password): self.salt, self.password = User.hash_password(password) import datetime self.pwd_change_time = datetime.datetime.now().timestamp() self.save() def change_password(self, password, old_password): """修改密码""" if self.password != User._hash(old_password + self.salt): raise UserError.PASSWORD self.salt, self.password = User.hash_password(password) import datetime self.pwd_change_time = datetime.datetime.now().timestamp() self.save() @staticmethod def _hash(s): from Base.common import md5 return md5(s) @classmethod def get_by_str_id(cls, user_str_id): try: return cls.objects.get(user_str_id=user_str_id) except cls.DoesNotExist: raise UserError.USER_NOT_FOUND @classmethod def get_by_phone(cls, phone): """根据手机号获取用户对象""" try: return cls.objects.get(phone=phone) except cls.DoesNotExist: raise UserError.USER_NOT_FOUND('手机号未注册') @classmethod def exist_with_phone(cls, phone): try: cls.objects.get(phone=phone) except cls.DoesNotExist: return raise UserError.PHONE_EXIST @classmethod def get_by_qitian(cls, qitian_id): try: return cls.objects.get(qitian=qitian_id) except cls.DoesNotExist: raise UserError.USER_NOT_FOUND('不存在的齐天号') @classmethod def exist_with_qitian(cls, qitian_id): try: cls.objects.get(qitian=qitian_id) except cls.DoesNotExist: return raise UserError.QITIAN_EXIST @classmethod def get_by_id(cls, user_id): """根据用户ID获取用户对象""" try: return cls.objects.get(pk=user_id) except cls.DoesNotExist: raise UserError.USER_NOT_FOUND def allow_qitian_modify(self): return self.qitian_modify_time == 0 def _readable_avatar(self): return self.get_avatar_url() def _readable_birthday(self): return self.birthday.strftime('%Y-%m-%d') if self.birthday else None def _readable_allow_qitian_modify(self): return int(self.allow_qitian_modify()) def d_oauth(self): return self.dictify('avatar', 'nickname', 'description') def d_base(self): return self.dictify('user_str_id', 'avatar', 'nickname', 'description') def d(self): return self.dictify('birthday', 'user_str_id', 'qitian', 'avatar', 'nickname', 'description', 'allow_qitian_modify', 'verify_status', 'verify_type', 'is_dev') @classmethod def authenticate(cls, qitian, phone, password): """验证手机号和密码是否匹配""" if qitian: user = cls.get_by_qitian(qitian) else: user = cls.get_by_phone(phone) salt, hashed_password = User.hash_password(password, user.salt) if hashed_password == user.password: return user raise UserError.PASSWORD def get_avatar_url(self, small=True): """获取用户头像地址""" if self.avatar is None: return None from Base.qn import qn_public_manager key = "%s-small" % self.avatar if small else self.avatar return qn_public_manager.get_resource_url(key) def get_card_urls(self): from Base.qn import qn_res_manager front_url = qn_res_manager.get_resource_url(self.card_image_front) \ if self.card_image_front else None back_url = qn_res_manager.get_resource_url(self.card_image_back) \ if self.card_image_back else None return dict( front=front_url, back=back_url, ) def modify_avatar(self, avatar): """修改用户头像""" if self.avatar: from Base.qn import qn_public_manager qn_public_manager.delete_res(self.avatar) self.avatar = avatar self.save() def upload_verify_front(self, card_image_front): from Base.qn import qn_res_manager if self.card_image_front: qn_res_manager.delete_res(self.card_image_front) self.card_image_front = card_image_front self.save() qn_res_manager.get_resource_url(self.card_image_front + '-small') def upload_verify_back(self, card_image_back): from Base.qn import qn_res_manager if self.card_image_back: qn_res_manager.delete_res(self.card_image_back) self.card_image_back = card_image_back self.save() qn_res_manager.get_resource_url(self.card_image_back + '-small') def modify_info(self, nickname, description, qitian, birthday): """修改用户信息""" if nickname is None: nickname = self.nickname if description is None: description = self.description if qitian is None: qitian = self.qitian if birthday is None or (self.verify_status and self.real_verify_type == User.VERIFY_CHINA): birthday = self.birthday if self.allow_qitian_modify(): if self.qitian != qitian: self.exist_with_qitian(qitian) self.qitian = qitian self.qitian_modify_time += 1 self.nickname = nickname self.description = description self.birthday = birthday self.save() def update_card_info(self, real_name, male, idcard, birthday): self.real_name = real_name self.male = male self.idcard = idcard self.birthday = birthday try: self.save() except Exception as err: return IDCardError.AUTO_VERIFY_FAILED(debug_message=err) def update_verify_status(self, status): self.verify_status = status self.save() def update_verify_type(self, verify_type): self.real_verify_type = verify_type self.save() def developer(self): self.is_dev = True self.save()