【Python使用】嘿马头条项目从到完整开发教程第7篇:OSS对象存储,存储【附代码文档】

举报
程序员一诺python 发表于 2025/09/03 21:35:47 2025/09/03
【摘要】 1.APScheduler任务调度涵盖安装配置、使用方式、调度器Scheduler、执行器executors、触发器Trigger等核心组件。2. RPC远程过程调用包括RPC概念、背景用途、优缺点分析。3. Protocol Buffers数据序列化涉及文档结构、注释语法、数据类型、枚举类型、消息类型(字段编号、字段规则、嵌套类型、保留字段、默认值)。4. 客户端开发包含头条

🏆🏆🏆教程全知识点简介:1.APScheduler任务调度涵盖安装配置、使用方式、调度器Scheduler、执行器executors、触发器Trigger等核心组件。2. RPC远程过程调用包括RPC概念、背景用途、优缺点分析。3. Protocol Buffers数据序列化涉及文档结构、注释语法、数据类型、枚举类型、消息类型(字段编号、字段规则、嵌套类型、保留字段、默认值)。4. 客户端开发包含头条首页新闻推荐接口编写。5. 即时通讯技术涵盖需求场景、传统推送实现、Socket.IO(Python服务器端开发、事件处理)。6. Elasticsearch搜索引擎包括简介原理、倒排索引、分析器、相关性排序、集群概念、IK中文分析器、索引类型、文档操作(索引文档、获取文档、判断存在、更新删除)、Logstash数据导入、查询(基本查询、高级查询)、全文检索实现、Python客户端使用、联想提示(拼写纠错、自动补全)。7. 单元测试涵盖测试分类、基本写法、测试必要性。8. 服务器部署包括Gunicorn、Supervisor配置管理。9. 项目开发流程涉及产品介绍、原型图UI图、技术架构、开发环境(ToutiaoWeb虚拟机、Pycharm远程开发)。10. 数据库技术包含ORM理解、SQLAlchemy映射构建、数据库连接设置、模型类字段选项。11. 分布式系统涵盖分布式ID方案选择、Twitter Snowflake算法(64位ID划分、最大取值计算、移位偏移计算、序号循环掩码、时间戳处理)。12. Redis数据库包括Redis持久化机制。13. Git工作流涵盖Gitflow工作流(工作方式、历史分支、功能分支、发布分支、维护分支)、调试方法。14. 身份认证技术包含JWT、JWS、JWE概念、Python库使用、项目封装实施方案。15. 对象存储涉及OSS对象存储、七牛云存储服务。16. 缓存系统包括缓存架构、缓存数据保存方式、缓存有效期TTL、缓存淘汰策略、缓存问题(缓存穿透、缓存雪崩)、头条项目缓存设计(User Cache、Article Cache、Announcement Cache)、持久存储设计(阅读历史、搜索历史、统计数据)。


📚📚👉👉👉本站这篇博客:   https://bbs.huaweicloud.com/blogs/453687    中查看

📚📚👉👉👉本站这篇博客:   https://bbs.huaweicloud.com/blogs/455384    中查看

📚📚👉👉👉本站这篇博客:   https://bbs.huaweicloud.com/blogs/458098    中查看

✨ 本教程项目亮点

🧠 知识体系完整:覆盖从基础原理、核心方法到高阶应用的全流程内容
💻 全技术链覆盖:完整前后端技术栈,涵盖开发必备技能
🚀 从零到实战:适合 0 基础入门到提升,循序渐进掌握核心能力
📚 丰富文档与代码示例:涵盖多种场景,可运行、可复用
🛠 工作与学习双参考:不仅适合系统化学习,更可作为日常开发中的查阅手册
🧩 模块化知识结构:按知识点分章节,便于快速定位和复习
📈 长期可用的技术积累:不止一次学习,而是能伴随工作与项目长期参考


🎯🎯🎯全教程总章节


🚀🚀🚀本篇主要内容

OSS对象存储

存储

需求

在头条项目中,如用户头像、文章图片等数据需要使用文件存储系统来保存

方案

  • 自己搭建文件系统服务
  • 选用第三方对象存储服务

