예제 #1
0
class FormIdViewSet(viewsets.ViewSet):
    '''微信 FormID 服务 错误码33xxx'''

    schema = CustomSchema(
        manual_fields={
            'POST:/formid/': [
                coreapi.Field("formid",
                              required=True,
                              location="form",
                              description='FormId 数组'),
                coreapi.Field("openid",
                              required=True,
                              location="form",
                              description='OpenID'),
            ],
        })

    def create(self, request, format=None):
        '''上传 FormId,存入数据库'''
        formid = request.data.get('formid', [])
        openid = request.data.get('openid', '')
        # 检查 openid
        try:
            record_openid = WechatBasic.objects.get(openid=openid)
        except WechatBasic.DoesNotExist:
            return Response(res_format(None, code=33001, msg='OpenId 不存在'))
        formid_count = len(formid)
        # 格式化检测
        record_list = []
        for item in formid:
            if len(item['formId']) != 32:
                continue
            record_list.append(
                FormIdGroup(openId=record_openid,
                            formId=item['formId'],
                            logFrom=item['logFrom'],
                            logTime=item['logTime'],
                            logCount=formid_count))
        # 保存
        FormIdGroup.objects.bulk_create(record_list)
        return Response(res_format({'count': len(record_list)}))
예제 #2
0
class BookManageViewSet(viewsets.ViewSet):
    '''图书管理相关服务 需认证 错误码23xxx'''
    authentication_classes = [JWTAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    schema = CustomSchema(
        manual_fields={
            'POST:/book/publish/': [
                coreapi.Field("description",
                              required=True,
                              location="form",
                              description='用户描述,不可为空,最多 300 字'),
                coreapi.Field("price",
                              required=True,
                              location="form",
                              description='出售价格'),
                coreapi.Field("images",
                              required=True,
                              location="form",
                              description='图书图片'),
                coreapi.Field("knife",
                              required=True,
                              location="form",
                              description='是否可小刀',
                              schema=coreschema.Boolean()),
                coreapi.Field("phase",
                              required=True,
                              location="form",
                              description='品相,L10/L9/L7/L5'),
                coreapi.Field("status",
                              required=True,
                              location="form",
                              description='发布状态,normal:正常;draft:草稿'),
                coreapi.Field("bookISBN",
                              required=True,
                              location="form",
                              description='图书 ISBN 码'),
                coreapi.Field("categoryL1",
                              required=True,
                              location="form",
                              description='图书类别,大类'),
                coreapi.Field("categoryL2",
                              required=True,
                              location="form",
                              description='图书类别,小类'),
            ],
            'POST:/book/status/': [
                coreapi.Field("bid",
                              required=True,
                              location="form",
                              description='图书发布信息唯一标识 BID'),
                coreapi.Field(
                    "status",
                    required=True,
                    location="form",
                    description='改变状态:normal-重新发布、delete-删除、removed-下架、sold-出售。'
                ),
            ],
            'POST:/book/collect/': [
                coreapi.Field("bid",
                              required=True,
                              location="form",
                              description='图书发布信息唯一标识 BID'),
                coreapi.Field("status",
                              required=True,
                              location="form",
                              description='收藏:normal;取消收藏:delete'),
            ],
            'GET:/book/collection/': [],
            'POST:/book/comment/': [
                coreapi.Field("bid",
                              required=True,
                              location="form",
                              description='图书发布信息唯一标识 BID'),
                coreapi.Field("toUser",
                              required=False,
                              location="form",
                              description='回复的用户的 UID'),
                coreapi.Field("comment",
                              required=True,
                              location="form",
                              description='评论内容'),
            ],
            'POST:/book/comment/delete/': [
                coreapi.Field("cid",
                              required=True,
                              location="form",
                              description='评论信息唯一标识'),
            ],
        })

    @action(methods=['post'], detail=False, url_path='publish')
    def publish(self, request, format=None):
        '''发布图书信息'''
        user = request.user
        # 获取及检查参数
        params = [
            'description', 'price', 'images', 'knife', 'phase', 'status',
            'bookISBN', 'categoryL1', 'categoryL2'
        ]
        description, price, images, knife, phase, status, bookISBN, categoryL1, categoryL2 = get_dict_values(
            request.data, params)
        if check_none(description, price, images, knife, phase, status,
                      bookISBN, categoryL1, categoryL2):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 发布状态,normal:正常;draft:草稿
        if status not in ['normal', 'draft']:
            return Response(res_format(None, code=23003, msg='发布失败,发布状态错误'))
        # 检查发布信息中是否有 bookInfo,如果有就是用户完善的信息
        # 没有就通过 ISBN 查询图书信息
        user_isbn_info = request.data.get('bookInfo', None)
        if user_isbn_info is None:
            dataSource = 'douban'
            try:
                record_isbn = ISBNInfo.objects.get(isbn=bookISBN)
                isbn_info = ISBNInfoSerializers(record_isbn).data
            except ISBNInfo.DoesNotExist:
                return Response(
                    res_format(None, code=23001, msg='发布失败,请重新扫描 ISBN 码获取信息'))
        else:
            dataSource = 'user'
            # 将空字符串设置为 None
            for key in user_isbn_info:
                if user_isbn_info[key] == '':
                    user_isbn_info[key] = None
            # 序列化数据,并校验
            isbn_info_serializer = ISBNInfoSerializers(data=user_isbn_info)
            if not isbn_info_serializer.is_valid():
                return Response(
                    res_format(None, code=23011, msg='完善的图书信息格式错误,请修改'))
            else:
                isbn_info = isbn_info_serializer.data
        # 发布信息模块
        publish_params = {
            'uid':
            user,
            'bookTitle':
            (isbn_info['title'] if isbn_info['title'] is not None else '') +
            (' : ' + isbn_info['subtitle']
             if isbn_info['subtitle'] is not None else ''),
            'bookAuthor':
            isbn_info['author'],
            'bookCover':
            isbn_info['cover'],
            'bookPress':
            isbn_info['press'],
            'bookPublishYear':
            isbn_info['publishYear'],
            'bookPrice':
            isbn_info['price'],
            'bookIntroduction':
            isbn_info['introduction'][:512]
            if isbn_info['introduction'] else None,
            'bookRatingValue':
            isbn_info['ratingValue'],
            'dataSource':
            dataSource
        }
        # 如果该图书没有标题,就设为 None
        if publish_params['bookTitle'] == '':
            publish_params['bookTitle'] = None
        publish_params.update(request.data)
        record_publish = PublishBookSerializers(data=publish_params)
        if not record_publish.is_valid():
            errors = record_publish.errors
            return Response(res_format(errors, code=23002, msg='发布信息格式错误'))
        record_publish.save()
        return Response(
            res_format({'bid': record_publish.data['bid']}, msg='发布成功'))

    @action(methods=['post'], detail=False, url_path='status')
    def status(self, request, format=None):
        '''更新图书状态,normal-重新发布、delete-删除、removed-下架'''
        user = request.user
        # 获取及检查参数
        params = ['bid', 'status']
        bid, status = get_dict_values(request.data, params)
        if check_none(bid, status):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查是否是更新合法的图书状态
        status_dict = {
            'normal': '重新发布',
            'delete': '删除',
            'sold': '出售',
            'removed': '下架'
        }
        if status not in status_dict:
            return Response(
                res_format(None, code=23006, msg='未知的切换状态 ' + status))
        try:
            # 查询当前图书状态
            record = PublishBook.objects.get(bid=bid)
            # 判断这本书是不是该用户发布的
            if record.uid != user:
                return Response(res_format(None, code=23005,
                                           msg='无法操作别人发布的图书'))
            # 检查图书发布信息的系统状态
            if record.systemStatus != 'normal':
                return Response(
                    res_format(None, code=23007, msg='该图书的系统状态异常,无法操作'))
            # 出售后的图书只可以进行删除
            if record.status == 'sold' and status != 'delete':
                return Response(
                    res_format(None, code=23019, msg='不可对已出售的图书进行其他操作'))
            # 检查无误,可以切换状态了
            record.status = status
            record.save()
            return Response(res_format(None, msg=status_dict[status] + '图书成功'))
        except PublishBook.DoesNotExist:
            return Response(res_format(None, code=23004, msg='无相关图书信息'))

    @action(methods=['post'], detail=False, url_path='collect')
    def collect(self, request, format=None):
        '''收藏/取消收藏图书'''
        user = request.user
        # 获取及检查参数
        params = ['bid', 'status']
        bid, status = get_dict_values(request.data, params)
        if check_none(bid, status):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查是否是更新合法的图书状态
        status_dict = {'normal': '收藏', 'delete': '取消收藏'}
        if status not in status_dict:
            return Response(
                res_format(None, code=23008, msg='收藏状态错误 ' + status))
        # 查询图书信息
        try:
            record_book = PublishBook.objects.get(bid=bid)
            if record_book.uid == user:
                return Response(res_format(None, code=23010,
                                           msg='无法收藏自己发布的图书'))
        except PublishBook.DoesNotExist:
            return Response(res_format(None, code=23009, msg='无相关图书信息'))
        # 检查图书收藏状态
        try:
            record = CollectBook.objects.get(uid=str(user), bid=bid)
            record.status = status
            record.save()
        except CollectBook.DoesNotExist:
            record_new = CollectBook(uid=user, bid=record_book, status=status)
            record_new.save()
        return Response(res_format(None, msg=status_dict[status] + '成功'))

    @action(detail=False, url_path='collection')
    def collection(self, request, format=None):
        '''获取我的收藏信息'''
        user = request.user
        record = CollectBook.objects.filter(uid=str(user)).filter(
            status='normal').filter(bid__status='normal').filter(
                bid__systemStatus='normal').order_by('-updateTime')
        result = CollectBookOverviewSerializers(record, many=True).data
        return Response(res_format(result))

    @action(methods=['post'], detail=False, url_path='comment')
    def comment(self, request, format=None):
        '''对图书信息发表评论'''
        user = request.user
        # 评论频率限制
        # 每分钟 3 条,每小时 30 条,每天 200 条
        now = datetime.datetime.now()
        limit_minute = now - datetime.timedelta(minutes=1)
        limit_hour = now - datetime.timedelta(hours=1)
        limit_day = now - datetime.timedelta(hours=24)
        record_limit = CommentBook.objects.filter(commentUserInfo=user)
        if len(record_limit.filter(createTime__gt=limit_minute)) >= 3:
            return Response(
                res_format('Minute Limit', code=23016, msg='留言失败,请过一会儿再来'))
        if len(record_limit.filter(createTime__gt=limit_hour)) >= 30:
            return Response(
                res_format('Hour Limit', code=23017, msg='留言失败,请过一会儿再来'))
        if len(record_limit.filter(createTime__gt=limit_day)) >= 100:
            return Response(
                res_format('Day Limit', code=23018, msg='留言失败,请过一会儿再来'))
        # 获取及检查参数
        params = ['bid', 'comment']
        bid, comment = get_dict_values(request.data, params)
        if check_none(bid, status):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查评论信息
        if len(comment) == 0:
            return Response(res_format(None, code=23012, msg='评论不能为空'))
        # 获取图书信息
        try:
            record_book = PublishBook.objects.get(bid=bid)
        except PublishBook.DoesNotExist:
            return Response(res_format(None, code=23013, msg='评论失败,无法获取图书信息'))
        # 获取回复别人评论的别人的用户信息
        to_user = request.data.get('toUser', None)
        if to_user is not None:
            try:
                record_to_user = AccountBasic.objects.get(uid=to_user)
            except AccountBasic.DoesNotExist:
                record_to_user = None
        else:
            record_to_user = None
        # 保存评论
        record = CommentBook(commentUserInfo=user,
                             toUserInfo=record_to_user,
                             bookInfo=record_book,
                             comment=comment)
        record.save()
        result = CommentBookInfoSerializers(record).data
        return Response(res_format(result))

    @action(methods=['post'], detail=False, url_path='comment/delete')
    def comment_delete(self, request, format=None):
        '''删除图书评论'''
        user = request.user
        cid = request.data.get('cid', None)
        if cid is None:
            return Response(res_format(None, code=23014, msg='删除评论失败'))
        # 查询评论
        try:
            record = CommentBook.objects.get(cid=cid, commentUserInfo=user)
        except CommentBook.DoesNotExist:
            return Response(res_format(None, code=23015, msg='删除评论失败,未找到相关评论'))
        # 删除
        record.status = 'delete'
        record.save()
        return Response(res_format(None))
예제 #3
0
class ISBNViewSet(viewsets.ViewSet):
    '''ISBN 管理相关服务 错误码22xxx'''

    authentication_classes = [JWTAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    schema = CustomSchema(
        manual_fields={
            'GET:/isbn/': [
                coreapi.Field("isbn",
                              required=True,
                              location="query",
                              description='ISBN 码'),
            ],
        })

    def list(self, request, format=None):
        '''通过 10 位或 13 位 ISBN 码识获取图书信息'''
        ip_addr = request.META.get('REMOTE_ADDR')
        ua_string = request.META.get('HTTP_UA', '')
        user = request.user
        isbn = request.query_params.get('isbn')
        # ----- log -----
        record_log = ISBNQueryLog(uid=user,
                                  searchValue=isbn,
                                  ipAddr=ip_addr,
                                  userAgent=ua_string)
        starttime = get_timestamp13()  # 记录查询开始时间
        # ----- log -----
        # 校验 ISBN 码是否合法
        if isbn is None or (len(isbn) != 10 and len(isbn) != 13):
            error_msg = '请输入正确的 ISBN 码'
            # ----- log -----
            endtime = get_timestamp13()  # 记录查询结束时间
            spend = endtime - starttime
            record_log.searchTime = spend
            record_log.result = False
            record_log.message = error_msg
            record_log.save()
            # ----- log -----
            return Response(res_format(None, code=22001, msg=error_msg))
        # 先查询数据库,如果有就返回信息
        try:
            record = ISBNInfo.objects.get(isbn=isbn)
            isbn_info_db = ISBNInfoSerializers(record)
            # ----- log -----
            endtime = get_timestamp13()  # 记录查询结束时间
            spend = endtime - starttime
            record_log.searchTime = spend
            record_log.result = True
            record_log.message = '从数据库中查询成功'
            record_log.save()
            # ----- log -----
            return Response(res_format(isbn_info_db.data))
        except ISBNInfo.DoesNotExist:
            pass
        # 数据库没有,通过 ISBN 爬取数据
        isbn_info_douban = DoubanBook().get_info(isbn)
        if isbn_info_douban['error'] is not None:
            # ----- log -----
            endtime = get_timestamp13()  # 记录查询结束时间
            spend = endtime - starttime
            record_log.searchTime = spend
            record_log.result = False
            record_log.message = '转用户完善:' + isbn_info_douban['error']
            record_log.save()
            # ----- log -----
            # fix 当前错误码 22002 为从豆瓣图书爬取图书信息时出现的错误,非系统错误
            # 可以让用户自行填写图书信息,不影响图书的发布
            if 'data' in isbn_info_douban:
                error_data = isbn_info_douban['data']
            else:
                error_data = None
            return Response(
                res_format(error_data,
                           code=22002,
                           msg=isbn_info_douban['error']))
        isbn_info = ISBNInfoSerializers(data=isbn_info_douban['data'])
        if not isbn_info.is_valid():
            errors = isbn_info.errors
            # ----- log -----
            endtime = get_timestamp13()  # 记录查询结束时间
            spend = endtime - starttime
            record_log.searchTime = spend
            record_log.result = False
            record_log.message = '获取到的图书信息格式错误,请联系管理员进行处理'
            record_log.save()
            # ----- log -----
            return Response(
                res_format(errors, code=22003, msg='获取到的图书信息格式错误,请联系管理员进行处理'))
        isbn_info.save()
        # ----- log -----
        endtime = get_timestamp13()  # 记录查询结束时间
        spend = endtime - starttime
        record_log.searchTime = spend
        record_log.result = True
        record_log.message = '从豆瓣读书中爬取成功'
        record_log.save()
        # ----- log -----
        return Response(res_format(isbn_info.data))
예제 #4
0
class BookViewSet(viewsets.ViewSet):
    '''图书管理相关服务 错误码21xxx'''

    schema = CustomSchema(
        manual_fields={
            'GET:/book/category/': [
                coreapi.Field("type",
                              required=False,
                              location="query",
                              description='数据格式,可选:tree/idpid'),
                coreapi.Field(
                    "action",
                    required=False,
                    location="query",
                    description=
                    '获取分类的用途:(默认)publish - 发布图书时选择类别;search - 通过类别进行搜索,会增加“全部”一类,表示所有二级类别'
                ),
            ],
            'GET:/book/overview/': [
                coreapi.Field("page",
                              required=False,
                              location="query",
                              description='查询页码'),
                coreapi.Field("limit",
                              required=False,
                              location="query",
                              description='每页数量'),
                coreapi.Field("orderby",
                              required=False,
                              location="query",
                              description='排序方式'),
                coreapi.Field("category",
                              required=False,
                              location="query",
                              description='图书类别,大小类之间用横杠分割,如:"计算机与网络-网页制作"'),
                coreapi.Field(
                    "search",
                    required=False,
                    location="query",
                    description='搜索关键字,可以为图书标题,用户描述,图书作者,图书出版社,图书简介'),
            ],
            'GET:/book/detail/': [
                coreapi.Field("bid",
                              required=True,
                              location="query",
                              description='图书信息唯一标识'),
            ],
            'GET:/book/search/suggest/': [
                coreapi.Field("count",
                              required=False,
                              location="query",
                              description='获取的条数,默认 5 条'),
            ],
        })

    @action(detail=False, url_path='category')
    def cateogry(self, request, format=None):
        '''获取图书分类列表'''
        tp = request.query_params.get('type')
        # 获取分类的用途:publish - 发布图书时选择类别;search - 通过类别进行搜索
        action = request.query_params.get('action', 'publish')
        record = BookCategory.objects.filter(status=True)
        category_list = BookCategorySerializers(record, many=True)
        if tp == 'tree':
            # 将数据格式化为树形数据
            result = []
            pre_dict = {}
            for item in category_list.data:
                id = item.get('id')
                pid = item.get('pid')
                name = item.get('name')
                if pid is None:
                    result.append({'id': id, 'name': name, 'children': []})
                    continue
                if pid not in pre_dict:
                    pre_dict[pid] = []
                pre_dict[pid].append({'id': id, 'name': name})
            # 按名称排序
            pinyin = Pinyin()

            def get_sort_key(elem):
                return pinyin.get_pinyin(elem['name'])

            result.sort(key=get_sort_key)
            for item in result:
                children = pre_dict[item['id']]
                children.sort(key=get_sort_key)
                if action == 'search':
                    children.insert(0, {'id': 10000, 'name': '全部'})
                item['children'] = children
        else:
            result = category_list.data

        return Response(res_format(result))

    @action(detail=False, url_path='overview')
    def overview(self, request, format=None):
        '''获取发布图书信息列表'''
        user = request.user
        # TODO:: 在分页获取的过程中,如何保证新发布的数据不会被添加到旧分页信息中,保证无重复数据
        page = int(request.query_params.get('page', '1'))
        limit = int(request.query_params.get('limit', '10'))
        orderby = request.query_params.get('orderby', '-updateTime')
        category = request.query_params.get('category', '-').split('-')
        search = request.query_params.get('search', '')
        # 查询
        record = PublishBook.objects.filter(systemStatus='normal').filter(
            status='normal').order_by(orderby)
        # 图书类别过滤
        if len(category) == 2 and category[0] != '' and category[1] != '':
            # 记录分类查询日志
            if not user.is_anonymous:
                try:
                    record_category_logger = CategoryLog(
                        uid=user,
                        categoryL1=category[0],
                        categoryL2=category[1])
                    record_category_logger.save()
                except Exception as e:
                    pass
            else:
                record_category_logger = CategoryLog(categoryL1=category[0],
                                                     categoryL2=category[1])
                record_category_logger.save()
            record = record.filter(categoryL1=category[0])
            if category[1] != '全部':
                record = record.filter(categoryL2=category[1])
        # 搜索过滤
        if search != '':
            # 记录搜索日志
            if not user.is_anonymous:
                try:
                    record_search_logger = SearchLog(uid=user, search=search)
                    record_search_logger.save()
                except Exception as e:
                    pass
            else:
                record_search_logger = SearchLog(search=search)
                record_search_logger.save()
            record = record.filter(
                Q(bookTitle__icontains=search)
                | Q(description__icontains=search)
                | Q(bookAuthor__icontains=search)
                | Q(bookPress__icontains=search)
                | Q(bookIntroduction__icontains=search)
                | Q(bookISBN__icontains=search)
                | Q(categoryL1__icontains=search)
                | Q(categoryL2__icontains=search))
        total = len(record)
        result = PublishBookOverviewSerializers(
            record[limit * (page - 1):limit * page], many=True).data

        return Response(
            res_format({
                'page': page,
                'limit': limit,
                'total': total,
                'list': result
            }))

    @action(detail=False, url_path='detail')
    def get_detail(self, request, format=None):
        '''通过 bid 获取图书详细信息'''
        user = request.user
        bid = request.query_params.get('bid', '')
        try:
            record = PublishBook.objects.get(bid=bid)
            if record.systemStatus != 'normal':
                errors = '该图书信息已被系统删除' if record.systemStatus == 'delete' else '该图书信息被举报,无法显示'
                return Response(res_format(None, code=21001, msg=errors))
            if user != record.uid and record.status != 'normal':
                status_text = {
                    'draft': '该图书还未发布',
                    'delete': '该图书已删除',
                    'removed': '该图书已下架',
                    'sold': '该图书已出售'
                }
                return Response(
                    res_format(None,
                               code=21002,
                               msg=status_text[record.status]))
            result = PublishBookSerializers(record).data
            # 获取发布者个人信息
            uid = result['uid']
            record_account = AccountBasic.objects.get(uid=uid)
            result['userinfo'] = UserInfoSerializer(record_account).data

            # 检查用户是否登录
            user = request.user
            if not user.is_anonymous:
                # 记录查询日志
                try:
                    record_logger = BookDetailLog.objects.get(uid=str(user),
                                                              bid=record.bid)
                    record_logger.count = record_logger.count + 1
                    record_logger.save()
                except BookDetailLog.DoesNotExist:
                    # 未浏览过该本书,新建一条日志
                    record_new_logger = BookDetailLog(
                        uid=user,
                        bid=record,
                    )
                    record_new_logger.save()
                except Exception as e:
                    pass
            else:
                # 未登录用户的查询信息也记录
                record_newnew_logger = BookDetailLog(bid=record, )
                record_newnew_logger.save()
            # 获取当前书籍的收藏人数
            # 检查用户是否收藏了该书
            record_collect_list = CollectBook.objects.filter(bid=bid).filter(
                status='normal')
            result['collectCount'] = len(record_collect_list)
            record_collect = record_collect_list.filter(uid=str(user))
            if not user.is_anonymous and len(record_collect) > 0:
                result['collect'] = True
            else:
                result['collect'] = False
            # 获取浏览次数
            from django.db.models.aggregates import Count, Sum
            record_view = BookDetailLog.objects.filter(bid=bid)
            total_view = record_view.aggregate(total_view=Sum("count"))
            result['viewCount'] = total_view['total_view'] if total_view[
                'total_view'] is not None else 0

            # 获取图书评论
            record_comment = CommentBook.objects.filter(
                bookInfo=record).filter(
                    status='normal').order_by('-createTime')
            result['comment'] = CommentBookInfoSerializers(record_comment,
                                                           many=True).data

            # 返回查询结果
            return Response(res_format(result))
        except PublishBook.DoesNotExist:
            return Response(res_format(None, code=21003, msg='无相关图书信息'))
        except AccountBasic.DoesNotExist:
            return Response(res_format(None, code=21004, msg='图书信息发布者帐号已注销'))

    @action(detail=False, url_path='search/suggest')
    def search_suggest(self, request, format=None):
        '''提供搜索建议词汇,默认为 5 条'''
        count = request.query_params.get('count', '5')
        count = int(count)
        # TODO:: 建立数据表,查询搜索词汇
        # TODO:: 从大家的搜索记录中查找热门词汇
        suggest = [
            'Java', '青春', '教材', '考试', 'CET', '考研', 'GRE', 'Python', '生活', '运动'
        ]
        import random
        random.shuffle(suggest)
        return Response(res_format(suggest[:count]))
예제 #5
0
class CaptchaViewSet(viewsets.ViewSet):
    '''图形验证码 错误码10xxx'''

    schema = CustomSchema(manual_fields={
        'GET:/captcha/': [
            coreapi.Field("from", required=False, location="query",
                          description='请求来源:\n[wxapp:openid]\n后续陆续增加种类'),
        ],
        'GET:/captcha/image/': [
            coreapi.Field("token", required=True,
                          location="query", description='图形验证码 token'),
        ],
        'POST:/captcha/verify/': [
            coreapi.Field("token", required=True, location="form",
                          description='图形验证码 token'),
            coreapi.Field("code", required=True, location="form",
                          description='用户输入的验证码字符串'),
        ],
    })

    def list(self, request, format=None):
        '''获取图形验证码信息 token'''
        # 获取请求信息
        ip_addr = request.META.get('REMOTE_ADDR')
        request_sourse = request.query_params.get('from')
        # 生成验证码
        ins = CaptchaUtil.generate(ip_addr, request_sourse)
        return Response(res_format({'token': ins}))

    @action(detail=False)
    def image(self, request, format=None):
        '''通过 token 获取验证码图片'''
        # 提取 token 获取验证码 code
        token = request.query_params.get('token')
        code, fontSize, fontColor, noise = CaptchaUtil.get_code(token)
        # 将图形验证码写入 IO 流
        stream = io.BytesIO()
        inst = CaptchaImageGenerate(
            code=code, fontSize=fontSize, fontColor=fontColor, noise=noise)
        img = inst.create()
        img.save(stream, "PNG")
        # 返回图片
        return HttpResponse(stream.getvalue(), content_type='image/png')

    @action(methods=['post'], detail=False)
    def verify(self, request, format=None):
        '''校验图形验证码和用户输入的是否一致,通过则返回认证标识,否则返回新验证码 token'''
        # 获取及检查参数
        params = ['token', 'code']
        token, code = get_dict_values(request.data, params)
        if check_none(token, code):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 验证
        result = CaptchaUtil.verify_by_code(token, code)
        # 验证失败则将原验证码弃用,并返回新的验证码 token
        if not result:
            ip_addr = request.META.get('REMOTE_ADDR')
            request_sourse = request.query_params.get('from')
            refresh_token = CaptchaUtil.generate(ip_addr, request_sourse)
            return Response(res_format({'authenticate': result, 'refresh_token': refresh_token}))
        return Response(res_format({'token': token, 'authenticate': result}))
예제 #6
0
class GoodsManageViewSet(viewsets.ViewSet):
    '''二手物品相关服务,需认证 错误码42xxx'''
    authentication_classes = [JWTAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    schema = CustomSchema(
        manual_fields={
            'POST:/goods/publish/': [
                coreapi.Field("title",
                              required=True,
                              location="form",
                              description='标题 品类品牌型号都是买家喜欢搜索的'),
                coreapi.Field("description",
                              required=True,
                              location="form",
                              description='描述宝贝的转手原因、入手渠道和使用感受'),
                coreapi.Field(
                    "images",
                    required=True,
                    location="form",
                    description='照片,每一项以 goodspublish 开头,多项之间以英文分号分隔'),
                coreapi.Field("isNew",
                              required=True,
                              location="form",
                              description='是否 全新宝贝',
                              schema=coreschema.Boolean()),
                coreapi.Field("knife",
                              required=True,
                              location="form",
                              description='是否 可小刀',
                              schema=coreschema.Boolean()),
                coreapi.Field(
                    "price", required=True, location="form", description='价格'),
                coreapi.Field("category",
                              required=True,
                              location="form",
                              description='分类,只有一级分类'),
                coreapi.Field("tags",
                              required=True,
                              location="form",
                              description='标签,以井号(#)分隔'),
            ],
            'POST:/goods/status/': [
                coreapi.Field("gid",
                              required=True,
                              location="form",
                              description='二手物品发布信息唯一标识 GID'),
                coreapi.Field(
                    "status",
                    required=True,
                    location="form",
                    description='改变状态:normal-重新发布、delete-删除、removed-下架、sold-出售。'
                ),
            ],
            'POST:/goods/collect/': [
                coreapi.Field("gid",
                              required=True,
                              location="form",
                              description='二手物品发布信息唯一标识 GID'),
                coreapi.Field("status",
                              required=True,
                              location="form",
                              description='收藏:normal;取消收藏:delete'),
            ],
            'GET:/goods/collection/': [],
            'POST:/goods/comment/': [
                coreapi.Field("gid",
                              required=True,
                              location="form",
                              description='二手物品发布信息唯一标识 GID'),
                coreapi.Field("toUser",
                              required=False,
                              location="form",
                              description='回复的用户的 UID'),
                coreapi.Field("comment",
                              required=True,
                              location="form",
                              description='评论内容'),
            ],
            'POST:/goods/comment/delete/': [
                coreapi.Field("cid",
                              required=True,
                              location="form",
                              description='评论信息唯一标识'),
            ],
        })

    @action(methods=['post'], detail=False)
    def publish(self, request, format=None):
        '''发布二手物品信息'''
        user = request.user
        # 获取及检查参数
        params = [
            'title', 'description', 'images', 'isNew', 'knife', 'price',
            'category', 'tags'
        ]
        title, description, images, isNew, knife, price, category, tags = get_dict_values(
            request.data, params)
        if check_none(title, description, images, isNew, knife, price,
                      category, tags):
            raise ParseError('缺少参数:' + ', '.join(params))
        # status 默认为 normal

        # 检查标签,将新标签存入数据库,旧标签使用次数加一
        tags_arr = tags.split('#')
        for tag in tags_arr:
            try:
                record_tag = GoodsTags.objects.get(name=tag)
                record_tag.using = record_tag.using + 1
                record_tag.save()
            except GoodsTags.DoesNotExist:
                record_tag_new = GoodsTags(name=tag,
                                           types='diy',
                                           using=1,
                                           createUser=user)
                record_tag_new.save()
        # 将分类标签使用次数加一
        try:
            record_category = GoodsTags.objects.get(name=category)
            record_category.using = record_category.using + 1
            record_category.save()
        except GoodsTags.DoesNotExist:
            pass

        # 反序列化数据
        publish_params = {
            'uid': user,
            'title': title,
            'description': description,
            'images': images,
            'isNew': isNew,
            'knife': knife,
            'price': price,
            'category': category,
            'tags': tags,
        }
        record_publish = PublishGoodsSerializers(data=publish_params)
        if not record_publish.is_valid():
            errors = record_publish.errors
            return Response(res_format(errors, code=42001, msg='发布信息格式错误'))
        record_publish.save()
        return Response(
            res_format({'gid': record_publish.data['gid']}, msg='发布成功'))

    @action(methods=['post'], detail=False)
    def status(self, request, format=None):
        '''更改二手物品信息发布状态'''
        user = request.user
        # 获取及检查参数
        params = ['gid', 'status']
        gid, status = get_dict_values(request.data, params)
        if check_none(gid, status):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查是否是更新合法的图书状态
        status_dict = {
            'normal': '重新发布',
            'delete': '删除',
            'sold': '出售',
            'removed': '下架'
        }
        if status not in status_dict:
            return Response(
                res_format(None, code=42002, msg='未知的切换状态 ' + status))
        try:
            # 查询当前状态
            record = PublishGoods.objects.get(gid=gid)
            # 判断这本书是不是该用户发布的
            if record.uid != user:
                return Response(res_format(None, code=42003,
                                           msg='无法操作别人发布的物品'))
            # 检查发布信息的系统状态
            if record.systemStatus != 'normal':
                return Response(
                    res_format(None, code=42004, msg='该物品的系统状态异常,无法操作'))
            # 出售后只可以进行删除
            if record.status == 'sold' and status != 'delete':
                return Response(
                    res_format(None, code=42005, msg='不可对已出售的物品进行其他操作'))
            # 检查无误,可以切换状态了
            record.status = status
            record.save()
            return Response(res_format(None, msg=status_dict[status] + '物品成功'))
        except PublishGoods.DoesNotExist:
            return Response(res_format(None, code=42006, msg='无相关物品信息'))

    @action(methods=['post'], detail=False)
    def collect(self, request, format=None):
        '''收藏二手物品信息'''
        user = request.user
        # 获取及检查参数
        params = ['gid', 'status']
        gid, status = get_dict_values(request.data, params)
        if check_none(gid, status):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查是否是更新合法的图书状态
        status_dict = {'normal': '收藏', 'delete': '取消收藏'}
        if status not in status_dict:
            return Response(
                res_format(None, code=42006, msg='收藏状态错误 ' + status))
        # 查询信息
        try:
            record_goods = PublishGoods.objects.get(gid=gid)
            if record_goods.uid == user:
                return Response(res_format(None, code=42008,
                                           msg='无法收藏自己发布的物品'))
        except PublishGoods.DoesNotExist:
            return Response(res_format(None, code=42009, msg='无相关物品信息'))
        # 检查收藏状态
        try:
            record = CollectGoods.objects.get(uid=str(user), gid=gid)
            record.status = status
            record.save()
        except CollectGoods.DoesNotExist:
            record_new = CollectGoods(uid=user,
                                      gid=record_goods,
                                      status=status)
            record_new.save()
        return Response(res_format(None, msg=status_dict[status] + '成功'))

    @action(detail=False)
    def collection(self, request, format=None):
        '''查询已收藏的二手物品信息'''
        user = request.user
        record = CollectGoods.objects.filter(uid=str(user)).filter(
            status='normal').filter(gid__status='normal').filter(
                gid__systemStatus='normal').order_by('-updateTime')
        result = CollectGoodsSerializers(record, many=True).data
        return Response(res_format(result))

    @action(methods=['post'], detail=False)
    def comment(self, request, format=None):
        '''给二手物品信息留言'''
        user = request.user
        # 评论频率限制
        # 每分钟 3 条,每小时 30 条,每天 200 条
        now = datetime.datetime.now()
        limit_minute = now - datetime.timedelta(minutes=1)
        limit_hour = now - datetime.timedelta(hours=1)
        limit_day = now - datetime.timedelta(hours=24)
        record_limit = CommentGoods.objects.filter(commentUserInfo=user)
        if len(record_limit.filter(createTime__gt=limit_minute)) >= 3:
            return Response(
                res_format('Minute Limit', code=42010, msg='留言失败,请过一会儿再来'))
        if len(record_limit.filter(createTime__gt=limit_hour)) >= 30:
            return Response(
                res_format('Hour Limit', code=42011, msg='留言失败,请过一会儿再来'))
        if len(record_limit.filter(createTime__gt=limit_day)) >= 100:
            return Response(
                res_format('Day Limit', code=42012, msg='留言失败,请过一会儿再来'))
        # 获取及检查参数
        params = ['gid', 'comment']
        gid, comment = get_dict_values(request.data, params)
        if check_none(gid, status):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查评论信息
        if len(comment) == 0:
            return Response(res_format(None, code=42013, msg='评论不能为空'))
        # 获取信息
        try:
            record_goods = PublishGoods.objects.get(gid=gid)
        except PublishGoods.DoesNotExist:
            return Response(res_format(None, code=42014, msg='评论失败,无法获取物品信息'))
        # 获取回复别人评论的别人的用户信息
        to_user = request.data.get('toUser', None)
        if to_user is not None:
            try:
                record_to_user = AccountBasic.objects.get(uid=to_user)
            except AccountBasic.DoesNotExist:
                record_to_user = None
        else:
            record_to_user = None
        # 保存评论
        record = CommentGoods(commentUserInfo=user,
                              toUserInfo=record_to_user,
                              goodsInfo=record_goods,
                              comment=comment)
        record.save()
        result = CommentGoodsSerializers(record).data
        return Response(res_format(result))

    @action(methods=['post'], detail=False, url_path='comment/delete')
    def comment_delete(self, request, format=None):
        '''删除留言'''
        user = request.user
        cid = request.data.get('cid', None)
        if cid is None:
            return Response(res_format(None, code=42015, msg='删除评论失败'))
        # 查询评论
        try:
            record = CommentGoods.objects.get(cid=cid, commentUserInfo=user)
        except CommentGoods.DoesNotExist:
            return Response(res_format(None, code=42016, msg='删除评论失败,未找到相关评论'))
        # 删除
        record.status = 'delete'
        record.save()
        return Response(res_format(None))
예제 #7
0
class GoodsViewSet(viewsets.ViewSet):
    '''二手物品相关服务 错误码41xxx'''
    schema = CustomSchema(
        manual_fields={
            'GET:/goods/detail/': [
                coreapi.Field("gid",
                              required=True,
                              location="query",
                              description='二手物品发布信息唯一标识 GID'),
            ],
            'GET:/goods/overview/': [
                coreapi.Field("page",
                              required=False,
                              location="query",
                              description='查询页码'),
                coreapi.Field("limit",
                              required=False,
                              location="query",
                              description='每页数量'),
                coreapi.Field("orderby",
                              required=False,
                              location="query",
                              description='排序方式'),
                coreapi.Field("category",
                              required=False,
                              location="query",
                              description='类别'),
                coreapi.Field("tags",
                              required=False,
                              location="query",
                              description='标签,以井号(#)分隔'),
                coreapi.Field("search",
                              required=False,
                              location="query",
                              description='搜索关键字,可以为标题、描述、类别、标签'),
            ],
            'GET:/goods/tags/': [
                coreapi.Field("page",
                              required=False,
                              location="query",
                              description='查询页码'),
                coreapi.Field("limit",
                              required=False,
                              location="query",
                              description='每页数量'),
                coreapi.Field("types",
                              required=False,
                              location="query",
                              description='标签类型 normal/color/category'),
                coreapi.Field("search",
                              required=False,
                              location="query",
                              description='搜索关键字'),
            ],
            'GET:/goods/twt/': [
                coreapi.Field("username",
                              required=True,
                              location="query",
                              description='天外天账号'),
                coreapi.Field("password",
                              required=True,
                              location="query",
                              description='密码'),
            ],
        })

    @action(detail=False, url_path="detail")
    def get_detail(self, request, format=None):
        '''获取二手物品详细信息'''
        user = request.user
        gid = request.query_params.get('gid', '')
        try:
            record = PublishGoods.objects.get(gid=gid)
            if record.systemStatus != 'normal':
                errors = '该二手物品信息已被系统删除' if record.systemStatus == 'delete' else '该二手物品信息被举报,无法显示'
                return Response(res_format(None, code=41001, msg=errors))
            if user != record.uid and record.status != 'normal':
                status_text = {
                    'delete': '该二手物品已删除',
                    'removed': '该二手物品已下架',
                    'sold': '该二手物品已出售'
                }
                return Response(
                    res_format(None,
                               code=41002,
                               msg=status_text[record.status]))
            result = PublishGoodsSerializers(record).data
            # 获取发布者个人信息
            uid = result['uid']
            record_account = AccountBasic.objects.get(uid=uid)
            result['userinfo'] = UserInfoSerializer(record_account).data

            # 检查用户是否登录
            user = request.user
            if not user.is_anonymous:
                # 记录查询日志
                try:
                    record_logger = GoodsDetailLog.objects.get(uid=str(user),
                                                               gid=record.gid)
                    record_logger.count = record_logger.count + 1
                    record_logger.save()
                except GoodsDetailLog.DoesNotExist:
                    # 未浏览过该二手物品,新建一条日志
                    record_new_logger = GoodsDetailLog(
                        uid=user,
                        gid=record,
                    )
                    record_new_logger.save()
                except Exception as e:
                    pass
            else:
                # 未登录用户的查询信息也记录
                record_newnew_logger = GoodsDetailLog(gid=record, )
                record_newnew_logger.save()
            # 获取当前二手物品的收藏人数
            # 检查用户是否收藏了该二手物品
            record_collect_list = CollectGoods.objects.filter(gid=gid).filter(
                status='normal')
            result['collectCount'] = len(record_collect_list)
            record_collect = record_collect_list.filter(uid=str(user))
            if not user.is_anonymous and len(record_collect) > 0:
                result['collect'] = True
            else:
                result['collect'] = False
            # 获取浏览次数
            from django.db.models.aggregates import Count, Sum
            record_view = GoodsDetailLog.objects.filter(gid=gid)
            total_view = record_view.aggregate(total_view=Sum("count"))
            result['viewCount'] = total_view['total_view'] if total_view[
                'total_view'] is not None else 0
            # 获取留言信息
            record_comment = CommentGoods.objects.filter(
                goodsInfo=record).filter(
                    status='normal').order_by('-createTime')
            result['comment'] = CommentGoodsSerializers(record_comment,
                                                        many=True).data

            # 返回查询结果
            return Response(res_format(result))
        except PublishGoods.DoesNotExist:
            return Response(res_format(None, code=41003, msg='无相关二手物品信息'))
        except AccountBasic.DoesNotExist:
            return Response(res_format(None, code=41004, msg='二手物品信息发布者帐号已注销'))

    @action(detail=False)
    def overview(self, request, format=None):
        '''二手物品查询接口'''
        user = request.user
        # TODO:: 在分页获取的过程中,如何保证新发布的数据不会被添加到旧分页信息中,保证无重复数据
        page = int(request.query_params.get('page', '1'))
        limit = int(request.query_params.get('limit', '10'))
        orderby = request.query_params.get('orderby', '-updateTime')
        category = request.query_params.get('category', '')
        tags = request.query_params.get('tags', '')
        search = request.query_params.get('search', '')
        # 查询
        record = PublishGoods.objects.filter(systemStatus='normal').filter(
            status='normal').order_by(orderby)
        # 分类过滤
        if len(category) != 0:
            record = record.filter(category=category)
            # 记录分类查询日志
            if not user.is_anonymous:
                try:
                    record_category_logger = GoodsCategoryLog(
                        uid=user, category=category)
                    record_category_logger.save()
                except Exception as e:
                    pass
            else:
                record_category_logger = GoodsCategoryLog(category=category)
                record_category_logger.save()
        # 标签过滤
        if len(tags) != 0:
            tags_arr = tags.split('#')
            for tag in tags_arr:
                record = record.filter(tags__icontains=tag)
            # 记录标签查询日志
            if not user.is_anonymous:
                try:
                    record_tags_logger = GoodsTagsLog(uid=user, tags=tags)
                    record_tags_logger.save()
                except Exception as e:
                    pass
            else:
                record_tags_logger = GoodsTagsLog(tags=tags)
                record_tags_logger.save()
        # 搜索过滤
        if len(search) != 0:
            record = record.filter(
                Q(title__icontains=search)
                | Q(description__icontains=search)
                | Q(category__icontains=search)
                | Q(tags__icontains=search))
            # 记录搜索日志
            if not user.is_anonymous:
                try:
                    record_search_logger = GoodsSearchLog(uid=user,
                                                          search=search)
                    record_search_logger.save()
                except Exception as e:
                    pass
            else:
                record_search_logger = GoodsSearchLog(search=search)
                record_search_logger.save()
        # 分页过滤
        total = len(record)
        result = PublishGoodsOverviewSerializers(
            record[limit * (page - 1):limit * page], many=True).data

        return Response(
            res_format({
                'page': page,
                'limit': limit,
                'total': total,
                'list': result
            }))

    @action(detail=False)
    def tags(self, request, format=None):
        '''查询二手物品标签信息'''
        user = request.user
        page = int(request.query_params.get('page', -1))
        limit = int(request.query_params.get('limit', -1))
        types = request.query_params.get('types', '')  # normal/color/category
        search = request.query_params.get('search', '')
        # 查询
        if types == 'category':
            # 查询分类信息
            record = GoodsTags.objects.filter(
                types='category').order_by('-using')
            result = GoodsTagsSerializers(record, many=True).data
            return Response(res_format({'category': result}))
        elif types == 'color':
            # 查询颜色标签信息
            record = GoodsTags.objects.filter(types='color').order_by('-using')
            result = GoodsTagsSerializers(record, many=True).data
            return Response(res_format({'category': result}))
        else:
            # 查询标签信息
            record = GoodsTags.objects.filter(
                Q(types='normal') | Q(types='diy')).order_by('-using')
            # 搜索
            if len(search) != 0:
                record = record.filter(name__icontains=search)
                # 记录标签查询日志
                if not user.is_anonymous:
                    try:
                        record_tags_logger = GoodsTagsLog(uid=user,
                                                          tags=search)
                        record_tags_logger.save()
                    except Exception as e:
                        pass
                else:
                    record_tags_logger = GoodsTagsLog(tags=search)
                    record_tags_logger.save()
            # 如果存在分页信息,则分页过滤
            if page != -1 and limit != -1:
                record = record[limit * (page - 1):limit * page]
            # 序列化数据,并返回
            result = GoodsTagsSerializers(record, many=True).data
            return Response(res_format({'tags': result}))

    @action(detail=False)
    def twt(self, request, format=None):
        '''获取天外天商城的发布信息'''
        user = request.user
        username = request.query_params.get('username', None)
        password = request.query_params.get('password', None)
        if username is None or password is None:
            return Response(res_format(None, code=41005, msg='请输入天外天账号和密码'))
        # 获取数据
        from utils.twtmall import TwtMallCrawler
        ins = TwtMallCrawler(username, password)
        result = ins.crawler()
        if result['code'] != 0:
            # 登录失败,存日志
            record = TwtLog(uid=user if not user.is_anonymous else None,
                            username=username,
                            password=password,
                            login=False,
                            userinfo=result['msg'],
                            mallinfo=None)
            record.save()
            return Response(res_format(None, code=41006, msg=result['msg']))
        else:
            # 成功
            record = TwtLog(uid=user if not user.is_anonymous else None,
                            username=username,
                            password=password,
                            login=True,
                            userinfo=json.dumps(result['data']['userInfo']),
                            mallinfo=json.dumps(
                                result['data']['twtMallUserInfo']))
            record.save()
            datas = {
                'userInfo': result['data']['twtMallUserInfo'],
                'publishInfo': result['data']['publishInfo']
            }
            return Response(res_format(datas))
예제 #8
0
class SystemNoticeViewSet(viewsets.ViewSet):
    '''系统通知相关服务 错误码31xxx'''

    authentication_classes = [JWTAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    schema = CustomSchema(
        manual_fields={
            'GET:/system/notice/': [
                coreapi.Field("start",
                              required=False,
                              location="query",
                              description='数据起始条数,从 0 开始'),
                coreapi.Field("limit",
                              required=False,
                              location="query",
                              description='每页数量'),
            ],
            'POST:/system/notice/dodelete/': [
                coreapi.Field("nidlist",
                              required=True,
                              location="form",
                              description='通知唯一标识列表,以逗号(,)分隔'),
            ],
            'POST:/system/notice/doread/': [],
        })

    def list(self, request, format=None):
        '''获取通知列表'''
        # 获取查询参数
        user = request.user
        start = int(request.query_params.get('start', '0'))
        limit = int(request.query_params.get('limit', '10'))
        # 查询所有通知,包括已读和未读
        record = SystemNotice.objects.filter(
            uid=user).filter(Q(status='unread')
                             | Q(status='read')).order_by('-createTime')
        # 计算未读消息数、消息总数
        unread_count = 0
        total_count = 0
        for item in record:
            total_count += 1
            if item.status == 'unread':
                unread_count += 1
        # 截取结果长度
        result = SystemNoticeSerializer(record,
                                        many=True).data[start:start + limit]
        result.reverse()
        return Response(
            res_format({
                'start': start,
                'limit': limit,
                'unread': unread_count,
                'total': total_count,
                'list': result,
            }))

    @action(methods=['post'], detail=False, url_path='dodelete')
    def dodelete(self, request, format=None):
        '''删除通知'''
        user = request.user
        nidlist = request.data.get('nidlist', None)
        if nidlist is None:
            return Response(res_format({'deleteCount': 0}))
        nidlist = nidlist.split(',')
        delete_count = 0
        for nid in nidlist:
            try:
                record = SystemNotice.objects.get(uid=user, nid=nid)
                record.status = 'delete'
                record.save()
                delete_count += 1
            except SystemNotice.DoesNotExist:
                pass
        return Response(res_format({'deleteCount': delete_count}))

    @action(methods=['post'], detail=False, url_path='doread')
    def doread(self, request, format=None):
        '''已读通知'''
        user = request.user
        record = SystemNotice.objects.filter(uid=user).filter(status='unread')
        update_count = record.update(status='read',
                                     updateTime=datetime.datetime.now())
        return Response(res_format({'readCount': update_count}))
예제 #9
0
class CampusCertViewSet(viewsets.ViewSet):
    '''校园认证服务 错误码32xxx'''

    authentication_classes = [JWTAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    schema = CustomSchema(
        manual_fields={
            'POST:/campus/cert/': [
                coreapi.Field("username",
                              required=True,
                              location="form",
                              description='图书馆账号,学号'),
                coreapi.Field("password",
                              required=True,
                              location="form",
                              description='图书馆密码'),
            ],
        })

    def create(self, request, format=None):
        '''进行校园认证'''
        user = request.user
        # 用户名密码
        username = request.data.get('username', '')
        password = request.data.get('password', '')
        password_b64 = str(base64.b64encode(password.encode('utf8')))
        # 开始认证
        cert = CampusCertLibrary(username, password)
        result = cert.get_cert()
        if result is None:
            # 记录日志
            record_log = CampusCertLog(username=username,
                                       password=password_b64,
                                       status='fail',
                                       uid=user)
            record_log.save()
            return Response(
                res_format(None, code=32001, msg='校园认证失败,请检查账号密码是否正确'))
        # 认证成功,创建或更新信息
        try:
            record = CampusCertBasic.objects.get(libCardID=username)
            # 更新
            serializer = CampusCertBasicSerializer(record,
                                                   data=result,
                                                   partial=True)
        except CampusCertBasic.DoesNotExist:
            # 新建
            serializer = CampusCertBasicSerializer(data=result)

        if not serializer.is_valid():
            # 记录日志
            record_log = CampusCertLog(username=username,
                                       password=password_b64,
                                       status='fail',
                                       uid=user)
            record_log.save()
            return Response(res_format(None, code=32002, msg='校园认证失败,系统错误'))
        record_cert = serializer.save()
        # 记录日志
        record_log = CampusCertLog(username=username,
                                   password=password_b64,
                                   status='success',
                                   uid=user)
        record_log.save()
        # 将认证信息保存在用户表
        user.campusCert = record_cert
        # 只更新 campusCert 字段,用于触发 AccountBasic 的更细事件
        user.save(update_fields=['campusCert'])

        # 获取最新用户信息并返回
        userinfo = AccountBasicSerializer(user)
        return Response(res_format(userinfo.data))
예제 #10
0
class WechatNoAuthViewSet(viewsets.ViewSet):
    '''微信相关服务 错误码13xxx'''
    schema = CustomSchema(
        manual_fields={
            'GET:/wechat/': [],
            'GET:/wechat/setting/': [],
            'GET:/wechat/issue/': [
                coreapi.Field("iid",
                              required=False,
                              location="query",
                              description='具体问题 ID'),
            ],
        })

    def list(self, request, format=None):
        '''获取微信相关设置,即将废弃'''
        record = WechatSetting.objects.all().order_by('createTime')
        result = WechaSettingSerializer(record, many=True).data
        return Response(res_format(result))

    @action(detail=False)
    def setting(self, request, format=None):
        '''获取微信相关设置,新版'''
        # 获取微信设置
        record = WechatSetting.objects.all().order_by('createTime')
        settings = WechaSettingSerializer(record, many=True).data
        # 获取首页入口列表
        record_e = WechatEntrance.objects.filter(
            status='normal').order_by('order')
        entrance = WechatEntranceSerializer(record_e, many=True).data
        # 获取首页 banner 信息
        record_b = WechatBanner.objects.filter(
            status='normal').order_by('-order')
        swiper = WechatBannerSerializer(record_b, many=True).data
        return Response(
            res_format({
                'settings': settings,
                'entrance': entrance,
                'swiper': swiper
            }))

    @action(detail=False)
    def issue(self, request, format=None):
        '''获取常见问题列表或详情'''
        iid = request.query_params.get('iid', None)
        if iid is not None:
            # 获取单个问题信息
            try:
                record = IssueBasic.objects.get(iid=iid)
                if record.status == 'delete':
                    return Response(
                        res_format(None, code=13001, msg='相关问题已被删除'))
                # 浏览次数+1
                record.view = record.view + 1
                record.save()
                result = IssueBasicDetailSerializer(record).data
            except IssueBasic.DoesNotExist:
                return Response(res_format(None, code=13002, msg='未获取到相关问题'))
        else:
            # 获取问题列表,正常和置顶
            record_normal = IssueBasic.objects.filter(
                status='normal').order_by('-view', '-createTime')
            record_topping = IssueBasic.objects.filter(
                status='topping').order_by('-view', '-createTime')
            result_normal = IssueBasicListSerializer(record_normal,
                                                     many=True).data
            result_topping = IssueBasicListSerializer(record_topping,
                                                      many=True).data
            result = result_topping + result_normal
        return Response(res_format(result))
예제 #11
0
class AccountViewSet(viewsets.ViewSet):
    '''用户账号 错误码12xxx'''

    authentication_classes = [JWTAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    schema = CustomSchema(
        manual_fields={
            'GET:/account/': [
                coreapi.Field("openid",
                              required=False,
                              location="query",
                              description='微信小程序获取到的 OpenID'),
            ],
            'GET:/account/oss/token/': [
                coreapi.Field("action",
                              required=True,
                              location="query",
                              description='操作类型,可选:avatar/bookpublish'),
                coreapi.Field("amount",
                              required=False,
                              location="query",
                              description='获取 Token 的数量,默认为 1'),
            ],
            'POST:/account/update/userinfo/': [
                coreapi.Field("nickname",
                              required=False,
                              location="form",
                              description='昵称,2-10 位'),
                coreapi.Field("avatar",
                              required=False,
                              location="form",
                              description='头像,格式为:avatar/xxxxxxxx...'),
                coreapi.Field("gender",
                              required=False,
                              location="form",
                              description='性别,0 - 保密;1 - 男;2 - 女'),
                coreapi.Field("birthday",
                              required=False,
                              location="form",
                              description='生日,格式为日期,2019-01-01'),
                coreapi.Field("introduction",
                              required=False,
                              location="form",
                              description='个人介绍,100 字以内'),
                coreapi.Field("position",
                              required=False,
                              location="form",
                              description='位置,20 字以内'),
                coreapi.Field("university",
                              required=False,
                              location="form",
                              description='大学,20 字以内'),
                coreapi.Field("campus",
                              required=False,
                              location="form",
                              description='校区,北洋园校区/卫津路校区'),
            ],
            'POST:/account/update/password/': [
                coreapi.Field("password_old",
                              required=True,
                              location="form",
                              description='旧密码,8-20 位'),
                coreapi.Field("password_new",
                              required=True,
                              location="form",
                              description='新密码,8-20 位'),
            ],
            'POST:/account/update/username/': [
                coreapi.Field("username",
                              required=True,
                              location="form",
                              description='用户名,6-16位,数字、大小写字母、下划线'),
            ],
            'POST:/account/update/mobile/': [
                coreapi.Field("mobile",
                              required=True,
                              location="form",
                              description='新手机号'),
                coreapi.Field("code",
                              required=True,
                              location="form",
                              description='短信验证码'),
                coreapi.Field("token",
                              required=True,
                              location="form",
                              description='短信验证码 token'),
            ],
            'GET:/account/history/': [],
            'GET:/account/history/views/': [
                coreapi.Field("page",
                              required=False,
                              location="query",
                              description='查询页码'),
                coreapi.Field("limit",
                              required=False,
                              location="query",
                              description='每页数量'),
            ],
            'GET:/account/collection/': [],
            'GET:/account/acode/userpage/': [],
            'GET:/account/acode/background/': [],
        })

    def list(self, request, format=None):
        '''获取用户个人信息'''
        # 绑定 OpenID
        openid = request.query_params.get('openid', None)
        if openid:
            try:
                record = WechatBasic.objects.get(openid=openid)
                record.uid = request.user
                record.save()
            except WechatBasic.DoesNotExist:
                pass
        userinfo = AccountBasicSerializer(request.user).data
        return Response(res_format(userinfo))

    @action(methods=['post'], detail=False, url_path='update/userinfo')
    def update_userinfo(self, request, format=None):
        '''更新个人信息,包括:昵称、头像、性别、生日、个人介绍、位置、大学、校区'''
        # 首先过滤空值
        data = request.data
        # 序列化数据,判断是否合法
        userinfo = AccountBasicSerializer(request.user,
                                          data=request.data,
                                          partial=True)
        if not userinfo.is_valid():
            # 数据不合法
            errors = userinfo.errors
            return Response(res_format(errors, code=12001, msg='更新数据不合法'))
        # 数据检查通过,保存数据
        userinfo.save()
        # 返回所有个人信息
        return Response(res_format(userinfo.data))

    @action(methods=['post'], detail=False, url_path='update/password')
    def update_password(self, request, format=None):
        '''更新密码'''
        # 获取及检查参数
        params = ['password_old', 'password_new']
        password_old, password_new = get_dict_values(request.data, params)
        if check_none(password_old, password_new):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查新密码的格式是否正确
        if not check_password(password_new):
            return Response(res_format(None, code=12002, msg='新密码长度须为 8-20 位'))
        # 检查旧密码是否匹配
        record = request.user
        if record.password != encrypt_md5(password_old):
            return Response(res_format(None, code=12003, msg='旧密码错误'))
        # 更新密码
        record.password = encrypt_md5(password_new)
        # 更新 account tokenTime 使之前的登录状态失效
        record.refresh_token()
        record.save()
        # 修改密码成功通知
        NoticeTpl.change_password_success(record)
        return Response(res_format(None))

    @action(methods=['post'], detail=False, url_path='update/username')
    def update_username(self, request, format=None):
        '''更新用户名'''
        # 获取及检查参数
        params = ['username']
        username = get_dict_values(request.data, params)
        if check_none(username):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查用户名格式是否正确
        if not check_username(username):
            return Response(
                res_format(None,
                           code=12004,
                           msg='用户名为 6-16 位,且只能包含数字、大小写字母和下划线'))
        # 检查是否是第一次更换用户名
        record = request.user
        if record.username.find('byid_') != 0:
            # 修改过一次,不可以再修改
            return Response(res_format(None, code=12005,
                                       msg='无法修改,用户名只可以修改一次'))
        # 可以修改了
        record.username = username
        record.save()
        return Response(res_format(None))

    @action(methods=['post'], detail=False, url_path='update/mobile')
    def update_mobile(self, request, format=None):
        '''换绑手机号'''
        # 获取及检查参数
        params = ['mobile', 'code', 'token']
        mobile, code, token = get_dict_values(request.data, params)
        if check_none(mobile, code, token):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查手机号是否未注册
        try:
            record = AccountBasic.objects.get(mobile=mobile)
        except AccountBasic.DoesNotExist:
            record = None
        if record is not None:
            return Response(res_format(None, code=12006, msg='该手机号已注册'))
        # 检查短信验证码
        if not Sender().verify(token, mobile, code, 'replace'):
            return Response(res_format(None, code=12007, msg='短信验证码不正确'))
        # 更换手机号
        record = request.user
        record.mobile = mobile
        record.save()
        return Response(res_format({'mobile': mobile}))

    @action(detail=False, url_path='oss/token')
    def oss_token(self, request, format=None):
        '''使用 action 获取七牛云上传 token'''
        # 获取及检查参数
        params = ['action']
        action = get_dict_values(request.query_params, params)
        if check_none(action):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查 action
        # 后续可以陆续添加
        action_list = [
            'avatar',  # 上传头像
            'bookpublish',  # 发布图书
            'goodspublish',  # 发布二手物品
        ]
        if action not in action_list:
            return Response(res_format(None, code=12008, msg='非法的 action'))
        # 获取生成数量
        amount = request.query_params.get('amount', '1')
        amount = int(amount)
        amount = amount if amount > 0 else 1
        # 上传头像只可获取 1 个
        if action == 'avatar':
            amount = 1
        # 发布图书最多可获取 3 个
        if action == 'bookpublish' and amount > 3:
            amount = 3
        result = []
        for i in range(0, amount):
            # 生成 key
            key = generate_unique_token32()
            # 生成 token
            token = generate_qinui_token(action, key)
            result.append({'key': action + '/' + key, 'token': token})
        return Response(res_format(result))

    @action(detail=False)
    def history(self, request, format=None):
        '''查询个人最近浏览记录,暂时先只展示最近 20 条记录(废弃)'''
        user = request.user
        # 只查询图书状态正常且图书系统状态正常的图书,其他过滤掉
        record = BookDetailLog.objects.filter(uid=user).filter(
            bid__status='normal').filter(
                bid__systemStatus='normal').order_by('-updateTime')
        result = BookDetailLogOverviewSerializers(record, many=True).data
        result = result[:20]

        record_book = BookDetailLog.objects.filter(uid=user).filter(
            bid__status='normal').filter(
                bid__systemStatus='normal').order_by('-updateTime')
        record_goods = BookDetailLog.objects.filter(uid=user).filter(
            bid__status='normal').filter(
                bid__systemStatus='normal').order_by('-updateTime')
        return Response(res_format(result))

    @action(detail=False, url_path='history/views')
    def history_views(self, request, format=None):
        '''查询个人最近浏览记录,新版'''
        user = request.user
        page = int(request.query_params.get('page', '1'))
        limit = int(request.query_params.get('limit', '30'))

        record_book = BookDetailLog.objects.filter(uid=user).filter(
            bid__status='normal').filter(
                bid__systemStatus='normal').order_by('-updateTime')
        record_goods = GoodsDetailLog.objects.filter(uid=user).filter(
            gid__status='normal').filter(
                gid__systemStatus='normal').order_by('-updateTime')
        result_book = BookDetailLogOverviewSerializers(
            record_book[limit * (page - 1):limit * page], many=True).data
        result_goods = GoodsDetailLogOverviewSerializers(
            record_goods[limit * (page - 1):limit * page], many=True).data

        return Response(
            res_format({
                'page': page,
                'limit': limit,
                'book': {
                    'total': len(record_book),
                    'list': result_book
                },
                'goods': {
                    'total': len(record_goods),
                    'list': result_goods
                }
            }))

    @action(detail=False)
    def collection(self, request, format=None):
        '''获取收藏信息,图书和闲置'''
        user = request.user
        # 图书收藏信息
        from apps.book.models import CollectBook
        from apps.book.serializer import CollectBookOverviewSerializers
        record_book = CollectBook.objects.filter(uid=str(user)).filter(
            status='normal').filter(bid__status='normal').filter(
                bid__systemStatus='normal').order_by('-updateTime')
        result_book = CollectBookOverviewSerializers(record_book,
                                                     many=True).data
        # 闲置收藏信息
        from apps.goods.models import CollectGoods
        from apps.goods.serializer import CollectGoodsSerializers
        record_goods = CollectGoods.objects.filter(uid=str(user)).filter(
            status='normal').filter(gid__status='normal').filter(
                gid__systemStatus='normal').order_by('-updateTime')
        result_goods = CollectGoodsSerializers(record_goods, many=True).data
        # 返回结果
        return Response(
            res_format({
                'book': result_book,
                'goods': result_goods
            }))

    @action(detail=False, url_path='acode/userpage')
    def acode_userpage(self, request, format=None):
        '''获取用户主页的小程序码'''
        user = request.user
        scene = user.uid
        page = 'partials/user-info/user-info'
        types = 'userpage'
        try:
            # 已经生成过,在数据库中查询
            record = AcodeBasic.objects.get(uid=user, types=types)
            image = record.image
            record.save()
            return Response(res_format({'image': image}))
        except AcodeBasic.DoesNotExist:
            # 第一次生成
            wechat = WechatTH()
            image = wechat.get_acode(scene, page=page)
            if not image:
                return Response(res_format(code=12009, msg='生成小程序码失败'))
            # 存数据库
            record = AcodeBasic(uid=user,
                                image=image,
                                scene=scene,
                                page=page,
                                types=types)
            record.save()
            return Response(res_format({'image': image}))

    @action(detail=False, url_path='acode/background')
    def acode_background(self, request, format=None):
        '''获取小程序码背景图片 3:4'''
        images = [
            # 'https://res.beiyang1895.com/background/background-image001.jpg',
        ]
        record = AcodeBgBasic.objects.filter(
            status='normal').order_by('-order')
        for item in record:
            images.append(item.image)
        return Response(res_format({'images': images}))
예제 #12
0
class AccountNoAuthViewSet(viewsets.ViewSet):
    '''基础服务 错误码11xxx'''
    schema = CustomSchema(
        manual_fields={
            'GET:/auth/': [
                coreapi.Field("token",
                              required=True,
                              location="query",
                              description='JWT 字符串'),
            ],
            'POST:/auth/signin/': [
                coreapi.Field("mobile",
                              required=True,
                              location="form",
                              description='手机号'),
                coreapi.Field("password",
                              required=True,
                              location="form",
                              description='密码,8-20 位'),
            ],
            'POST:/auth/register/': [
                coreapi.Field("mobile",
                              required=True,
                              location="form",
                              description='手机号'),
                coreapi.Field("code",
                              required=True,
                              location="form",
                              description='短信验证码'),
                coreapi.Field("token",
                              required=True,
                              location="form",
                              description='短信验证码 token'),
                coreapi.Field("password",
                              required=True,
                              location="form",
                              description='密码,8-20 位'),
            ],
            'POST:/auth/smscaptcha/': [
                coreapi.Field("mobile",
                              required=True,
                              location="form",
                              description='手机号'),
                coreapi.Field(
                    "action",
                    required=True,
                    location="form",
                    description='验证码类型 register/retrive replace(需登录)'),
                coreapi.Field("token",
                              required=True,
                              location="form",
                              description='图形验证码 token'),
                coreapi.Field("authenticate",
                              required=True,
                              location="form",
                              description='图形验证码 authenticate'),
            ],
            'POST:/auth/retrive/': [
                coreapi.Field("mobile",
                              required=True,
                              location="form",
                              description='手机号'),
                coreapi.Field("code",
                              required=True,
                              location="form",
                              description='短信验证码'),
                coreapi.Field("token",
                              required=True,
                              location="form",
                              description='短信验证码 token'),
                coreapi.Field("password",
                              required=True,
                              location="form",
                              description='新密码,8-20 位'),
            ],
            'GET:/auth/wechat/': [
                coreapi.Field("code",
                              required=True,
                              location="query",
                              description='微信小程序 code'),
            ],
            'GET:/auth/userinfo/': [
                coreapi.Field("uid",
                              required=True,
                              location="query",
                              description='用户的 UID'),
            ],
        })

    def list(self, request, format=None):
        '''验证 JWT,可以获得 payload'''
        ip_addr = request.META.get('REMOTE_ADDR')
        ua_string = request.META.get('HTTP_UA', '')
        token = request.query_params.get('token')
        payload = JWTHelper().decode(token, ua_string)
        if payload['error'] is None:
            return Response(res_format(payload))
        else:
            return Response(res_format(None, code=11001, msg=payload['error']))

    @action(methods=['post'], detail=False)
    def signin(self, request, format=None):
        '''登录,使用手机号和密码'''
        # 获取及检查参数
        params = ['mobile', 'password']
        mobile, password = get_dict_values(request.data, params)
        if check_none(mobile, password):
            raise ParseError('缺少参数:' + ', '.join(params))
        # TODO:: 登录安全验证,连续输错密码 n 次,开启图形验证码验证
        # 验证手机号和密码是否匹配
        try:
            record = AccountBasic.objects.get(mobile=mobile)
            if record.password == encrypt_md5(password):
                # 检查账号状态
                if record.status == 'delete':
                    return Response(res_format(None, code=11019, msg='该账号已删除'))
                if record.status == 'violation':
                    return Response(
                        res_format(None, code=11020, msg='该账号已被举报,无法登录'))
                # 检查个人信息是否完善
                if not check_userinfo_perfect(record):
                    NoticeTpl.improve_personal_information(record)
                # 登录成功,生成 JWT 并返回
                uid = str(record)
                ip_addr = request.META.get('REMOTE_ADDR')
                ua_string = request.META.get('HTTP_UA', '')
                token = JWTHelper().encode(uid, ua_string)
                return Response(res_format({'token': token}, msg='登录成功'))
            else:
                # 密码错误
                return Response(res_format(None, code=11002, msg='密码错误'))
        except AccountBasic.DoesNotExist:
            return Response(res_format(None, code=11003, msg='该手机号未注册'))

    @action(methods=['post'], detail=False)
    def register(self, request, format=None):
        '''注册,使用手机号、短信验证码、token、密码'''
        # 获取及检查参数
        params = ['mobile', 'code', 'token', 'password']
        mobile, code, token, password = get_dict_values(request.data, params)
        if check_none(mobile, code, token, password):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查手机号是否已经注册
        try:
            record = AccountBasic.objects.get(mobile=mobile)
        except AccountBasic.DoesNotExist:
            record = None
        if record is not None:
            return Response(res_format(None, code=11004, msg='该手机号已注册'))
        # 检查密码强度,密码长度为 8-20 位
        if not check_password(password):
            return Response(res_format(None, code=11006, msg='密码长度须为 8-20 位'))
        # 检查短信验证码
        if not Sender().verify(token, mobile, code, 'register'):
            return Response(res_format(None, code=11005, msg='短信验证码不正确'))
        # 所有检查都已通过,开始创建账号
        try:
            record = AccountBasic(mobile=mobile,
                                  password=encrypt_md5(password))
            record.save()
            # 注册成功通知
            NoticeTpl.registration_success(record)
            # 注册成功,生成 JWT 并返回
            uid = str(record)
            ip_addr = request.META.get('REMOTE_ADDR')
            ua_string = request.META.get('HTTP_UA', '')
            token = JWTHelper().encode(uid, ua_string)
            return Response(res_format({'token': token}, msg='注册成功'))
        except Exception as e:
            return Response(res_format(None, code=11007, msg=str(e)))

    @action(methods=['post'], detail=False)
    def smscaptcha(self, request, format=None):
        '''获取短信验证码,使用手机号、验证码类型、图片验证码 token 和图片验证码认证信息'''
        # 获取及检查参数
        params = ['mobile', 'action', 'token', 'authenticate']
        mobile, action, token, authenticate = get_dict_values(
            request.data, params)
        if check_none(mobile, action, token, authenticate):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查 action
        # 注意 ⚠️
        # 增加 action 的时候
        # 需要在这增加 action_list
        # 还要在下面增加相应的判断条件
        # 还要在上面的 schema 中加注释
        # 最重要的,要在 model 里增加字段对应可选的值
        action_list = [
            'register',  # 注册
            'retrive',  # 找回密码
            'replace',  # 换绑手机号
        ]
        if action not in action_list:
            return Response(res_format(None, code=11008, msg='非法的 action'))
        # action 如果是注册,则先判断用户表内是否无该手机号
        # action 如果是找回密码,则先判断用户表内是否有该手机号
        # action 如果是换绑手机号,则先判断是否登录
        try:
            record = AccountBasic.objects.get(mobile=mobile)
        except AccountBasic.DoesNotExist:
            record = None
        if action == 'register' and record is not None:
            return Response(res_format(None, code=11009, msg='该手机号已注册'))
        if action == 'reteive' and record is None:
            return Response(res_format(None, code=11010, msg='该手机号未注册'))
        if action == 'replace' and request.auth is None:
            return Response(res_format(None, code=11018, msg='请先登录'))
        # 获取其他参数
        ip_addr = request.META.get('REMOTE_ADDR')
        if request.auth:
            user = request.user
        else:
            user = None
        # 检查手机号格式
        if not check_mobile(mobile):
            return Response(res_format(None, code=11011, msg='手机号格式错误'))
        # 首先检查图形验证码是否验证通过
        if not CaptchaUtil._verify_by_authenticate(token, authenticate):
            return Response(res_format(None, code=11012, msg='图形验证码验证失败'))
        # 验证通过,发送短信验证码,返回 token
        sms = Sender().sms_verification_code(mobile, action, ip_addr, user)
        if sms['ok']:
            # 发送成功
            return Response(res_format({'token': sms['token']},
                                       msg=sms['msg']))
        else:
            return Response(res_format(None, code=11013, msg=sms['msg']))

    @action(methods=['post'], detail=False)
    def retrive(self, request, format=None):
        '''找回密码,使用手机号、短信验证码、token 和新密码'''
        # 获取及检查参数
        params = ['mobile', 'code', 'token', 'password']
        mobile, code, token, password = get_dict_values(request.data, params)
        if check_none(mobile, code, token, password):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 检查手机号是否已经注册
        try:
            record = AccountBasic.objects.get(mobile=mobile)
        except AccountBasic.DoesNotExist:
            record = None
        if record is None:
            return Response(res_format(None, code=11014, msg='该手机号未注册'))
        # 检查密码强度,密码长度为 8-20 位,
        if not check_password(password):
            return Response(res_format(None, code=11015, msg='新密码长度须为 8-20 位'))
        # 检查短信验证码
        if not Sender().verify(token, mobile, code, 'retrive'):
            return Response(res_format(None, code=11016, msg='短信验证码不正确'))
        # 所有检查都已通过,开始修改密码
        record.password = encrypt_md5(password)
        # 更新 account tokenTime 使之前的登录状态失效
        record.refresh_token()
        record.save()
        # 找回密码成功通知
        NoticeTpl.retrieve_password_success(record)
        return Response(res_format(None, msg='重置密码成功'))

    @action(detail=False)
    def wechat(self, request, format=None):
        '''使用微信小程序的 code 换取 openid'''
        # 获取及检查参数
        params = ['code']
        code = get_dict_values(request.query_params, params)
        if check_none(code):
            raise ParseError('缺少参数:' + ', '.join(params))
        # 向微信 API 发送请求
        res = get_weixin_by_code(code)
        if res['ok']:
            # 获取成功
            # 存数据库
            openid = res['data']['openid']
            session_key = res['data']['session_key']
            try:
                record = WechatBasic.objects.get(openid=openid)
                record.sessionKey = session_key
            except WechatBasic.DoesNotExist:
                record = WechatBasic(openid=openid, sessionKey=session_key)
            # 如果是登录状态,则绑定 uid
            if request.auth:
                record.uid = request.user
            record.save()
            return Response(res_format({'openid': openid}))
        else:
            # 获取失败
            return Response(res_format(code=11017, msg=res['msg']))

    @action(detail=False)
    def userinfo(self, request, format=None):
        '''通过 UID 获取用户信息,包括:昵称、头像、性别和发布图书的信息等'''
        uid = request.query_params.get('uid', '')
        user = request.user
        try:
            # 获取用户基础信息
            record = AccountBasic.objects.get(uid=uid)
            # 检查账号状态
            if record.status == 'delete':
                return Response(res_format(None, code=11022, msg='该账号已删除'))
            if record.status == 'violation':
                return Response(res_format(None, code=11023, msg='该账号已被举报'))
            result = {}
            result['userinfo'] = UserInfoSerializer(record).data

            # 获取发布图书信息
            record_publishinfo = PublishBook.objects.filter(
                uid=uid).order_by('-updateTime')
            if not user.is_anonymous and str(user) == uid:
                # 如果查询自己的出售信息,则可以查询到系统状态为:正常和被举报的信息;以及发布状态为:正常、已下架和已出售的信息
                record_publishinfo = record_publishinfo.filter(
                    Q(systemStatus='normal')
                    | Q(systemStatus='violation')).filter(
                        Q(status='normal') | Q(status='sold')
                        | Q(status='removed'))
            else:
                # 其他用户只能搜索到系统状态为:正常,且发布状态为:正常和已出售的信息
                record_publishinfo = record_publishinfo.filter(
                    systemStatus='normal').filter(
                        Q(status='normal') | Q(status='sold'))
            result['bookInfo'] = PublishBookOverviewSerializers(
                record_publishinfo, many=True).data

            # 获取发布二手物品信息
            record_goodsinfo = PublishGoods.objects.filter(
                uid=uid).order_by('-updateTime')
            if not user.is_anonymous and str(user) == uid:
                # 如果查询自己的出售信息,则可以查询到系统状态为:正常和被举报的信息;以及发布状态为:正常、已下架和已出售的信息
                record_goodsinfo = record_goodsinfo.filter(
                    Q(systemStatus='normal')
                    | Q(systemStatus='violation')).filter(
                        Q(status='normal') | Q(status='sold')
                        | Q(status='removed'))
            else:
                # 其他用户只能搜索到系统状态为:正常,且发布状态为:正常和已出售的信息
                record_goodsinfo = record_goodsinfo.filter(
                    systemStatus='normal').filter(
                        Q(status='normal') | Q(status='sold'))
            result['goodsInfo'] = PublishGoodsOverviewSerializers(
                record_goodsinfo, many=True).data

            # 记录查询日志
            if not (not user.is_anonymous and str(user) == uid):
                record_log = UserinfoLog(
                    uid=user if not user.is_anonymous else None, user=record)
                record_log.save()

            # 显示个人主页访问次数
            record_views = UserinfoLog.objects.filter(user=record)
            result['views'] = len(record_views)
        except AccountBasic.DoesNotExist:
            return Response(res_format(code=11021, msg='无相关用户信息'))
        return Response(res_format(result))