在头条项目中使用对象存储服务 [

使用

  1. 注册
  2. 新建存储空间
  3. 使用七牛SDK完成代码实现

七牛Python SDK 网址 [

安装SDK
pip install qiniu
编码

七牛提供的上传代码参考示例

from qiniu import Auth, put_file, etag
import qiniu.config



#需要填写你的 Access Key 和 Secret Key


access_key = 'Access_Key'
secret_key = 'Secret_Key'



#构建鉴权对象


q = Auth(access_key, secret_key)



#要上传的空间


bucket_name = 'Bucket_Name'



#上传后保存的文件名


key = 'my-python-logo.png'



#生成上传 Token,可以指定过期时间等


token = q.upload_token(bucket_name, key, 3600)



#要上传文件的本地路径


localfile = './sync/bbb.jpg'

ret, info = put_file(token, key, localfile)
print(info)
assert ret['key'] == key
assert ret['hash'] == etag(localfile)

头条项目实现

from qiniu import Auth, put_file, etag, put_data
import qiniu.config
from flask import current_app


def upload_image(file_data):
    """
    上传图片到七牛
    :param file_data: bytes 文件
    :return: file_name
    """
    # 需要填写你的 Access Key 和 Secret Key
    access_key = current_app.config['QINIU_ACCESS_KEY']
    secret_key = current_app.config['QINIU_SECRET_KEY']

    # 构建鉴权对象
    q = Auth(access_key, secret_key)

    # 要上传的空间
    bucket_name = current_app.config['QINIU_BUCKET_NAME']

    # 上传到七牛后保存的文件名
    # key = 'my-python-七牛.png'
    key = None

    # 生成上传 Token,可以指定过期时间等
    token = q.upload_token(bucket_name, expires=1800)

    # # 要上传文件的本地路径
    # localfile = '/Users/jemy/Documents/qiniu.png'

    # ret, info = put_file(token, key, localfile)
    ret, info = put_data(token, key, file_data)

    return ret['key']

上传头像、身份证图片接口

在common/utils/parser.py

import imghdr

def image_file(value):
    """
    检查是否是图片文件
    :param value:
    :return:
    """
    try:
        file_type = imghdr.what(value)
    except Exception:
        raise ValueError('Invalid image.')
    else:
        if not file_type:
            raise ValueError('Invalid image.')
        else:
            return value

toutiao/resources/user/profle.py

[Scrapy 文档]

class PhotoResource(Resource):
    """
    用户图像 (头像,身份证)
    """
    method_decorators = [login_required]

    def patch(self):
        file_parser = RequestParser()
        file_parser.add_argument('photo', type=parser.image_file, required=False, location='files')
        file_parser.add_argument('id_card_front', type=parser.image_file, required=False, location='files')
        file_parser.add_argument('id_card_back', type=parser.image_file, required=False, location='files')
        file_parser.add_argument('id_card_handheld', type=parser.image_file, required=False, location='files')
        files = file_parser.parse_args()

        user_id = g.user_id
        new_user_values = {}
        new_profile_values = {}
        return_values = {'id': user_id}

        if files.photo:
            try:
                photo_url = upload_image(files.photo.read())
            except Exception as e:
                current_app.logger.error('upload failed {}'.format(e))
                return {'message': 'Uploading profile photo image failed.'}, 507
            new_user_values['profile_photo'] = photo_url
            return_values['photo'] = current_app.config['QINIU_DOMAIN'] + photo_url
            need_delete_profile = True

        if files.id_card_front:
            try:
                id_card_front_url = upload_image(files.id_card_front.read())
            except Exception as e:
                current_app.logger.error('upload failed {}'.format(e))
                return {'message': 'Uploading id_card_front image failed.'}, 507
            new_profile_values['id_card_front'] = id_card_front_url
            return_values['id_card_front'] = current_app.config['QINIU_DOMAIN'] + id_card_front_url

        if files.id_card_back:
            try:
                id_card_back_url = upload_image(files.id_card_back.read())
            except Exception as e:
                current_app.logger.error('upload failed {}'.format(e))
                return {'message': 'Uploading id_card_back image failed.'}, 507
            new_profile_values['id_card_back'] = id_card_back_url
            return_values['id_card_back'] = current_app.config['QINIU_DOMAIN'] + id_card_back_url

        if files.id_card_handheld:
            try:
                id_card_handheld_url = upload_image(files.id_card_handheld.read())
            except Exception as e:
                current_app.logger.error('upload failed {}'.format(e))
                return {'message': 'Uploading id_card_handheld image failed.'}, 507
            new_profile_values['id_card_handheld'] = id_card_handheld_url
            return_values['id_card_handheld'] = current_app.config['QINIU_DOMAIN'] + id_card_handheld_url

        if new_user_values:
            User.query.filter_by(id=user_id).update(new_user_values)
        if new_profile_values:
            UserProfile.query.filter_by(id=user_id).update(new_profile_values)

        db.session.commit()

        return return_values, 201

CDN

使用第三方OSS服务的好处是集成了CDN服务,下面来了解一下什么是CDN。

CDN

全称:Content Delivery Network或Content Distribute Network,即内容分发网络

是将源站内容分发至最接近用户的节点,使用户可就近取得所需内容,提高用户访问的响应速度和成功率。解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播等场景。

基本思路

尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。

目的

解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播等场景。使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度和成功率。

控制时延无疑是现代信息科技的重要指标,CDN的意图就是尽可能的减少资源在转发、传输、链路抖动等情况下顺利保障信息的连贯性。

CDN就是扮演者护航者和加速者的角色,更快准狠的触发信息和触达每一个用户,带来更为极致的使用体验。

基本原理

最简单的CDN网络由一个DNS服务器和几台缓存服务器组成:

  1. 当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器。
  2. CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回用户。
  3. 用户向CDN的全局负载均衡设备发起内容URL访问请求。
  4. CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。
  5. 区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址。
  6. 全局负载均衡设备把服务器的IP地址返回给用户。
  7. 用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。