1 介绍

1.1 定义

01.Flask框架起源
    a.历史背景
        Flask由奥地利开发者Armin Ronacher于2010年创建
        最初是作为愚人节玩笑项目而诞生
        基于Werkzeug WSGI工具包和Jinja2模板引擎构建
        名字来源于德语"Flasche",意为瓶子,象征轻便和简洁
    b.设计理念
        微架构(Microframework)设计哲学
        只提供Web开发的核心功能,保持精简
        高度可扩展性,通过扩展满足各种需求
        开发者拥有完全的控制权和选择权
    c.核心定位
        Python生态系统中的轻量级Web框架
        介于重量级框架(Django)和最小库(Bottle)之间
        适合快速原型开发和中小型项目
        也可扩展到企业级应用

02.微架构特点
    a.核心精简
        只包含路由、请求处理和模板渲染等核心功能
        不强制使用特定的数据库、表单验证等组件
        开发者可以自由选择和集成第三方库
        避免了不必要的依赖和性能开销
    b.显式配置
        不使用魔法式的配置,一切都显式声明
        配置项清晰可见,便于理解和维护
        支持多种配置方式:文件、对象、环境变量
        配置系统灵活且易于测试
    c.扩展友好
        官方和社区提供丰富的扩展库
        扩展遵循统一的接口规范
        支持按需加载,避免不必要的功能加载
        扩展之间相互独立,可自由组合

03.技术基础
    a.Werkzeug WSGI工具包
        提供完整的WSGI实现
        包含路由、请求/响应处理等核心功能
        强大的调试和开发服务器
        支持多种Web服务器部署
    b.Jinja2模板引擎
        高性能的模板渲染引擎
        支持模板继承和组合
        丰富的过滤器和测试器
        沙箱环境保证模板执行安全
    c.标准库集成
        基于Python标准库和成熟第三方库
        遵循Python编程规范和最佳实践
        与Python生态系统无缝集成
        充分利用Python的动态特性

1.2 核心概念:6个

01.WSGI规范与实现
    a.WSGI定义
        Web Server Gateway Interface的缩写
        Python Web应用与Web服务器之间的标准接口
        定义了服务器如何与应用程序通信的规范
        实现了Web服务器与应用程序的解耦
    b.Flask中的WSGI
        基于Werkzeug实现WSGI规范
        Flask应用本身就是WSGI可调用对象
        支持与多种WSGI服务器集成
        提供开发服务器和生产环境适配
    c.优势意义
        使得不同的Web服务器可以运行相同的Python应用
        开发者可以专注于应用逻辑,不关心底层实现
        提高了Python Web应用的可移植性和可扩展性
        是Python Web生态系统的重要基础
    d.实际应用
        开发环境使用Flask内置服务器
        生产环境使用Gunicorn、uWSGI等专业服务器
        支持容器化部署(Docker、Kubernetes)
        云平台的良好适配性

02.路由系统机制
    a.路由定义
        使用@app.route()装饰器定义URL模式与视图函数的映射
        支持静态URL和动态URL参数
        可以为同一个URL指定多个HTTP方法
        路由规则按定义顺序进行匹配
    b.动态路由
        URL中可以包含变量部分,使用<变量名>语法
        支持类型转换器:string、int、float、path
        自定义转换器实现复杂参数验证
        正则表达式支持高级路由匹配
    c.端点管理
        每个路由规则都有唯一的端点名
        默认端点名是视图函数名
        用于URL构建和反向解析
        蓝图中端点命名避免冲突
    d.路由优化
        路由规则编译为正则表达式
        匹配算法经过性能优化
        支持路由缓存和预编译
        提供路由性能监控工具

03.视图函数处理流程
    a.视图定义
        处理特定URL请求的Python函数
        接收URL参数作为函数参数
        返回响应对象或简单数据
        是业务逻辑的主要载体
    b.请求处理
        通过上下文对象访问请求数据
        支持多种请求数据格式:表单、JSON、文件
        请求数据的验证和清理
        异常处理和错误响应
    c.响应生成
        支持多种响应格式:HTML、JSON、XML
        状态码和响应头的自定义
        响应缓存和压缩支持
        流式响应处理大文件
    d.装饰器增强
        权限验证装饰器
        缓存装饰器
        日志记录装饰器
        性能监控装饰器

04.模板引擎集成
    a.Jinja2集成
        Flask内置Jinja2模板引擎
        无缝集成,开箱即用
        支持模板继承和组合
        强大的过滤器和测试器系统
    b.模板特性
        变量替换:{{ variable }}
        控制结构:{% if %}、{% for %}
        宏定义:{% macro %}
        模板继承:{% extends %}、{% block %}
    c.模板安全
        自动HTML转义防止XSS攻击
        沙箱环境限制模板执行权限
        安全标记:|safe过滤器
        模板调试和错误处理
    d.性能优化
        模板编译为Python字节码
        模板缓存机制提高渲染速度
        异步模板渲染支持
        模板调试和性能分析

05.请求/响应循环
    a.请求接收
        Web服务器接收HTTP请求
        调用Flask应用的__call__方法
        创建请求上下文和应用上下文
        将请求信息封装到request对象中
    b.路由匹配
        根据请求URL查找匹配的路由规则
        提取URL参数和查询参数
        如果没有找到匹配,返回404错误
        路由匹配支持变量转换和验证
    c.视图执行
        调用匹配的视图函数处理请求
        视图函数访问请求上下文数据
        执行业务逻辑,处理数据
        生成响应对象或简单数据
    d.响应返回
        将简单数据转换为响应对象
        响应经过中间件处理
        设置响应头和状态码
        通过Web服务器返回给客户端

06.应用上下文管理
    a.应用上下文
        存储应用级别的全局变量
        包含current_app和g对象
        在请求处理期间保持活跃
        支持多应用实例的并发运行
    b.请求上下文
        存储请求相关的临时数据
        包含request、session对象
        每个HTTP请求都有独立的上下文
        确保多线程环境下的数据隔离
    c.上下文栈
        使用LocalStack实现线程安全
        支持上下文的嵌套和切换
        在测试环境中可以手动管理
        上下文生命周期由框架自动管理
    d.上下文代理
        werkzeug.local.LocalProxy实现
        延迟加载和线程安全访问
        代理对象的创建和配置
        性能优化和内存管理

1.3 优缺点

01.优势特点
    a.轻量级和简洁
        核心代码量小,启动速度快
        学习成本低,上手快速
        配置简单,约定少,自由度高
        内存占用少,性能表现优异
    b.高度灵活性
        开发者可以自由选择数据库、表单验证等组件
        不强制使用特定的项目结构或工具
        支持多种开发模式和架构风格
        便于集成第三方库和工具
    c.良好的文档和社区
        官方文档详细完善,示例丰富
        社区活跃,问题响应及时
        丰富的教程和最佳实践
        持续更新和维护
    d.强大的扩展生态
        拥有丰富的官方和第三方扩展
        Flask-SQLAlchemy、Flask-Login、Flask-Mail等
        扩展遵循统一规范,易于使用
        支持按需加载,避免功能冗余
    e.调试友好
        内置调试服务器,开发效率高
        错误信息详细,便于问题定位
        支持交互式调试和代码执行
        集成测试工具,便于单元测试
    f.测试支持
        内置测试客户端,便于API测试
        支持模拟请求和响应
        测试覆盖率统计工具
        与pytest等测试框架良好集成

02.劣势限制
    a.项目结构需要自行设计
        缺乏标准化的项目结构指导
        开发者需要具备良好的架构设计能力
        团队协作时需要统一规范
        大型项目的组织复杂度较高
    b.功能组件需要自行选择
        缺乏内置的ORM,需要额外配置数据库层
        表单验证、用户认证等需要选择合适的扩展
        扩展质量参差不齐,需要仔细评估
        集成多个扩展时可能出现兼容性问题
    c.大型项目的最佳实践不够明确
        相比Django,企业级开发的最佳实践较少
        缺乏成熟的架构模式参考
        大规模应用的性能调优经验有限
        团队技能要求较高
    d.并发处理能力需要额外配置
        内置开发服务器不适合生产环境
        需要配置专业的WSGI服务器
        高并发场景的性能优化复杂
        分布式部署的技术门槛较高
    e.缺乏内置的管理后台
        没有类似Django Admin的管理界面
        需要自行开发或集成第三方解决方案
        数据库管理和内容编辑功能有限
        非技术用户的操作便利性不足
    f.安全配置需要手动设置
        需要开发者了解和配置各项安全措施
        CSRF、XSS等防护需要手动启用
        安全配置的复杂度较高
        容易出现安全配置遗漏

1.4 适用场景

01.快速原型开发
    a.MVP产品开发
        适合快速构建最小可行产品
        支持敏捷开发和快速迭代
        功能验证和用户反馈收集
        创业项目和产品原型
    b.概念验证项目
        技术可行性验证
        业务模式测试
        算法效果演示
        学术研究项目
    c.数据科学应用
        机器学习模型展示
        数据可视化平台
        实验结果展示系统
        数据处理工作流
    d.教育和培训
        Web开发教学项目
        编程训练营课程案例
        技术概念演示原型
        学生实践项目

02.API服务开发
    a.RESTful API后端
        微服务架构中的单个服务
        移动应用的后端API
        第三方集成接口
        B2B系统集成服务
    b.数据处理服务
        数据清洗和转换服务
        文件处理和转换API
        图像处理服务
        内容管理系统API
    c.企业内部服务
        管理后台系统的API层
        数据报表生成服务
        自动化运维工具接口
        内部协作平台服务

03.中小型Web应用
    a.个人和小团队项目
        个人博客和作品集网站
        小型企业官网
        电商网站的小型实现
        社交网络的小型应用
    b.内容管理系统
        博客系统
        新闻发布平台
        文档管理系统
        知识库和Wiki系统
    c.企业内部工具
        员工管理系统
        项目管理工具
        考勤和审批系统
        库存管理系统

04.单页应用后端
    a.前后端分离架构
        React/Vue/Angular应用的后端API
        移动混合应用的服务端
        桌面应用的后端服务
        小程序后端接口
    b.实时应用支持
        WebSocket集成支持
        实时聊天系统
        在线协作工具
        实时数据推送服务

05.云原生和微服务
    a.容器化部署
        Docker容器化支持
        Kubernetes部署友好
        微服务架构组件
        无服务器函数后端
    b.云平台集成
        AWS Lambda函数
        Google Cloud Functions
        Azure Functions
        各大云平台良好适配

06.教育和学习
    a.编程教育
        Web开发入门教学
        Python Web框架学习
        后端开发技能培训
        框架原理理解
    b.技术分享
        开源项目示例
        技术博客演示
        会议演讲素材
        代码教学案例

1.5 架构原理

01.请求处理流程
    a.请求接收阶段
        Web服务器接收HTTP请求
        服务器调用Flask应用的__call__方法
        Flask创建请求上下文和应用上下文
        将请求信息封装到request对象中
        上下文推入栈中,激活当前请求环境
    b.路由匹配阶段
        Flask根据请求URL查找匹配的路由规则
        遍历路由表,进行模式匹配
        提取URL参数和查询字符串参数
        参数类型转换和验证
        如果没有找到匹配,返回404错误
        路由匹配支持变量转换器和自定义规则
    c.预处理阶段
        执行before_request钩子函数
        支持多个钩子函数的链式执行
        钩子函数可以进行请求验证和预处理
        如果钩子函数返回响应,直接跳过视图函数
        请求上下文数据的初始化
        用户认证和权限检查
    d.视图执行阶段
        调用匹配的视图函数处理请求
        视图函数接收URL参数作为函数参数
        通过上下文对象访问请求数据
        执行业务逻辑,处理数据
        调用数据库、缓存、外部API等服务
        生成响应对象或简单数据
    e.后处理阶段
        执行after_request钩子函数
        对响应进行修改和增强
        添加响应头、压缩内容等操作
        异常处理和错误页面生成
        响应日志记录和统计
    f.响应返回阶段
        将简单数据转换为响应对象
        响应经过中间件处理
        设置响应头和状态码
        上下文弹出栈,清理临时数据
        最终通过Web服务器返回给客户端

02.中间件机制
    a.WSGI中间件
        位于Web服务器和Flask应用之间
        可以修改请求和响应数据
        支持多个中间件的链式组合
        常用中间件:代理处理、压缩、缓存等
    b.应用级中间件
        Flask钩子函数形式的中间件
        before_request、after_request钩子
        teardown_request清理钩子
        错误处理中间件
    c.自定义中间件开发
        实现WSGI应用接口
        支持配置参数传递
        中间件组合和嵌套
        性能监控和日志记录

03.蓝图系统架构
    a.蓝图定义
        蓝图是组织相关路由和视图的机制
        支持模块化应用开发
        每个蓝图可以独立开发和测试
        蓝图之间相互隔离,避免命名冲突
    b.蓝图注册机制
        蓝图在主应用中注册
        支持URL前缀和子域名
        蓝图级别的配置和钩子函数
        动态注册和注销蓝图
    c.蓝图上下文
        蓝图内部的请求前处理
        蓝图级别的错误处理
        蓝图模板和静态文件管理
        蓝图间通信机制

04.配置管理系统
    a.配置对象设计
        基于字典的配置存储
        支持多种数据类型的配置值
        配置项的层级关系和命名空间
        配置验证和类型检查机制
    b.配置加载策略
        多种配置来源:文件、对象、环境变量
        配置的继承和覆盖机制
        配置热重载和动态更新
        敏感配置的安全处理
    c.环境配置管理
        开发、测试、生产环境配置分离
        配置文件的版本控制
        配置变更的审核和追踪
        配置最佳实践指导

05.插件扩展机制
    a.扩展接口规范
        统一的扩展初始化接口
        扩展配置和状态管理
        扩展与应用的生命周期绑定
        扩展间的依赖关系管理
    b.扩展加载机制
        延迟加载和按需初始化
        扩展的自动发现和注册
        扩展冲突检测和解决
        扩展的卸载和资源清理
    c.扩展生态系统
        官方扩展库管理
        第三方扩展的认证和推荐
        扩展质量评估体系
        社区贡献和维护机制

1.6 核心特性

01.内置开发服务器
    a.功能特性
        提供轻量级的WSGI开发服务器
        支持代码热重载,修改代码后自动重启
        集成调试器,提供详细的错误信息
        支持交互式调试和代码执行
        SSL支持,便于开发HTTPS功能
    b.使用方式
        通过flask run命令启动开发服务器
        支持命令行参数配置端口和主机
        可以通过代码方式自定义启动参数
        集成到IDE中,提供一键启动和调试
        支持多线程和HTTPS模式
    c.调试功能
        详细的错误堆栈跟踪
        变量检查和修改功能
        代码执行环境和REPL
        断点设置和单步调试
        性能分析和内存监控
    d.限制说明
        仅适用于开发和测试环境
        性能和稳定性不适合生产环境
        不支持高并发请求处理
        建议生产环境使用专业WSGI服务器
        安全性考虑和生产环境差异

02.路由系统特性
    a.基础路由功能
        使用装饰器语法定义路由
        支持静态URL和动态URL参数
        HTTP方法的精确控制
        URL构建和反向解析功能
    b.高级路由特性
        正则表达式路由匹配
        自定义URL变量转换器
        子域名路由支持
        路由别名和重定向功能
    c.路由优化
        路由规则编译和缓存
        匹配算法性能优化
        路由冲突检测和处理
        大量路由的性能管理

03.模板系统特性
    a.Jinja2集成优势
        强大且快速的模板引擎
        模板继承和组合机制
        丰富的内置过滤器和测试器
        安全的沙箱执行环境
    b.模板功能特性
        变量替换和表达式求值
        条件判断和循环控制
        宏定义和包含机制
        国际化和本地化支持
    c.模板性能优化
        模板编译为Python字节码
        智能缓存机制
        异步模板渲染支持
        模板调试和性能分析工具

04.会话管理特性
    a.会话机制设计
        基于签名的Cookie会话
        支持服务端会话存储
        会话数据加密和完整性验证
        跨域会话共享支持
    b.安全性保障
        防止会话劫持和CSRF攻击
        会话ID随机生成和定期更新
        安全Cookie配置建议
        会话过期和清理机制
    c.扩展功能支持
        永久会话和临时会话
        会话数据分析和监控
        多设备会话管理
        分布式会话存储

05.错误处理特性
    a.错误捕获机制
        全局错误处理器
        HTTP错误码和异常类型匹配
        蓝图级别和应用级别处理
        错误日志自动记录
    b.调试支持功能
        交互式调试器集成
        错误堆栈详细跟踪
        变量状态检查和修改
        生产环境安全调试模式
    c.自定义错误页面
        静态和动态错误页面
        多语言错误信息支持
        错误页面模板化
        错误信息的用户友好化

06.测试支持特性
    a.测试客户端
        内置测试客户端工具
        模拟HTTP请求和响应
        支持会话和Cookie管理
        测试覆盖率统计
    b.测试工具集成
        pytest框架集成支持
        单元测试和集成测试
        测试夹具和工具函数
        性能测试和负载测试
    c.测试最佳实践
        测试驱动开发支持
        持续集成友好
        测试报告生成
        测试数据管理

07.安全特性
    a.安全头部支持
        自动添加安全相关HTTP头
        XSS和CSRF防护机制
        内容安全策略支持
        安全Cookie配置
    b.数据验证
        请求数据验证和清理
        文件上传安全检查
        SQL注入防护
        输入数据编码和转义
    c.认证和授权
        用户认证机制支持
        角色权限管理系统
        API密钥认证
        OAuth集成支持

1.7 生态对比

01.Flask vs Django
    a.设计理念对比
        Flask:微框架,核心功能最小化,高度可扩展
        Django:全能框架,开箱即用,约定优于配置
        Flask:开发者控制一切,灵活性高,学习成本低
        Django:框架决定大部分,开发效率高,学习曲线陡峭
    b.功能丰富度对比
        Flask:基础功能精简,通过扩展补充
        Django:内置ORM、管理后台、表单系统等完整功能
        Flask:功能按需加载,避免冗余
        Django:一站式解决方案,减少技术选型烦恼
    c.项目结构对比
        Flask:项目结构自由,开发者自行设计
        Django:标准化项目结构,最佳实践明确
        Flask:适合小到中型项目,灵活性强
        Django:适合中到大型企业级项目,一致性高
    d.学习曲线对比
        Flask:学习成本低,文档简洁,上手快速
        Django:学习曲线陡峭,概念较多,需要较长时间掌握
        Flask:适合初学者和快速原型开发
        Django:适合团队协作和企业级项目开发
    e.性能对比
        Flask:轻量级,内存占用少,启动速度快
        Django:功能完整,资源占用相对较高
        Flask:在简单场景下性能优异
        Django:在复杂业务场景下优化更好
    f.生态系统对比
        Flask:丰富的第三方扩展,社区活跃,扩展质量参差不齐
        Django:内置功能完整,官方插件丰富,质量有保障
        Flask:扩展选择自由,需要仔细评估质量
        Django:官方维护,生态稳定,集成度高
    g.适用场景对比
        Flask:小型项目、API服务、快速原型、个人项目
        Django:大型企业应用、内容管理系统、电商系统
        Flask:需要高度定制化的项目
        Django:需要快速开发和标准化管理的项目

02.Flask vs FastAPI
    a.性能对比
        FastAPI:基于Starlette和Pydantic,性能优异
        Flask:传统的同步框架,性能适中
        FastAPI:原生支持异步,并发处理能力强
        Flask:异步支持有限,主要依赖扩展
    b.开发体验对比
        FastAPI:自动API文档生成,类型提示支持
        Flask:传统开发方式,文档手动编写
        FastAPI:现代Python特性,IDE支持好
        Flask:成熟稳定,学习资源丰富
    c.类型安全对比
        FastAPI:强类型支持,Pydantic数据验证
        Flask:动态类型,运行时检查
        FastAPI:减少运行时错误,代码质量高
        Flask:开发灵活,适合快速迭代
    d.异步支持对比
        FastAPI:原生异步支持,性能优越
        Flask:通过扩展支持异步,功能有限
        FastAPI:适合I/O密集型应用
        Flask:适合CPU密集型和传统Web应用
    e.文档自动生成对比
        FastAPI:基于OpenAPI/Swagger自动生成文档
        Flask:需要手动编写和维护文档
        FastAPI:交互式API文档,支持在线测试
        Flask:文档维护成本高,容易过时
    f.学习成本对比
        FastAPI:需要了解异步编程和类型提示
        Flask:学习曲线平缓,入门简单
        FastAPI:现代开发理念,需要适应新概念
        Flask:传统Web开发模式,易于理解
    g.生态系统对比
        FastAPI:新兴框架,生态快速发展
        Flask:成熟框架,生态稳定完善
        FastAPI:现代化的工具和库支持
        Flask:丰富的扩展和第三方集成

03.Flask vs Spring Boot
    a.语言生态对比
        Flask:Python生态,简洁优雅,开发效率高
        Spring Boot:Java生态,企业级,性能稳定
        Flask:适合快速开发和数据科学项目
        Spring Boot:适合大型企业级和金融系统
    b.配置和部署对比
        Flask:配置简单,部署轻量,容器化友好
        Spring Boot:配置复杂,部署重量级,传统企业部署成熟
        Flask:云原生支持好,微服务架构友好
        Spring Boot:企业级部署经验丰富,监控工具完善
    c.性能对比
        Flask:Python解释型语言,性能相对较低
        Spring Boot:Java编译型语言,性能表现优秀
        Flask:开发效率高,但运行性能有限
        Spring Boot:启动较慢,但运行性能稳定
    d.企业特性对比
        Flask:需要第三方库支持企业功能
        Spring Boot:内置企业级特性丰富
        Flask:灵活性高,可按需选择组件
        Spring Boot:标准化程度高,一致性好
    e.开发效率对比
        Flask:代码简洁,开发速度快
        Spring Boot:代码量大,开发周期长
        Flask:适合MVP和快速迭代
        Spring Boot:适合大型长期维护项目
    f.团队技能要求对比
        Flask:Python技能相对易学
        Spring Boot:Java生态学习成本较高
        Flask:适合小团队和初创公司
        Spring Boot:适合大型企业和传统IT部门
    g.成本对比
        Flask:开发成本低,部署成本低
        Spring Boot:开发成本高,但运维成本相对可控
        Flask:开源方案丰富,成本低廉
        Spring Boot:企业级支持,但需要商业许可

04.框架选择建议
    a.选择Flask的场景
        项目规模小到中等,需要快速开发
        团队对Python熟悉,学习时间有限
        需要高度定制化和灵活性
        API服务开发或微服务架构
        原型开发和概念验证
        数据科学和机器学习Web应用
        个人项目和小团队开发
    b.选择Django的场景
        大型企业级项目,需要完整功能
        内容管理系统或电商平台
        需要标准化开发和团队协作
        快速开发和内置管理后台
        传统Web应用和数据库驱动项目
        需要ORM和管理界面的项目
    c.选择FastAPI的场景
        高性能API服务需求
        异步编程和I/O密集型应用
        需要自动API文档生成
        现代Python开发团队
        微服务架构和云原生应用
        需要类型安全和数据验证
    d.选择Spring Boot的场景
        Java技术栈,企业级应用
        金融系统和大流量系统
        传统IT部门和企业环境
        需要稳定性和长期维护
        大型团队和复杂业务逻辑
        严格的安全和合规要求
    e.决策因素分析
        技术栈匹配度:团队技能和现有技术栈
        项目规模和复杂度:小项目用Flask,大项目考虑Django/Spring Boot
        性能要求:高性能API考虑FastAPI
        开发时间:快速原型用Flask,企业项目用Django
        维护成本:长期维护考虑稳定成熟的框架
        社区支持:选择社区活跃、文档丰富的框架

2 基础概念

2.1 汇总

01.介绍
    a.常用信息
        a.应用对象
            Flask应用对象是Flask程序的核心。你可以通过创建一个Flask类的实例来定义一个Flask应用对象。通常我们会将它命名为app。
        b.路由
            路由是Flask的核心概念之一。它定义了URL和处理请求的视图函数之间的映射。使用装饰器@app.route可以轻松地将URL路径与视图函数关联。
        c.视图函数
            视图函数是处理特定URL请求的Python函数。它们通常会返回一个响应对象,包含HTML、JSON或其他内容。
        d.请求对象
            Flask提供了一个全局对象request,包含了客户端请求的所有信息,例如数据、头信息、文件等。它是由Werkzeug库提供的。
        e.响应对象
            视图函数的返回值最终会被转化为一个响应对象。Flask提供了简便的方法来创建和操作这些响应对象。
        f.模板
            Flask使用Jinja2作为其模板引擎。模板允许将动态数据插入到HTML中。你可以在模板中使用控制结构(如循环和条件语句)来生成动态内容。
        g.静态文件
            Flask应用可以通过一个名为static的特殊文件夹来服务静态文件(如CSS、JavaScript、图片等)。这些文件可以通过/static/ URL访问。
        h.配置
            Flask应用可以通过配置对象或配置文件进行配置。这些配置项可以控制应用的行为,例如调试模式、数据库连接等。
        i.扩展
            Flask的设计使其非常容易扩展。有许多现成的Flask扩展可以集成到你的应用中,如Flask-SQLAlchemy(ORM)、Flask-Migrate(数据库迁移)、Flask-WTF(表单处理)等。
        j.中间件
            中间件是位于请求和响应之间的组件,可以用来处理或修改请求和响应。Werkzeug提供了中间件支持,Flask可以通过Werkzeug中间件进行扩展。
        k.上下文
            Flask有两个主要的上下文:应用上下文和请求上下文。应用上下文包含应用相关的数据,如配置和扩展实例。请求上下文包含与当前请求相关的数据,如请求对象和用户会话。
        l.会话
            Flask提供了会话机制来在多个请求之间存储用户数据。默认情况下,Flask会话是使用签名cookie实现的。

02.扩展
    a.常用信息
        a.Flask-SQLAlchemy
            a.功能
                提供SQLAlchemy ORM(对象关系映射)的集成,使数据库操作更加简单和Pythonic。
            b.用途
                简化数据库查询和操作,支持多种数据库后端。
        b.Flask-Migrate
            a.功能
                基于Alembic,提供数据库迁移功能,便于管理数据库模式的变化。
            b.用途
                管理数据库版本,支持数据库模式的升级和降级。
        c.Flask-WTF
            a.功能
                集成WTForms库,提供表单处理、表单验证和CSRF保护功能。
            b.用途
                简化表单创建和验证,增强应用的安全性。
        d.Flask-Login
            a.功能
                提供用户会话管理功能,包括登录、登出和用户会话持久化。
            b.用途
                管理用户认证状态,支持用户登录和登出功能。
        e.Flask-Mail
            a.功能
                用于发送电子邮件,支持各种电子邮件服务和协议。
            b.用途
                实现邮件通知、用户注册验证等功能。
        f.Flask-RESTful
            a.功能
                简化创建REST API的过程,提供了用于定义API资源的类和方法。
            b.用途
                快速构建RESTful API,支持资源路由和请求处理。
        g.Flask-Security
            a.功能
                提供一套完整的用户认证和授权功能,包括密码重置、注册、角色管理等。
            b.用途
                增强应用的安全性,支持用户权限管理。
        h.Flask-Bootstrap
            a.功能
                集成Twitter Bootstrap框架,方便在Flask应用中使用Bootstrap的CSS和JavaScript组件。
            b.用途
                快速构建响应式Web界面,提升用户体验。
        i.Flask-Caching
            a.功能
                提供缓存支持,支持多种缓存后端(如内存、Redis、Memcached等)。
            b.用途
                提高应用性能,减少数据库查询次数。
        j.Flask-SocketIO
            a.功能
                基于Socket.IO,提供WebSocket支持,使实时通信变得简单。
            b.用途
                实现实时聊天、通知推送等功能。
        k.Flask-Babel
            a.功能
                提供国际化和本地化支持,方便处理多语言应用。
            b.用途
                支持多语言翻译和日期格式化。
        l.Flask-Admin
            a.功能
                提供一个可定制的管理后台界面,可以快速搭建管理后台。
            b.用途
                管理应用数据和用户,支持CRUD操作。
        m.Flask-Celery
            a.功能
                集成Celery任务队列,支持异步任务处理和分布式任务队列。
            b.用途
                执行异步任务,支持任务调度和并行处理。
        n.Flask-Uploads
            a.功能
                简化文件上传和管理,支持多种文件存储后端。
            b.用途
                处理用户上传的文件,支持文件验证和存储。
        o.Flask-Testing
            a.功能
                提供单元测试支持,方便对Flask应用进行测试。
            b.用途
                编写和运行测试用例,确保应用功能的正确性。
        p.Flask-Script(已过时,推荐使用Flask内置的flask命令)
            a.功能
                提供命令行解析功能,便于管理脚本任务。
            b.用途
                执行自定义管理命令,支持任务自动化。
        q.Flask-Limiter
            a.功能
                提供速率限制功能,防止滥用API。
            b.用途
                控制API请求频率,保护服务器资源。
        r.Flask-Principal
            a.功能
                提供权限管理和角色管理功能,便于实现复杂的权限控制。
            b.用途
                管理用户角色和权限,支持细粒度的访问控制。
        s.Flask-CORS
            a.功能
                提供跨域资源共享(CORS)支持,允许跨域请求。
            b.用途
                解决跨域请求问题,支持前后端分离开发。
        t.Flask-Cache(已被Flask-Caching取代)
            a.功能
                提供缓存机制,可以缓存视图函数的输出,减少服务器负载。
            b.用途
                提高应用性能,减少重复计算和数据库查询。

03.中间件
    a.Werkzeug Middleware
        a.ProxyFix
            功能:处理代理服务器设置的中间件,确保正确获取客户端IP地址和协议。
            用途:在应用部署在代理服务器后面时,确保应用能够正确识别客户端的真实IP地址和使用的协议(HTTP/HTTPS)。
        b.SharedDataMiddleware
            功能:提供静态文件服务的中间件,可以将请求路径映射到文件系统中的静态文件。
            用途:用于开发环境中快速提供静态文件服务。
        c.DispatcherMiddleware
            功能:将不同的URL路径分派给不同的WSGI应用,是创建复合应用的好工具。
            用途:在一个服务器实例中运行多个WSGI应用。
    b.Flask扩展中间件
        a.Flask-CORS
            功能:处理跨域资源共享(CORS)的中间件,允许跨域请求。
            用途:对于前后端分离的项目非常有用,解决跨域请求问题。
        b.Flask-Limiter
            功能:提供速率限制功能的中间件,用于防止API滥用。
            用途:可以根据IP地址、用户等进行限流,保护API资源。
        c.Flask-SSLify(已不再维护,推荐使用Flask-Talisman)
            功能:强制使用HTTPS协议的中间件,重定向所有HTTP请求到HTTPS。
            用途:确保应用通过HTTPS安全传输。
        d.Flask-Talisman
            功能:提供安全相关的HTTP头(如HSTS、CSP等)的中间件,增强Web应用的安全性。
            用途:提高应用的安全性,防止常见的Web攻击。
        e.Flask-Session
            功能:扩展Flask会话机制的中间件,支持服务器端会话存储(如Redis、Memcached等)。
            用途:提供更安全和持久的会话管理。
        f.Flask-Compress
            功能:为响应内容提供Gzip压缩的中间件,减少传输数据量,提高应用性能。
            用途:优化网络传输,提高加载速度。
        g.Flask-HTTPAuth
            功能:提供HTTP基础认证和摘要认证的中间件,简化API认证流程。
            用途:用于保护API,确保只有授权用户可以访问。
        h.Flask-DebugToolbar
            功能:提供调试信息的中间件,在开发环境中非常有用,可以显示详细的调试信息、SQL查询等。
            用途:帮助开发者快速定位和解决问题。
        i.Flask-Security
            功能:集成多种安全功能的中间件,包括认证、授权、角色管理等。
            用途:提供全面的安全解决方案,保护应用和用户数据。
        j.Flask-Cache(已被Flask-Caching取代)
            功能:提供缓存支持的中间件,可以缓存视图函数的输出,减少服务器负载。
            用途:提高应用性能,减少重复计算。
        k.Flask-Caching
            功能:现代的缓存中间件,支持多种缓存后端,可以缓存视图和其他数据。
            用途:优化性能,减少数据库查询和计算。

04.配置类
    a.使用配置类
        a.代码1
            ---
            import logging
            from datetime import timedelta


            from urllib.parse import quote_plus as urlquote


            class BaseConfig:
                SUPERADMIN = 'admin'

                SYSTEM_NAME = 'Pear Admin'
                # 主题面板的链接列表配置
                SYSTEM_PANEL_LINKS = [
                    {
                        "icon": "layui-icon layui-icon-auz",
                        "title": "官方网站",
                        "href": "http://www.pearadmin.com"
                    },
                    {
                        "icon": "layui-icon layui-icon-auz",
                        "title": "开发文档",
                        "href": "http://www.pearadmin.com"
                    },
                    {
                        "icon": "layui-icon layui-icon-auz",
                        "title": "开源地址",
                        "href": "https://gitee.com/Jmysy/Pear-Admin-Layui"
                    }
                ]

                UPLOADED_PHOTOS_DEST = 'static/upload'
                UPLOADED_FILES_ALLOW = ['gif', 'jpg']
                UPLOADS_AUTOSERVE = True

                # JSON配置
                JSON_AS_ASCII = False

                SECRET_KEY = "pear-system-flask"

                # mysql 配置
                MYSQL_USERNAME = "root"
                MYSQL_PASSWORD = "123456"
                MYSQL_HOST = "127.0.0.1"
                MYSQL_PORT = 3306
                MYSQL_DATABASE = "pradmin"

                # 数据库的配置信息
                # SQLALCHEMY_DATABASE_URI = 'sqlite:///../pear.db'
                SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{MYSQL_USERNAME}:{urlquote(MYSQL_PASSWORD)}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}?charset=utf8mb4"

                # 默认日志等级
                LOG_LEVEL = logging.WARN
                """
                flask-mail配置
                """
                MAIL_SERVER = 'smtp.qq.com'
                MAIL_USE_TLS = False
                MAIL_USE_SSL = True
                MAIL_PORT = 465
                MAIL_USERNAME = '[email protected]'
                MAIL_PASSWORD = 'XXXXX'  # 生成的授权码
                MAIL_DEFAULT_SENDER = MAIL_USERNAME

                # 插件配置,填写插件的文件名名称,默认不启用插件。
                PLUGIN_ENABLE_FOLDERS = []

                # 配置多个数据库连接的连接串写法示例
                # HOSTNAME: 指数据库的IP地址、USERNAME:指数据库登录的用户名、PASSWORD:指数据库登录密码、PORT:指数据库开放的端口、DATABASE:指需要连接的数据库名称
                # MSSQL:    f"mssql+pymssql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=cp936"
                # MySQL:    f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
                # Oracle:   f"oracle+cx_oracle://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}"
                # SQLite    "sqlite:/// database.db"
                # Postgres f"postgresql+psycopg2://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}"
                # Oracle的第二种连接方式
                # dsnStr = cx_Oracle.makedsn({HOSTNAME}, 1521, service_name='orcl')
                # connect_str = "oracle://%s:%s@%s" % ('{USERNAME}', ' {PASSWORD}', dsnStr)

                #  在SQLALCHEMY_BINDS 中设置:'{数据库连接别名}': '{连接串}'
                # 最后在models的数据模型class中,在__tablename__前设置        __bind_key__ = '{数据库连接别名}'  即可,表示该数据模型不使用默认的数据库连接,改用“SQLALCHEMY_BINDS”中设置的其他数据库连接
                #  SQLALCHEMY_BINDS = {
                #    'testMySQL': 'mysql+pymysql://test:[email protected]:3306/test?charset=utf8',
                #    'testMsSQL': 'mssql+pymssql://test:[email protected]:1433/test?charset=cp936',
                #    'testOracle': 'oracle+cx_oracle://test:[email protected]:1521/test',
                #    'testSQLite': 'sqlite:///database.db
                # }
                """
                session
                """

                PERMANENT_SESSION_LIFETIME = timedelta(days=7)

                SESSION_TYPE = "filesystem" # 默认使用文件系统来保存会话
                SESSION_PERMANENT = False  # 会话是否持久化
                SESSION_USE_SIGNER = True  # 是否对发送到浏览器上 session 的 cookie 值进行加密
            ---
        b.代码2
            ---
            import os
            from flask import Flask
            from applications.common.script import init_script
            from applications.config import BaseConfig
            from applications.extensions import init_plugs
            from applications.view import init_bps

            def create_app():
                app = Flask(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
                # 引入配置
                app.config.from_object(BaseConfig)

                # 注册flask组件
                init_plugs(app)

                # 注册蓝图
                init_bps(app)

                # 注册命令
                init_script(app)

                return app
            ---

05.路由、视图
    a.常见信息1
        a.路由(Routing)
            路由是将URL与特定的视图函数关联起来的机制。它定义了客户端请求的URL路径与处理请求的视图函数之间的映射关系。
            定义路由: 使用@app.route('/path')装饰器来定义路由。
            示例:
            @app.route('/hello')
            def hello():
                return 'Hello, World!'
        b.视图(View)
            视图函数是实际处理客户端请求的函数。它接收请求,处理业务逻辑,并返回响应。每个视图函数都与一个或多个路由关联。
            视图函数的返回值: 可以是字符串、HTML模板、JSON数据等。
            示例:
            @app.route('/user/<username>')
            def show_user_profile(username):
                return f'User {username}'
        c.蓝图(Blueprint)
            蓝图是组织Flask应用的组件,用于将应用的不同部分分离到独立的模块中。蓝图使得应用的路由和视图可以模块化,便于管理和复用。蓝图可以在应用工厂模式中使用,有助于创建大型应用。
            定义蓝图: 使用Blueprint类来定义蓝图。
            示例:
            from flask import Blueprint
            auth = Blueprint('auth', __name__)
            @auth.route('/login')
            def login():
                return 'Login Page'
            注册蓝图: 在主应用中注册蓝图。
            from flask import Flask
            from .auth import auth
            app = Flask(__name__)
            app.register_blueprint(auth, url_prefix='/auth')
        d.红图(Application Factories)
            红图并不是Flask的官方术语,但在社区中,有时会用“红图”来指代应用工厂模式。这种模式通过创建一个函数来构建和配置Flask应用实例,使得应用的配置和初始化更加灵活和可复用。
            应用工厂: 使用一个函数来创建和配置Flask应用实例。
            示例:
            def create_app():
                app = Flask(__name__)
                app.config.from_object('config.Config')
                # 在这里注册蓝图
                from .auth import auth
                app.register_blueprint(auth, url_prefix='/auth')
                return app
        e.总结
            路由与视图: 路由通过装饰器将URL路径与视图函数关联。每当客户端访问特定URL时,Flask会调用相应的视图函数来处理请求。
            蓝图与路由、视图: 蓝图将一组相关的路由和视图组织在一起,使得应用的模块化和复用更加容易。在主应用中注册蓝图后,蓝图中的路由和视图将成为应用的一部分。
            红图与蓝图: 应用工厂模式(红图)通过函数创建应用实例,并在其中注册蓝图,从而使得应用的配置和初始化更加灵活。这种模式特别适用于大型应用和测试环境。

2.2 应用对象

01.基本应用创建
    a.简单创建方式
        a.基本语法
            from flask import Flask
            app = Flask(__name__)
        b.完整示例
            from flask import Flask

            # 创建Flask应用实例
            app = Flask(__name__)

            # 定义路由
            @app.route('/')
            def hello_world():
                return 'Hello, World!'

            # 运行应用
            if __name__ == '__main__':
                app.run(debug=True)
        c.__name__参数说明
            __name__表示当前模块名称
            用于确定应用的根目录
            影响模板和静态文件的查找路径

02.应用配置参数
    a.构造函数参数
        a.import_name参数
            # 指定应用的导入名称
            app = Flask('myapp')
            app = Flask(__name__)  # 推荐
        b.template_folder参数
            # 自定义模板文件夹
            app = Flask(__name__, template_folder='my_templates')
        c.static_folder参数
            # 自定义静态文件夹
            app = Flask(__name__, static_folder='my_static')
            app = Flask(__name__, static_url_path='/assets')
        d.static_url_path参数
            # 静态文件URL路径
            app = Flask(__name__, static_url_path='/static')
    b.应用属性
        a.重要属性访问
            print(app.name)        # 应用名称
            print(app.root_path)    # 应用根目录
            print(app.instance_path) # 实例目录
            print(app.config)       # 配置对象
        b.扩展字典
            app.extensions = {}  # 扩展存储字典
            # 扩展会将自己存储在这里

03.应用工厂模式
    a.工厂函数实现
        a.基础工厂函数
            def create_app(config_name='development'):
                app = Flask(__name__)
                app.config.from_object(config[config_name])
                return app
        b.完整工厂实现
            def create_app(config_name='development'):
                app = Flask(__name__)

                # 加载配置
                app.config.from_object(config[config_name])

                # 初始化扩展
                db.init_app(app)
                migrate.init_app(app, db)

                # 注册蓝图
                from .main import bp as main_bp
                app.register_blueprint(main_bp)

                return app
        c.使用工厂模式
            app = create_app('production')
            app.run()
    d.工厂模式优势
        支持多应用实例
        便于测试
        配置灵活
        避免循环导入

04.应用实例方法
    a.路由相关方法
        a.route装饰器
            @app.route('/user/<username>')
            def show_user_profile(username):
                return f'User {username}'
        b.add_url_rule方法
            def hello():
                return 'Hello'
            app.add_url_rule('/hello', 'hello', hello)
        c.url_for反向解析
            with app.test_request_context():
                print(url_for('show_user_profile', username='alice'))
    b.上下文方法
        a.app_context应用上下文
            with app.app_context():
                # 在应用上下文中执行代码
                print(current_app.name)
        b.test_request_context测试上下文
            with app.test_request_context():
                # 模拟请求上下文
                print(request.method)

2.3 扩展系统

01.扩展基础概念
    a.扩展定义
        扩展是为Flask添加额外功能的Python包
        通过与应用实例绑定提供服务
        遵循统一的初始化接口
    b.扩展初始化模式
        a.扩展基础类
            from flask import Flask

            class MyExtension:
                def __init__(self, app=None):
                    self.app = app
                    if app is not None:
                        self.init_app(app)

                def init_app(self, app):
                    app.my_extension = self
                    # 初始化逻辑
        b.使用扩展
            my_ext = MyExtension(app)
            # 或者
            my_ext = MyExtension()
            my_ext.init_app(app)

02.常用扩展集成
    a.Flask-SQLAlchemy数据库扩展
        a.安装和配置
            pip install Flask-SQLAlchemy

            from flask import Flask
            from flask_sqlalchemy import SQLAlchemy

            app = Flask(__name__)
            app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
            app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

            db = SQLAlchemy(app)
        b.模型定义
            class User(db.Model):
                id = db.Column(db.Integer, primary_key=True)
                username = db.Column(db.String(80), unique=True, nullable=False)
                email = db.Column(db.String(120), unique=True, nullable=False)

                def __repr__(self):
                    return f'<User {self.username}>'
        c.数据库操作
            # 创建表
            with app.app_context():
                db.create_all()

            # 插入数据
            user = User(username='admin', email='[email protected]')
            db.session.add(user)
            db.session.commit()

            # 查询数据
            users = User.query.all()
            user = User.query.filter_by(username='admin').first()
    b.Flask-WTF表单扩展
        a.安装和基础配置
            pip install Flask-WTF

            from flask_wtf import FlaskForm
            from wtforms import StringField, SubmitField
            from wtforms.validators import DataRequired

            app = Flask(__name__)
            app.config['SECRET_KEY'] = 'your-secret-key'
        b.表单类定义
            class LoginForm(FlaskForm):
                username = StringField('用户名', validators=[DataRequired()])
                password = StringField('密码', validators=[DataRequired()])
                submit = SubmitField('登录')
        c.表单处理
            @app.route('/login', methods=['GET', 'POST'])
            def login():
                form = LoginForm()
                if form.validate_on_submit():
                    return f'登录成功: {form.username.data}'
                return render_template('login.html', form=form)
    c.Flask-Login认证扩展
        a.基础配置
            pip install Flask-Login

            from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required

            login_manager = LoginManager()
            login_manager.init_app(app)
            login_manager.login_view = 'login'
        b.用户模型
            class User(UserMixin, db.Model):
                id = db.Column(db.Integer, primary_key=True)
                username = db.Column(db.String(80), unique=True, nullable=False)
        c.认证逻辑
            @login_manager.user_loader
            def load_user(user_id):
                return User.query.get(int(user_id))

            @app.route('/login')
            def login():
                user = User.query.first()
                login_user(user)
                return '登录成功'

            @app.route('/protected')
            @login_required
            def protected():
                return '需要登录才能访问'

03.扩展开发实践
    a.创建自定义扩展
        a.扩展结构
            # flask_myext/__init__.py
            from flask import current_app

            class FlaskMyExt:
                def __init__(self, app=None):
                    if app is not None:
                        self.init_app(app)

                def init_app(self, app):
                    app.config.setdefault('MYEXT_SETTING', 'default_value')
                    app.teardown_appcontext(self.teardown)
                def teardown(self, exception):
                    # 清理逻辑
                    pass
        b.扩展工厂函数
            def create_my_ext(app):
                return FlaskMyExt(app)
        c.扩展使用
            from flask_myext import FlaskMyExt
            my_ext = FlaskMyExt(app)
    b.扩展最佳实践
        a.配置项设计
            class FlaskMyExt:
                def __init__(self, app=None):
                    self.app = app
                    if app:
                        self.init_app(app)

                def init_app(self, app):
                    app.config.setdefault('MYEXT_ENABLED', True)
                    app.config.setdefault('MYEXT_TIMEOUT', 30)
        b.错误处理
            def init_app(self, app):
                app.config.setdefault('MYEXT_ENABLED', True)

                if not app.config.get('MYEXT_ENABLED'):
                    current_app.logger.warning('MyExt is disabled')
                    return
        c.日志记录
            import logging
            logger = logging.getLogger('flask_myext')

            def do_something(self):
                logger.info('Doing something with MyExt')

2.4 蓝图架构

01.蓝图基础概念
    a.蓝图定义
        蓝图是组织和路由应用组件的机制
        支持模块化应用开发
        每个蓝图可以独立开发和测试
        蓝图之间相互隔离,避免命名冲突
    b.蓝图优势
        模块化开发提高代码组织性
        支持团队协作开发
        便于代码重用和维护
        简化大型应用的管理
    c.应用场景
        大型Web应用的模块化拆分
        API服务的版本管理
        插件化系统的实现
        微服务架构的组件组织

02.蓝图创建和配置
    a.基本创建方式
        a.简单蓝图创建
            from flask import Blueprint

            # 创建用户蓝图
            user_bp = Blueprint('user', __name__)

            @user_bp.route('/profile')
            def profile():
                return 'User Profile'

            # 在主应用中注册
            from flask import Flask
            app = Flask(__name__)
            app.register_blueprint(user_bp, url_prefix='/user')
        b.蓝图参数配置
            # 带参数的蓝图创建
            admin_bp = Blueprint('admin', __name__,
                               template_folder='admin_templates',
                               static_folder='admin_static',
                               url_prefix='/admin')
        c.蓝图属性管理
            print(user_bp.name)        # 蓝图名称
            print(user_bp.import_name)  # 导入模块名
            print(user_bp.url_prefix)   # URL前缀
            print(user_bp.deferred_functions)  # 延迟函数

03.蓝图路由管理
    a.路由定义方式
        a.基础路由定义
            @user_bp.route('/login')
            def login():
                return 'Login Page'

            @user_bp.route('/logout')
            def logout():
                return 'Logout Page'

            @user_bp.route('/user/<int:user_id>')
            def get_user(user_id):
                return f'User {user_id}'
        b.HTTP方法指定
            @user_bp.route('/users', methods=['GET', 'POST'])
            def users():
                if request.method == 'POST':
                    return 'Create User'
                return 'List Users'
        c.动态路由参数
            @user_bp.route('/posts/<int:post_id>/comments/<int:comment_id>')
            def get_comment(post_id, comment_id):
                return f'Post {post_id}, Comment {comment_id}'
    b.路由命名空间
        a.端点命名规则
            # 蓝图路由的端点会自动添加蓝图名前缀
            @user_bp.route('/profile')
            def profile():
                pass
            # 端点名: user.profile

            # 可以自定义端点名
            @user_bp.route('/login', endpoint='user_login')
            def login():
                pass
            # 端点名: user.user_login
        b.路由反向解析
            from flask import url_for
            # 在视图函数中
            url_for('user.profile')  # -> /user/profile
            url_for('admin.dashboard')  # -> /admin/dashboard
    c.蓝图注册机制
        a.基础注册
            from flask import Flask
            app = Flask(__name__)

            # 注册蓝图
            app.register_blueprint(user_bp)
            app.register_blueprint(admin_bp, url_prefix='/admin')
            app.register_blueprint(api_bp, url_prefix='/api/v1')
        b.子域名注册
            # 注册到子域名
            app.register_blueprint(api_bp, subdomain='api')
            # 访问: api.example.com/users
        c.动态注册
            def register_blueprints(app):
                app.register_blueprint(user_bp, url_prefix='/user')
                app.register_blueprint(admin_bp, url_prefix='/admin')

            # 在工厂模式中使用
            def create_app():
                app = Flask(__name__)
                register_blueprints(app)
                return app

04.蓝图模板和静态文件
    a.模板文件管理
        a.蓝图专属模板
            # blueprint目录结构
            # user/
            #   __init__.py
            #   templates/
            #     profile.html
            #     settings.html
            #   static/
            #     css/
            #     js/

            from flask import Blueprint, render_template

            user_bp = Blueprint('user', __name__,
                              template_folder='templates',
                              static_folder='static')

            @user_bp.route('/profile')
            def profile():
                return render_template('profile.html')  # 使用user/templates/profile.html
        b.模板优先级
            # 模板查找顺序:
            # 1. 应用主模板目录
            # 2. 蓝图模板目录
            @user_bp.route('/dashboard')
            def dashboard():
                return render_template('dashboard.html')  # 查找顺序同上
    b.静态文件服务
        a.蓝图静态文件访问
            # 蓝图静态文件URL: /static/<filename>
            # 访问: /static/css/style.css

            @user_bp.route('/static-page')
            def static_page():
                return render_template('page.html')  # 页面中使用蓝图静态文件
        b.自定义静态URL
            user_bp = Blueprint('user', __name__,
                              static_folder='user_static',
                              static_url_path='/user_assets')
            # 静态文件URL: /user_assets/<filename>
    c.资源组织策略
        a.按功能模块组织
            project/
            ├── app/
            │   ├── __init__.py
            │   ├── main/
            │   │   ├── __init__.py
            │   │   ├── routes.py
            │   │   └── templates/
            │   ├── admin/
            │   │   ├── __init__.py
            │   │   ├── routes.py
            │   │   └── templates/
            │   └── api/
            │       ├── __init__.py
            │       └── routes.py
        b.模块化资源管理
            from .admin.routes import admin_bp
            from .api.routes import api_bp
            from .main.routes import main_bp

            def create_app():
                app = Flask(__name__)
                app.register_blueprint(main_bp)
                app.register_blueprint(admin_bp, url_prefix='/admin')
                app.register_blueprint(api_bp, url_prefix='/api')
                return app

05.蓝图高级特性
    a.蓝图错误处理
        a.蓝图级错误处理器
            @user_bp.errorhandler(404)
            def user_not_found(error):
                return 'User page not found', 404

            @user_bp.errorhandler(500)
            def user_server_error(error):
                return 'User server error', 500

            @user_bp.app_errorhandler(403)
            def app_forbidden(error):
                return 'Access forbidden', 403
        b.应用级错误处理
            @app.errorhandler(404)
            def not_found(error):
                return render_template('404.html'), 404
    b.蓝图钩子函数
        a.蓝图级钩子
            @user_bp.before_request
            def before_user_request():
                # 在用户蓝图所有请求前执行
                g.user_start_time = time.time()

            @user_bp.after_request
            def after_user_request(response):
                # 在用户蓝图所有请求后执行
                execution_time = time.time() - g.user_start_time
                response.headers['X-Execution-Time'] = str(execution_time)
                return response

            @user_bp.teardown_request
            def teardown_user_request(exception):
                # 请求清理
                pass
        c.应用级钩子
            @app.before_request
            def before_request():
                # 在所有请求前执行
                pass
    c.蓝图上下文处理
        a.蓝图上下文处理器
            @user_bp.context_processor
            def inject_user_vars():
                return {'user_name': 'John Doe', 'user_role': 'admin'}

            @user_bp.app_context_processor
            def inject_global_vars():
                return {'site_name': 'My Flask App'}
        b.上下文数据使用
            @user_bp.route('/dashboard')
            def dashboard():
                # 在模板中可以直接使用 user_name, user_role, site_name
                return render_template('dashboard.html')

06.蓝图最佳实践
    a.项目结构设计
        a.推荐目录结构
            myapp/
            ├── app/
            │   ├── __init__.py          # 工厂函数
            │   ├── models.py            # 数据模型
            │   ├── extensions.py        # 扩展实例
            │   ├── main/                # 主应用蓝图
            │   │   ├── __init__.py
            │   │   ├── forms.py
            │   │   └── views.py
            │   ├── admin/               # 管理蓝图
            │   │   ├── __init__.py
            │   │   └── views.py
            │   └── api/                 # API蓝图
            │       ├── __init__.py
            │       └── views.py
            ├── migrations/               # 数据库迁移
            ├── tests/                   # 测试文件
            └── config.py                # 配置文件
        b.模块初始化
            # app/__init__.py
            from flask import Flask
            from .extensions import db
            from .main import bp as main_bp
            from .admin import bp as admin_bp
            from .api import bp as api_bp

            def create_app(config_object='config.Development'):
                app = Flask(__name__)
                app.config.from_object(config_object)

                db.init_app(app)

                app.register_blueprint(main_bp)
                app.register_blueprint(admin_bp, url_prefix='/admin')
                app.register_blueprint(api_bp, url_prefix='/api')

                return app
    b.性能优化策略
        a.延迟加载
            def register_blueprints(app):
                # 延迟导入和注册蓝图
                from .admin.views import bp as admin_bp
                app.register_blueprint(admin_bp, url_prefix='/admin')
        b.路由优化
            # 使用路由缓存
            app.url_map._rules_by_endpoint.clear()
            app.url_map.update()
    c.开发调试支持
        a.蓝图调试配置
            if app.debug:
                app.config['TEMPLATES_AUTO_RELOAD'] = True
                app.jinja_env.auto_reload = True
        b.蓝图性能监控
            @user_bp.before_request
            def monitor_request():
                g.start_time = time.time()

            @user_bp.after_request
            def log_request(response):
                duration = time.time() - g.start_time
                app.logger.info(f'Request took {duration:.3f}s')
                return response

2.5 上下文管理

01.上下文基础概念
    a.上下文定义
        上下文是存储请求和应用相关数据的机制
        提供线程安全的数据访问
        支持数据的生命周期管理
        确保多请求环境下的数据隔离
    b.上下文类型
        应用上下文:存储应用级别数据
        请求上下文:存储请求相关数据
        上下文栈:管理多个上下文实例
        上下文代理:提供全局访问接口
    c.上下文作用
        全局对象的线程安全访问
        请求和应用数据的临时存储
        资源管理和清理
        配置和状态数据的共享

02.应用上下文详解
    a.current_app对象
        a.基本访问方式
            from flask import current_app

            @app.route('/app-info')
            def app_info():
                return {
                    'name': current_app.name,
                    'debug': current_app.debug,
                    'config': dict(current_app.config)
                }
        b.应用配置访问
            @app.route('/db-config')
            def db_config():
                return {
                    'database_uri': current_app.config.get('DATABASE_URI'),
                    'pool_size': current_app.config.get('POOL_SIZE')
                }
        c.应用方法调用
            @app.route('/create-report')
            def create_report():
                # 使用应用实例的方法
                with current_app.open_resource('templates/report.html') as f:
                    return f.read()
    b.g对象详解
        a.数据存储和传递
            @app.before_request
            def load_user_data():
                g.user_id = session.get('user_id')
                g.user_agent = request.headers.get('User-Agent')
                g.request_start = time.time()

            @app.route('/profile')
            def profile():
                if g.user_id:
                    user = User.query.get(g.user_id)
                    return f'Welcome {user.name}'
                return 'Please login'
        b.临时数据管理
            @app.route('/process-data')
            def process_data():
                g.processing_data = {'step1': 'completed', 'step2': 'in_progress'}

                # 在其他函数中使用g对象数据
                result = process_step2()

                return result

            def process_step2():
                return {'data': g.processing_data, 'timestamp': time.time()}
    c.应用上下文生命周期
        a.手动创建上下文
            def init_database():
                with app.app_context():
                    # 在应用上下文中执行
                    db.create_all()
                    print('Database initialized')
        b.上下文验证
            def safe_operation():
                try:
                    with app.app_context():
                        # 确保在应用上下文中
                        current_app.logger.info('Operation started')
                        # 执行操作
                except Exception as e:
                    current_app.logger.error(f'Operation failed: {e}')

03.请求上下文详解
    a.request对象
        a.请求数据访问
            @app.route('/request-info')
            def request_info():
                return {
                    'method': request.method,
                    'url': request.url,
                    'path': request.path,
                    'args': dict(request.args),
                    'form': dict(request.form),
                    'json': request.get_json(),
                    'headers': dict(request.headers)
                }
        b.文件上传处理
            @app.route('/upload', methods=['POST'])
            def upload_file():
                if 'file' not in request.files:
                    return 'No file part'

                file = request.files['file']
                if file.filename:
                    filename = secure_filename(file.filename)
                    file.save(f'uploads/{filename}')
                    return f'File {filename} uploaded successfully'
            return 'No selected file'
        c.请求头和参数处理
            @app.route('/api/data')
            def api_data():
                api_key = request.headers.get('X-API-Key')
                content_type = request.headers.get('Content-Type')
                user_id = request.args.get('user_id')

                return {
                    'api_key': api_key,
                    'content_type': content_type,
                    'user_id': user_id
                }
    b.session对象
        a.会话数据操作
            @app.route('/login', methods=['POST'])
            def login():
                username = request.form.get('username')
                if authenticate_user(username):
                    session['user_id'] = get_user_id(username)
                    session['username'] = username
                    session['login_time'] = time.time()
                    return 'Login successful'
                return 'Login failed'
        b.会话管理
            @app.route('/logout')
            def logout():
                session.pop('user_id', None)
                session.pop('username', None)
                session.clear()  # 清除所有会话数据
                return 'Logged out'
        c.会话安全配置
            app.config['SESSION_COOKIE_SECURE'] = True      # HTTPS only
            app.config['SESSION_COOKIE_HTTPONLY'] = True    # 防止 XSS
            app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'   # CSRF 保护
            app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)

04.上下文栈机制
    a.LocalStack实现
        a.基础概念
            from werkzeug.local import LocalStack

            # Flask内部使用LocalStack管理上下文
            # 每个线程都有独立的上下文栈
        b.手动上下文操作
            def test_with_context():
                with app.app_context():
                    # 当前上下文在栈顶
                    print(current_app.name)

                    with app.test_request_context():
                        # 请求上下文推入栈顶
                        print(request.method)
                        print(current_app.name)

                    # 请求上下文弹出,应用上下文仍在栈顶
                    print(current_app.name)
        c.上下文嵌套
            def nested_contexts():
                with app.app_context():
                    with app.test_request_context():
                        # 深层嵌套上下文
                        pass  # 内部操作
                # 上下文自动弹出
    b.上下文代理机制
        a.代理对象访问
            # current_app 和 request 实际上是代理对象
            from flask import current_app, request, g

            # 代理对象会自动指向当前激活的上下文
            def get_app_info():
                return current_app.config  # 自动获取当前应用
        b.延迟加载特性
            def check_context():
                # 只有实际访问时才会获取上下文
                try:
                    # 此时如果不在上下文中会抛出 RuntimeError
                    print(current_app.name)
                except RuntimeError as e:
                    print(f"Context error: {e}")
    c.多应用上下文
        a.多应用支持
            app1 = Flask('app1')
            app2 = Flask('app2')

            def handle_request(request_data, app_name):
                if app_name == 'app1':
                    app = app1
                else:
                    app = app2

                with app.app_context():
                    with app.test_request_context():
                        # 处理请求
                        return current_app.name
        b.上下文隔离
            def test_isolation():
                with app1.app_context():
                    app1_var = 'App1 data'

                with app2.app_context():
                    app2_var = 'App2 data'
                    # 在这里只能访问 app2 的上下文
                    print(current_app.name)  # 输出 'app2'

05.上下文生命周期
    a.创建阶段
        a.请求处理流程
            @app.before_request
            def setup_context():
                g.start_time = time.time()
                g.request_id = str(uuid.uuid4())
                current_app.logger.info(f"Request {g.request_id} started")
        b.上下文初始化
            def init_request_context():
                with app.test_request_context():
                    # 模拟上下文创建
                    print("Context created")

                    # 初始化数据
                    g.user_agent = "Test Agent"
                    g.ip_address = "127.0.0.1"
    b.激活阶段
        a.上下文数据访问
            @app.route('/data')
            def get_data():
                # 在激活阶段可以安全访问所有上下文数据
                return {
                    'request_id': g.request_id,
                    'start_time': g.start_time,
                    'user_agent': g.user_agent,
                    'app_name': current_app.name
                }
        b.上下文修改
            @app.route('/update-context')
            def update_context():
                g.new_data = "Updated at " + str(time.time())
                session['last_access'] = time.time()
                return "Context updated"
    c.销毁阶段
        a.清理资源
            @app.teardown_appcontext
            def cleanup_appcontext(exception):
                # 应用上下文销毁时的清理
                current_app.logger.info("App context teardown")
                # 清理资源
                pass

            @app.teardown_request
            def cleanup_request(exception):
                # 请求上下文销毁时的清理
                duration = time.time() - g.start_time
                current_app.logger.info(f"Request completed in {duration:.3f}s")
                # 记录请求结束
                pass
        b.异常处理
            @app.teardown_appcontext
            def handle_teardown_error(exception):
                if exception:
                    current_app.logger.error(f"Context teardown error: {exception}")
                # 清理工作,无论是否有异常
                pass

06.高级上下文操作
    a.手动上下文管理
        a.脚本中的上下文
            def init_database():
                with app.app_context():
                    from models import User, Post
                    db.create_all()

                    # 创建初始数据
                    admin = User(username='admin', email='[email protected]')
                    db.session.add(admin)
                    db.session.commit()
        b.测试上下文
            def test_user_creation():
                with app.test_client() as client:
                    with app.app_context():
                        # 测试环境下的上下文
                        user = User(username='testuser', email='[email protected]')
                        db.session.add(user)
                        db.session.commit()

                        assert User.query.count() == 1
        c.命令行工具上下文
            import click
            from flask import current_app

            @click.command()
            @with_appcontext
            def init_db():
                """Initialize the database."""
                db.create_all()
                click.echo('Initialized the database.')
    b.上下文测试策略
        a.上下文隔离测试
            def test_context_isolation():
                with app.app_context():
                    app_config = current_app.config['TESTING']

                with app.test_request_context():
                    request_data = request.args.get('test', 'default')

                # 测试上下文已经销毁
                try:
                    print(request.args)  # 会抛出 RuntimeError
                except RuntimeError:
                    pass  # 预期的错误
        b.上下文数据验证
            def test_context_data():
                with app.test_request_context(query_string={'user': 'alice'}):
                    assert request.args['user'] == 'alice'
                    assert g.get('test_data') is None

                    g.test_data = 'test value'

                # 上下文外无法访问
                assert g.get('test_data') is None
    c.上下文性能优化
        a.上下文重用
            from contextlib import contextmanager

            @contextmanager
            def request_context_cache():
                """缓存请求上下文以提高性能"""
                with app.test_request_context() as ctx:
                    yield ctx

            def batch_process():
                with request_context_cache():
                    # 批量操作重用上下文
                    for item in large_dataset:
                        process_item(item)
        b.上下文监控
            def monitor_context_performance():
                @app.before_request
                def start_timing():
                    g.context_start = time.time()

                @app.teardown_request
                def end_timing(exception):
                    duration = time.time() - g.context_start
                    if duration > 1.0:  # 超过1秒的请求
                        current_app.logger.warning(f"Slow request: {duration:.3f}s")

2.6 配置系统

01.配置对象体系
    a.Config类详解
        a.基本属性和操作
            app = Flask(__name__)

            # 配置访问
            app.config['DEBUG'] = True
            app.config['SECRET_KEY'] = 'my-secret-key'
            print(app.config['DEBUG'])  # True

            # 配置迭代
            for key, value in app.config.items():
                print(f"{key}: {value}")
        b.配置项类型支持
            app.config.update({
                'DEBUG': True,                    # 布尔值
                'PORT': 5000,                     # 整数
                'DATABASE_URI': 'sqlite:///app.db',  # 字符串
                'API_TIMEOUT': 30.5,              # 浮点数
                'ALLOWED_HOSTS': ['localhost', '127.0.0.1'],  # 列表
                'RETRY_ATTEMPTS': 3               # 字典
            })
        c.配置验证
            def validate_config():
                required_keys = ['SECRET_KEY', 'DATABASE_URI']
                for key in required_keys:
                    if key not in app.config:
                        raise ValueError(f"Missing required config: {key}")

                if len(app.config['SECRET_KEY']) < 16:
                    raise ValueError("SECRET_KEY too short")
    b.配置方法接口
        a.from_object方法
            class DevelopmentConfig:
                DEBUG = True
                SECRET_KEY = 'dev-secret-key'
                DATABASE_URI = 'sqlite:///dev.db'

            class ProductionConfig:
                DEBUG = False
                SECRET_KEY = os.environ.get('SECRET_KEY')
                DATABASE_URI = os.environ.get('DATABASE_URL')

            app.config.from_object(DevelopmentConfig)
        b.from_pyfile方法
            # config.py 文件
            # DEBUG = True
            # SECRET_KEY = 'config-secret'
            # DATABASE_URI = 'sqlite:///config.db'

            app.config.from_pyfile('config.py')
        c.from_json方法
            # config.json 文件
            # {
            #   "DEBUG": true,
            #   "SECRET_KEY": "json-secret",
            #   "DATABASE_URI": "sqlite:///json.db"
            # }

            app.config.from_json('config.json')
        d.from_envvar方法
            # 设置环境变量
            # export APP_CONFIG_SETTINGS="/path/to/settings.py"
            app.config.from_envvar('APP_CONFIG_SETTINGS')
    c.配置属性操作
        a.字典式操作
            # 添加配置
            app.config['NEW_SETTING'] = 'value'

            # 检查存在性
            if 'DEBUG' in app.config:
                print('Debug mode is enabled')

            # 获取配置,带默认值
            timeout = app.config.get('TIMEOUT', 30)
        b.配置更新
            # 批量更新
            new_config = {
                'DEBUG': False,
                'LOG_LEVEL': 'INFO'
            }
            app.config.update(new_config)
        c.配置继承
            class BaseConfig:
                DEBUG = False
                SECRET_KEY = 'base-secret'

            class DevelopmentConfig(BaseConfig):
                DEBUG = True
                DATABASE_URI = 'sqlite:///dev.db'

            app.config.from_object(DevelopmentConfig)

02.环境配置管理
    a.开发环境配置
        a.config/development.py
            class DevelopmentConfig:
                DEBUG = True
                TESTING = False

                # 开发数据库
                SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
                SQLALCHEMY_ECHO = True

                # 开发服务器
                HOST = '127.0.0.1'
                PORT = 5000

                # 开发安全设置
                SECRET_KEY = 'dev-secret-key-change-in-production'

                # 开发日志
                LOG_LEVEL = 'DEBUG'
                LOG_TO_STDOUT = True
        b.配置应用
            def create_development_app():
                app = Flask(__name__)
                app.config.from_object('config.development.DevelopmentConfig')
                return app
        c.开发环境特性
            with app.app_context():
                print(f"Debug mode: {app.config['DEBUG']}")
                print(f"Database: {app.config['SQLALCHEMY_DATABASE_URI']}")
                print(f"Log level: {app.config['LOG_LEVEL']}")
    b.测试环境配置
        a.config/testing.py
            import tempfile
            import os

            class TestingConfig:
                TESTING = True
                DEBUG = True

                # 内存数据库
                SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
                SQLALCHEMY_ECHO = False

                # 测试专用设置
                WTF_CSRF_ENABLED = False  # 禁用CSRF用于测试
                SERVER_NAME = 'localhost.localdomain'

                # 临时文件路径
                UPLOADS_DEFAULT_DEST = tempfile.mkdtemp()

                # 测试日志
                LOG_LEVEL = 'ERROR'
        b.测试应用创建
            def create_test_app():
                app = Flask(__name__)
                app.config.from_object('config.testing.TestingConfig')
                return app
        c.测试环境验证
            def test_config_validation():
                app = create_test_app()
                with app.app_context():
                    assert app.config['TESTING'] is True
                    assert app.config['SQLALCHEMY_DATABASE_URI'].startswith('sqlite://')
                    print("Test configuration validated successfully")
    c.生产环境配置
        a.config/production.py
            import os

            class ProductionConfig:
                DEBUG = False
                TESTING = False

                # 生产数据库
                SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
                SQLALCHEMY_ECHO = False
                SQLALCHEMY_POOL_SIZE = 10
                SQLALCHEMY_POOL_TIMEOUT = 30

                # 生产安全设置
                SECRET_KEY = os.environ.get('SECRET_KEY')
                SESSION_COOKIE_SECURE = True
                SESSION_COOKIE_HTTPONLY = True
                SESSION_COOKIE_SAMESITE = 'Lax'

                # 生产日志
                LOG_LEVEL = 'WARNING'
                LOG_FILE = '/var/log/flask/app.log'

                # 性能设置
                SEND_FILE_MAX_AGE_DEFAULT = 31536000  # 1年
        b.生产环境应用
            def create_production_app():
                app = Flask(__name__)
                app.config.from_object('config.production.ProductionConfig')

                # 验证必要的环境变量
                required_vars = ['SECRET_KEY', 'DATABASE_URL']
                missing_vars = [var for var in required_vars if not os.environ.get(var)]
                if missing_vars:
                    raise ValueError(f"Missing environment variables: {missing_vars}")

                return app
        d.生产环境监控
            def setup_production_logging(app):
                import logging
                from logging.handlers import RotatingFileHandler

                file_handler = RotatingFileHandler(
                    app.config['LOG_FILE'],
                    maxBytes=10240000,  # 10MB
                    backupCount=10
                )
                file_handler.setFormatter(logging.Formatter(
                    '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
                ))
                file_handler.setLevel(logging.INFO)
                app.logger.addHandler(file_handler)

03.配置加载策略
    a.配置工厂模式
        a.配置工厂
            class ConfigFactory:
                @staticmethod
                def create_config(config_name):
                    config_map = {
                        'development': DevelopmentConfig,
                        'testing': TestingConfig,
                        'production': ProductionConfig
                    }

                    if config_name not in config_map:
                        raise ValueError(f"Unknown config: {config_name}")

                    return config_map[config_name]()
        b.使用工厂
            def create_app(config_name=None):
                if config_name is None:
                    config_name = os.environ.get('FLASK_ENV', 'development')

                app = Flask(__name__)
                config = ConfigFactory.create_config(config_name)
                app.config.from_object(config)
                return app
        c.动态配置加载
            app = create_app(config_name='production')  # 从环境变量或默认
    b.配置文件结构
        a.推荐目录结构
            config/
            ├── __init__.py
            ├── base.py           # 基础配置
            ├── development.py    # 开发环境
            ├── testing.py        # 测试环境
            ├── production.py     # 生产环境
            └── defaults.py       # 默认值
        b.基础配置类
            # config/base.py
            import os
            from datetime import timedelta

            class BaseConfig:
                SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
                PERMANENT_SESSION_LIFETIME = timedelta(days=7)

                # 数据库配置
                SQLALCHEMY_TRACK_MODIFICATIONS = False

                # 邮件配置
                MAIL_SERVER = os.environ.get('MAIL_SERVER', 'localhost')
                MAIL_PORT = int(os.environ.get('MAIL_PORT', 587))
                MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
                MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
                MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')

                # 应用配置
                ITEMS_PER_PAGE = 20
                LANGUAGES = ['en', 'zh']
                MS_TRANSLATOR_KEY = os.environ.get('MS_TRANSLATOR_KEY')
        c.配置继承
            # config/development.py
            from .base import BaseConfig

            class DevelopmentConfig(BaseConfig):
                DEBUG = True
                SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
                    'sqlite:///' + os.path.join(os.path.dirname(__file__), '..', 'dev.db')

                # 开发专用
                TEMPLATES_AUTO_RELOAD = True
                SEND_FILE_MAX_AGE_DEFAULT = 0
    c.配置优先级
        a.加载顺序示例
            def load_app_config(app):
                # 1. 默认配置
                app.config.from_object('config.defaults.DefaultConfig')

                # 2. 配置文件
                app.config.from_pyfile('config.py', silent=True)

                # 3. 环境特定配置
                env = os.environ.get('FLASK_ENV', 'development')
                app.config.from_object(f'config.{env}.{env.capitalize()}Config', silent=True)

                # 4. 环境变量(最高优先级)
                if os.environ.get('FLASK_CONFIG_FILE'):
                    app.config.from_envvar('FLASK_CONFIG_FILE')
        b.配置冲突处理
            def handle_config_conflicts(app):
                # 检查配置冲突
                if app.config['DEBUG'] and not app.config.get('TESTING'):
                    app.logger.warning("Debug mode enabled in non-testing environment")

                # 验证数据库配置
                if not app.config.get('SQLALCHEMY_DATABASE_URI'):
                    app.logger.error("Database URI not configured")
                    raise ValueError("Database configuration missing")

04.敏感信息处理
    a.环境变量管理
        a..env文件支持
            # 安装: pip install python-dotenv
            # .env 文件
            FLASK_APP=run.py
            FLASK_ENV=development
            SECRET_KEY=your-secret-key-here
            DATABASE_URL=sqlite:///app.db
            [email protected]
            MAIL_PASSWORD=your-app-password
        b.环境变量加载
            from dotenv import load_dotenv

            # 自动加载.env文件
            load_dotenv()

            app = Flask(__name__)
            # 现在可以使用 os.environ 访问环境变量
            app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
            app.config['DATABASE_URL'] = os.environ.get('DATABASE_URL')
        c.生产环境变量
            # 使用 docker-compose.yml 或环境变量文件
            # 不要在代码中硬编码敏感信息
            def load_production_config():
                app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
                app.config['DATABASE_URL'] = os.environ.get('DATABASE_URL')
                app.config['REDIS_URL'] = os.environ.get('REDIS_URL')

                if not all([app.config['SECRET_KEY'], app.config['DATABASE_URL']]):
                    raise ValueError("Missing required environment variables")
    b.配置文件分离
        a.公共配置
            # config/common.py - 不包含敏感信息
            COMMON_CONFIG = {
                'DEBUG': False,
                'PORT': 5000,
                'HOST': '0.0.0.0',
                'TEMPLATES_AUTO_RELOAD': False
            }
        b.私有配置
            # config/private.py - 包含敏感信息,不加入版本控制
            PRIVATE_CONFIG = {
                'SECRET_KEY': 'super-secret-key',
                'DATABASE_PASSWORD': 'db-password',
                'API_KEYS': {
                    'google_maps': 'google-api-key',
                    'stripe': 'stripe-secret-key'
                }
            }
        c.配置合并
            def load_separated_config():
                # 加载公共配置
                app.config.update(COMMON_CONFIG)

                # 加载私有配置(如果存在)
                try:
                    from config.private import PRIVATE_CONFIG
                    app.config.update(PRIVATE_CONFIG)
                except ImportError:
                    app.logger.warning("Private config not found, using defaults")
    c.安全配置项
        a.密钥管理
            import secrets

            def generate_secret_key():
                """生成安全的随机密钥"""
                return secrets.token_hex(32)

            # 在应用启动时生成密钥
            if not app.config.get('SECRET_KEY'):
                app.config['SECRET_KEY'] = generate_secret_key()
                app.logger.warning("Generated new SECRET_KEY, save it for future use")
        b.数据库安全
            # 数据库连接安全配置
            app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
            app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
                'pool_pre_ping': True,
                'pool_recycle': 300,
                'pool_size': 10,
                'max_overflow': 20
            }
        c.文件上传安全
            # 文件上传安全配置
            app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB
            app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif', '.pdf']
            app.config['UPLOAD_FOLDER'] = '/var/www/uploads'

05.动态配置更新
    a.热重载机制
        a.配置监控
            import os
            from watchdog.observers import Observer
            from watchdog.events import FileSystemEventHandler

            class ConfigReloadHandler(FileSystemEventHandler):
                def on_modified(self, event):
                    if event.src_path.endswith('.py') and 'config' in event.src_path:
                        app.logger.info(f"Configuration file changed: {event.src_path}")
                        # 重新加载配置
                        reload_config()

            def setup_config_watcher():
                observer = Observer()
                observer.schedule(ConfigReloadHandler(), path='config/', recursive=True)
                observer.start()
        b.配置重新加载
            def reload_config():
                try:
                    # 重新加载配置文件
                    app.config.from_pyfile('config.py')
                    app.logger.info("Configuration reloaded successfully")
                except Exception as e:
                    app.logger.error(f"Failed to reload config: {e}")
    b.运行时配置更新
        a.配置API端点
            @app.route('/admin/config', methods=['GET', 'POST'])
            def manage_config():
                if request.method == 'GET':
                    # 返回当前配置(过滤敏感信息)
                    safe_config = {k: v for k, v in app.config.items()
                                 if 'SECRET' not in k and 'PASSWORD' not in k}
                    return jsonify(safe_config)

                elif request.method == 'POST':
                    # 更新配置(需要管理员权限)
                    if not current_user.is_admin:
                        return jsonify({'error': 'Admin access required'}), 403

                    new_config = request.get_json()
                    for key, value in new_config.items():
                        if not is_sensitive_config(key):
                            app.config[key] = value

                    return jsonify({'message': 'Configuration updated'})
        b.配置验证
            def is_sensitive_config(key):
                """检查配置项是否为敏感信息"""
                sensitive_keys = ['SECRET', 'PASSWORD', 'KEY', 'TOKEN', 'URI']
                return any(sensitive in key.upper() for sensitive in sensitive_keys)
        c.配置通知
            import redis

            def notify_config_change(key, old_value, new_value):
                """配置变更通知"""
                try:
                    r = redis.Redis(host='localhost', port=6379, db=0)
                    r.publish('config_changes', {
                        'key': key,
                        'old_value': old_value,
                        'new_value': new_value,
                        'timestamp': time.time()
                    })
                except Exception as e:
                    app.logger.error(f"Failed to notify config change: {e}")

06.配置最佳实践
    a.配置组织策略
        a.模块化配置
            # config/
            # ├── __init__.py
            # ├── database.py     # 数据库相关配置
            # ├── email.py        # 邮件配置
            # ├── security.py     # 安全配置
            # └── logging.py      # 日志配置

            # config/database.py
            DATABASE_CONFIG = {
                'development': {
                    'SQLALCHEMY_DATABASE_URI': 'sqlite:///dev.db',
                    'SQLALCHEMY_ECHO': True
                },
                'production': {
                    'SQLALCHEMY_DATABASE_URI': os.environ.get('DATABASE_URL'),
                    'SQLALCHEMY_ECHO': False,
                    'SQLALCHEMY_POOL_SIZE': 20
                }
            }
        b.配置命名规范
            # 使用大写字母和下划线
            # 按功能分组前缀
            DATABASE_URL = 'postgresql://localhost/myapp'
            DATABASE_POOL_SIZE = 20
            DATABASE_POOL_TIMEOUT = 30

            EMAIL_SERVER = 'smtp.gmail.com'
            EMAIL_PORT = 587
            EMAIL_USE_TLS = True

            CACHE_TYPE = 'redis'
            CACHE_REDIS_URL = 'redis://localhost:6379/0'
        c.配置文档化
            # config/__init__.py
            """
            配置模块文档

            开发环境配置:
                DEBUG: True/False - 调试模式开关
                SQLALCHEMY_ECHO: True/False - SQL语句日志输出

            生产环境配置:
                SECRET_KEY: str - 应用密钥(必需)
                DATABASE_URL: str - 数据库连接字符串(必需)
                LOG_LEVEL: str - 日志级别 (DEBUG/INFO/WARNING/ERROR)
            """
    b.配置验证和测试
        a.配置验证函数
            def validate_app_config(app):
                """验证应用配置的完整性"""
                errors = []

                # 必需配置项
                required_configs = ['SECRET_KEY']
                for config in required_configs:
                    if not app.config.get(config):
                        errors.append(f"Missing required config: {config}")

                # 配置格式验证
                if app.config.get('PORT'):
                    try:
                        port = int(app.config['PORT'])
                        if not (1 <= port <= 65535):
                            errors.append("PORT must be between 1 and 65535")
                    except ValueError:
                        errors.append("PORT must be a valid integer")

                # 数据库URL格式验证
                db_uri = app.config.get('SQLALCHEMY_DATABASE_URI')
                if db_uri and not db_uri.startswith(('sqlite://', 'postgresql://', 'mysql://')):
                    errors.append("Invalid database URI format")

                if errors:
                    raise ValueError("Configuration validation failed:\n" + "\n".join(errors))

                return True
        b.配置测试
            import unittest
            from config import DevelopmentConfig, ProductionConfig

            class ConfigTestCase(unittest.TestCase):
                def test_development_config(self):
                    config = DevelopmentConfig()
                    self.assertTrue(config.DEBUG)
                    self.assertTrue(config.SQLALCHEMY_ECHO)

                def test_production_config(self):
                    config = ProductionConfig()
                    self.assertFalse(config.DEBUG)
                    self.assertFalse(config.SQLALCHEMY_ECHO)
                    self.assertFalse(config.TEMPLATES_AUTO_RELOAD)

                def test_config_validation(self):
                    from app import create_app

                    app = create_app('testing')
                    with app.app_context():
                        self.assertTrue(validate_app_config(app))
    c.配置性能优化
        a.配置缓存
            from functools import lru_cache

            @lru_cache(maxsize=1)
            def get_database_config():
                """缓存数据库配置"""
                return {
                    'engine_options': app.config.get('SQLALCHEMY_ENGINE_OPTIONS', {}),
                    'pool_size': app.config.get('SQLALCHEMY_POOL_SIZE', 5)
                }
        b.延迟加载
            def get_expensive_config():
                """延迟加载昂贵配置"""
                if not hasattr(g, 'expensive_config'):
                    g.expensive_config = load_complex_configuration()
                return g.expensive_config

            @app.route('/use-config')
            def use_config():
                config = get_expensive_config()
                return jsonify(config)

3 核心组件

3.1 路由系统

01.路由定义基础
    a.基本路由装饰器
        a.简单路由定义语法
            from flask import Flask, jsonify

            app = Flask(__name__)

            # 最简单的路由定义
            @app.route('/')
            def index():
                return 'Hello, World!'

            # 带参数的路由
            @app.route('/user/<username>')
            def show_user_profile(username):
                return f'User: {username}'

            # 多参数路由
            @app.route('/post/<int:post_id>/comment/<int:comment_id>')
            def show_comment(post_id, comment_id):
                return f'Post {post_id}, Comment {comment_id}'
        b.路由参数类型转换器
            from flask import Flask

            app = Flask(__name__)

            # 内置类型转换器
            @app.route('/int/<int:user_id>')          # 整数
            def show_int(user_id):
                return f'Integer: {user_id}, type: {type(user_id)}'

            @app.route('/float/<float:price>')        # 浮点数
            def show_float(price):
                return f'Price: {price}, type: {type(price)}'

            @app.route('/path/<path:subpath>')        # 路径(包含斜杠)
            def show_path(subpath):
                return f'Subpath: {subpath}'

            @app.route('/uuid/<uuid:post_id>')        # UUID
            def show_uuid(post_id):
                return f'UUID: {post_id}'
        c.自定义类型转换器
            from werkzeug.routing import BaseConverter
            from flask import Flask

            class ListConverter(BaseConverter):
                def to_python(self, value):
                    return value.split(',')

                def to_url(self, values):
                    return ','.join(BaseConverter.to_url(value)
                                    for value in values)

            # 注册自定义转换器
            app.url_map.converters['list'] = ListConverter

            @app.route('/users/<list:username_list>')
            def show_users(username_list):
                return f'Users: {username_list}'

02.HTTP方法支持
    a.多方法路由定义
        a.GET和POST方法
            from flask import Flask, request, jsonify

            app = Flask(__name__)

            # 默认只支持GET方法
            @app.route('/api/data')
            def get_data():
                return {'message': 'GET request'}

            # 明确指定支持的方法
            @app.route('/api/users', methods=['GET', 'POST'])
            def handle_users():
                if request.method == 'POST':
                    # 处理POST请求,创建新用户
                    data = request.get_json()
                    if not data or 'username' not in data:
                        return jsonify({'error': 'Username required'}), 400

                    # 模拟创建用户
                    user = {
                        'id': 123,
                        'username': data['username'],
                        'email': data.get('email', ''),
                        'created_at': '2024-01-01'
                    }
                    return jsonify(user), 201
                else:
                    # 处理GET请求,返回用户列表
                    users = [
                        {'id': 1, 'username': 'alice'},
                        {'id': 2, 'username': 'bob'}
                    ]
                    return jsonify(users)
        b.完整RESTful API
            from flask import Flask, request, jsonify

            app = Flask(__name__)

            # 模拟数据存储
            users = {}
            next_id = 1

            # GET /users - 获取所有用户
            # POST /users - 创建新用户
            # GET /users/<id> - 获取特定用户
            # PUT /users/<id> - 更新特定用户
            # DELETE /users/<id> - 删除特定用户
            @app.route('/users', methods=['GET', 'POST'])
            @app.route('/users/<int:user_id>', methods=['GET', 'PUT', 'DELETE'])
            def handle_users(user_id=None):
                global next_id

                if request.method == 'GET':
                    if user_id is None:
                        # 返回所有用户
                        return jsonify(list(users.values()))
                    else:
                        # 返回特定用户
                        user = users.get(user_id)
                        if user:
                            return jsonify(user)
                        else:
                            return jsonify({'error': 'User not found'}), 404

                elif request.method == 'POST':
                    # 创建新用户
                    data = request.get_json()
                    if not data or 'username' not in data:
                        return jsonify({'error': 'Username required'}), 400

                    user = {
                        'id': next_id,
                        'username': data['username'],
                        'email': data.get('email', ''),
                        'age': data.get('age', 0),
                        'created_at': '2024-01-01'
                    }
                    users[next_id] = user
                    next_id += 1
                    return jsonify(user), 201

                elif request.method == 'PUT':
                    # 更新用户
                    user = users.get(user_id)
                    if not user:
                        return jsonify({'error': 'User not found'}), 404

                    data = request.get_json()
                    if not data:
                        return jsonify({'error': 'No data provided'}), 400

                    # 更新字段
                    user.update({
                        'username': data.get('username', user['username']),
                        'email': data.get('email', user['email']),
                        'age': data.get('age', user['age'])
                    })
                    return jsonify(user)

                elif request.method == 'DELETE':
                    # 删除用户
                    user = users.pop(user_id, None)
                    if user:
                        return jsonify({'message': 'User deleted successfully'})
                    else:
                        return jsonify({'error': 'User not found'}), 404
        c.方法验证和安全处理
            from flask import Flask, request, jsonify, abort

            app = Flask(__name__)

            @app.route('/api/secure', methods=['POST', 'PUT', 'DELETE'])
            def secure_endpoint():
                # 验证Content-Type
                if request.method in ['POST', 'PUT']:
                    if not request.is_json:
                        abort(415, description='Content-Type must be application/json')

                # 验证Authorization头
                auth_header = request.headers.get('Authorization')
                if not auth_header or not auth_header.startswith('Bearer '):
                    abort(401, description='Authorization header required')

                token = auth_header[7:]  # 去掉 "Bearer "
                if not validate_token(token):  # 假设的token验证函数
                    abort(401, description='Invalid token')

                return jsonify({'message': 'Authorized request processed'})

03.动态路由和高级匹配
    a.正则表达式路由
        a.使用werkzeug路由器
            from flask import Flask
            from werkzeug.routing import BaseConverter
            import re

            app = Flask(__name__)

            class RegexConverter(BaseConverter):
                def __init__(self, url_map, *items):
                    super(RegexConverter, self).__init__(url_map)
                    self.regex = items[0]

            # 注册正则表达式转换器
            app.url_map.converters['regex'] = RegexConverter

            # 邮箱验证路由
            @app.route('/user/<regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"):email>')
            def show_user_by_email(email):
                return f'User email: {email}'

            # 用户名验证(只允许字母和数字)
            @app.route('/profile/<regex("[a-zA-Z0-9]+"):username>')
            def show_profile(username):
                return f'Profile: {username}'

            # 年份验证(4位数字)
            @app.route('/archive/<regex("\d{4}"):year>')
            def show_archive(year):
                return f'Archive for year: {year}'
        b.复杂路由模式
            from flask import Flask, jsonify
            import re

            app = Flask(__name__)

            # 复杂的文件路径匹配
            @app.route('/files/<path:filepath>')
            def serve_file(filepath):
                # 验证文件路径安全性
                if '..' in filepath or filepath.startswith('/'):
                    return jsonify({'error': 'Invalid file path'}), 400

                # 检查文件扩展名
                allowed_extensions = {'.txt', '.pdf', '.doc', '.docx'}
                file_ext = '.' + filepath.split('.')[-1] if '.' in filepath else ''

                if file_ext not in allowed_extensions:
                    return jsonify({'error': 'File type not allowed'}), 400

                return jsonify({'file': filepath, 'extension': file_ext})

            # API版本控制路由
            @app.route('/api/v<int:version>/users/<username>')
            def api_user_version(version, username):
                if version not in [1, 2]:
                    return jsonify({'error': 'Unsupported API version'}), 400

                response_data = {
                    'version': version,
                    'username': username,
                    'data': f'User data from API v{version}'
                }

                # 根据版本返回不同格式
                if version == 1:
                    return jsonify(response_data)
                else:
                    # v2包含更多信息
                    response_data.update({
                        'created_at': '2024-01-01',
                        'last_login': '2024-01-15'
                    })
                    return jsonify(response_data)

04.路由错误处理和最佳实践
    a.404和500错误处理
        a.自定义错误页面
            from flask import Flask, render_template, jsonify, request

            app = Flask(__name__)

            # 404错误处理
            @app.errorhandler(404)
            def not_found_error(error):
                if request.path.startswith('/api/'):
                    # API请求返回JSON
                    return jsonify({
                        'error': 'Resource not found',
                        'path': request.path,
                        'method': request.method
                    }), 404
                else:
                    # 普通请求返回HTML页面
                    return render_template('404.html'), 404

            # 500错误处理
            @app.errorhandler(500)
            def internal_error(error):
                if request.path.startswith('/api/'):
                    return jsonify({
                        'error': 'Internal server error',
                        'message': 'Something went wrong on our end'
                    }), 500
                else:
                    return render_template('500.html'), 500

            # 特定异常处理
            @app.errorhandler(ValueError)
            def handle_value_error(error):
                return jsonify({'error': str(error)}), 400
        b.路由分组和版本控制
            from flask import Flask, Blueprint, jsonify

            app = Flask(__name__)

            # v1 API蓝图
            api_v1 = Blueprint('api_v1', __name__, url_prefix='/api/v1')

            @api_v1.route('/users')
            def get_users_v1():
                return jsonify([
                    {'id': 1, 'name': 'Alice'},
                    {'id': 2, 'name': 'Bob'}
                ])

            @api_v1.route('/users/<int:user_id>')
            def get_user_v1(user_id):
                return jsonify({'id': user_id, 'name': f'User {user_id}'})

            # v2 API蓝图
            api_v2 = Blueprint('api_v2', __name__, url_prefix='/api/v2')

            @api_v2.route('/users')
            def get_users_v2():
                return jsonify({
                    'users': [
                        {
                            'id': 1,
                            'name': 'Alice',
                            'email': '[email protected]',
                            'created_at': '2024-01-01'
                        },
                        {
                            'id': 2,
                            'name': 'Bob',
                            'email': '[email protected]',
                            'created_at': '2024-01-02'
                        }
                    ],
                    'total': 2
                })

            # 注册蓝图
            app.register_blueprint(api_v1)
            app.register_blueprint(api_v2)

3.2 请求处理

01.请求对象基础
    a.请求数据获取
        a.获取请求参数
            from flask import Flask, request, jsonify

            app = Flask(__name__)

            @app.route('/api/demo')
            def demo_request():
                # 获取查询字符串参数
                page = request.args.get('page', default=1, type=int)
                per_page = request.args.get('per_page', default=10, type=int)
                search = request.args.get('search', default='', type=str)

                # 获取表单数据
                username = request.form.get('username', '')
                password = request.form.get('password', '')

                # 获取JSON数据
                json_data = request.get_json() or {}
                json_username = json_data.get('username', '')

                # 获取请求头
                user_agent = request.headers.get('User-Agent', '')
                content_type = request.headers.get('Content-Type', '')

                # 获取请求方法
                method = request.method

                # 获取请求URL
                url = request.url
                base_url = request.base_url
                path = request.path

                response = {
                    'query_params': {
                        'page': page,
                        'per_page': per_page,
                        'search': search
                    },
                    'form_data': {
                        'username': username,
                        'password': '***' if password else ''
                    },
                    'json_data': json_data,
                    'headers': {
                        'User-Agent': user_agent,
                        'Content-Type': content_type
                    },
                    'request_info': {
                        'method': method,
                        'url': url,
                        'base_url': base_url,
                        'path': path
                    }
                }

                return jsonify(response)
        b.文件上传处理
            from flask import Flask, request, jsonify, safe_join
            from werkzeug.utils import secure_filename
            import os
            from datetime import datetime

            app = Flask(__name__)
            app.config['UPLOAD_FOLDER'] = 'uploads'
            app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB

            # 确保上传目录存在
            os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

            @app.route('/upload', methods=['POST'])
            def upload_file():
                # 检查是否有文件被上传
                if 'file' not in request.files:
                    return jsonify({'error': 'No file provided'}), 400

                file = request.files['file']

                # 检查文件名是否为空
                if file.filename == '':
                    return jsonify({'error': 'No file selected'}), 400

                # 检查文件类型
                allowed_extensions = {'.txt', '.pdf', '.doc', '.docx', '.jpg', '.png'}
                file_ext = os.path.splitext(file.filename).lower()

                if file_ext not in allowed_extensions:
                    return jsonify({
                        'error': f'File type {file_ext} not allowed',
                        'allowed_types': list(allowed_extensions)
                    }), 400

                # 安全处理文件名
                filename = secure_filename(file.filename)

                # 添加时间戳避免文件名冲突
                timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                filename = f"{timestamp}_{filename}"

                # 保存文件
                try:
                    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                    file.save(file_path)

                    # 获取文件信息
                    file_size = os.path.getsize(file_path)

                    return jsonify({
                        'message': 'File uploaded successfully',
                        'filename': filename,
                        'size': file_size,
                        'type': file_ext,
                        'path': file_path
                    })

                except Exception as e:
                    return jsonify({
                        'error': 'Failed to save file',
                        'details': str(e)
                    }), 500

            # 多文件上传
            @app.route('/upload/multiple', methods=['POST'])
            def upload_multiple_files():
                if 'files' not in request.files:
                    return jsonify({'error': 'No files provided'}), 400

                files = request.files.getlist('files')
                uploaded_files = []
                errors = []

                for file in files:
                    if file.filename == '':
                        continue

                    try:
                        # 安全处理文件名
                        filename = secure_filename(file.filename)
                        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                        safe_filename = f"{timestamp}_{filename}"

                        # 保存文件
                        file_path = os.path.join(app.config['UPLOAD_FOLDER'], safe_filename)
                        file.save(file_path)

                        uploaded_files.append({
                            'original_name': file.filename,
                            'saved_name': safe_filename,
                            'size': os.path.getsize(file_path)
                        })

                    except Exception as e:
                        errors.append({
                            'file': file.filename,
                            'error': str(e)
                        })

                return jsonify({
                    'uploaded': uploaded_files,
                    'errors': errors,
                    'total_uploaded': len(uploaded_files),
                    'total_errors': len(errors)
                })

02.请求验证和安全
    a.输入验证和清理
        a.基础输入验证
            from flask import Flask, request, jsonify
            import re
            from datetime import datetime

            app = Flask(__name__)

            def validate_email(email):
                """验证邮箱格式"""
                pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
                return re.match(pattern, email) is not None

            def validate_phone(phone):
                """验证手机号格式"""
                pattern = r'^1[3-9]\d{9}$'
                return re.match(pattern, phone) is not None

            def validate_password(password):
                """验证密码强度"""
                if len(password) < 8:
                    return False, "Password must be at least 8 characters"

                if not re.search(r'[A-Z]', password):
                    return False, "Password must contain at least one uppercase letter"

                if not re.search(r'[a-z]', password):
                    return False, "Password must contain at least one lowercase letter"

                if not re.search(r'\d', password):
                    return False, "Password must contain at least one digit"

                return True, "Password is valid"

            @app.route('/api/register', methods=['POST'])
            def register_user():
                data = request.get_json()

                if not data:
                    return jsonify({'error': 'No data provided'}), 400

                # 必填字段验证
                required_fields = ['username', 'email', 'password']
                for field in required_fields:
                    if field not in data or not data[field].strip():
                        return jsonify({
                            'error': f'{field} is required'
                        }), 400

                username = data['username'].strip()
                email = data['email'].strip().lower()
                password = data['password']

                # 用户名验证
                if len(username) < 3 or len(username) > 20:
                    return jsonify({
                        'error': 'Username must be between 3 and 20 characters'
                    }), 400

                if not re.match(r'^[a-zA-Z0-9_]+$', username):
                    return jsonify({
                        'error': 'Username can only contain letters, numbers, and underscores'
                    }), 400

                # 邮箱验证
                if not validate_email(email):
                    return jsonify({'error': 'Invalid email format'}), 400

                # 密码验证
                is_valid_password, password_message = validate_password(password)
                if not is_valid_password:
                    return jsonify({'error': password_message}), 400

                # 可选字段验证
                phone = data.get('phone', '').strip()
                if phone and not validate_phone(phone):
                    return jsonify({'error': 'Invalid phone number format'}), 400

                age = data.get('age')
                if age is not None:
                    try:
                        age = int(age)
                        if age < 0 or age > 150:
                            return jsonify({'error': 'Age must be between 0 and 150'}), 400
                    except ValueError:
                        return jsonify({'error': 'Age must be a valid integer'}), 400

                # 模拟用户创建
                user_data = {
                    'username': username,
                    'email': email,
                    'phone': phone,
                    'age': age,
                    'created_at': datetime.now().isoformat(),
                    'status': 'active'
                }

                return jsonify({
                    'message': 'User registered successfully',
                    'user': user_data
                }), 201
        b.XSS防护和数据清理
            from flask import Flask, request, jsonify
            import bleach
            import html

            app = Flask(__name__)

            def sanitize_html(content):
                """清理HTML内容,防止XSS攻击"""
                # 定义允许的HTML标签
                allowed_tags = ['p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3', 'ul', 'ol', 'li']
                allowed_attributes = {'*': ['class']}

                # 清理HTML
                clean_content = bleach.clean(
                    content,
                    tags=allowed_tags,
                    attributes=allowed_attributes,
                    strip=True
                )

                return clean_content

            def sanitize_input(text):
                """清理用户输入文本"""
                if not text:
                    return ''

                # 转义HTML特殊字符
                escaped_text = html.escape(text)

                # 移除潜在的JavaScript代码
                dangerous_patterns = [
                    r'<script[^>]*>.*?</script>',
                    r'javascript:',
                    r'vbscript:',
                    r'on\w+\s*=',
                ]

                cleaned_text = escaped_text
                for pattern in dangerous_patterns:
                    cleaned_text = re.sub(pattern, '', cleaned_text, flags=re.IGNORECASE | re.DOTALL)

                return cleaned_text.strip()

            @app.route('/api/comment', methods=['POST'])
            def add_comment():
                data = request.get_json()

                if not data or 'content' not in data:
                    return jsonify({'error': 'Comment content is required'}), 400

                raw_content = data['content']

                # 验证内容长度
                if len(raw_content) < 1:
                    return jsonify({'error': 'Comment cannot be empty'}), 400

                if len(raw_content) > 5000:
                    return jsonify({'error': 'Comment is too long (max 5000 characters)'}), 400

                # 清理用户名(如果有提供)
                username = sanitize_input(data.get('username', 'Anonymous'))

                # 清理评论内容
                if data.get('allow_html', False):
                    # 允许部分HTML标签
                    clean_content = sanitize_html(raw_content)
                else:
                    # 完全纯文本
                    clean_content = sanitize_input(raw_content)

                # 模拟保存评论
                comment = {
                    'id': 123,
                    'username': username,
                    'content': clean_content,
                    'created_at': datetime.now().isoformat(),
                    'ip_address': request.remote_addr
                }

                return jsonify({
                    'message': 'Comment added successfully',
                    'comment': comment
                }), 201

03.请求上下文管理
    a.上下文对象使用
        a.全局上下文对象
            from flask import Flask, request, g, session, jsonify
            from functools import wraps
            import time
            import uuid

            app = Flask(__name__)
            app.secret_key = 'your-secret-key-here'

            @app.before_request
            def before_request():
                """在每个请求前执行的函数"""
                g.request_start_time = time.time()
                g.request_id = str(uuid.uuid4())
                g.user_agent = request.headers.get('User-Agent', '')

                # 记录请求信息
                app.logger.info(f"Request {g.request_id} started: {request.method} {request.path}")

                # 模拟用户认证
                auth_header = request.headers.get('Authorization')
                if auth_header and auth_header.startswith('Bearer '):
                    token = auth_header[7:]
                    # 这里应该验证token,简化示例
                    g.current_user = {
                        'id': 123,
                        'username': 'demo_user',
                        'token': token
                    }
                else:
                    g.current_user = None

            @app.after_request
            def after_request(response):
                """在每个请求后执行的函数"""
                # 计算请求处理时间
                if hasattr(g, 'request_start_time'):
                    processing_time = time.time() - g.request_start_time
                    response.headers['X-Processing-Time'] = str(processing_time)

                # 添加请求ID到响应头
                if hasattr(g, 'request_id'):
                    response.headers['X-Request-ID'] = g.request_id

                # 记录响应信息
                app.logger.info(f"Request completed with status: {response.status_code}")

                return response

            def login_required(f):
                """登录验证装饰器"""
                @wraps(f)
                def decorated_function(*args, **kwargs):
                    if not g.current_user:
                        return jsonify({
                            'error': 'Authentication required',
                            'message': 'Please provide valid authentication token'
                        }), 401
                    return f(*args, **kwargs)
                return decorated_function

            @app.route('/api/profile')
            @login_required
            def get_profile():
                """获取用户资料(需要登录)"""
                # 使用g对象中的用户信息
                user = g.current_user

                # 模拟获取详细资料
                profile_data = {
                    'id': user['id'],
                    'username': user['username'],
                    'email': '[email protected]',
                    'created_at': '2024-01-01',
                    'last_login': '2024-01-15',
                    'request_info': {
                        'request_id': g.request_id,
                        'user_agent': g.user_agent
                    }
                }

                return jsonify(profile_data)

            @app.route('/api/public-data')
            def public_data():
                """公开数据(不需要登录)"""
                # 可以访问g对象中的请求信息
                data = {
                    'message': 'This is public data',
                    'request_id': getattr(g, 'request_id', 'no-id'),
                    'is_authenticated': g.current_user is not None,
                    'timestamp': time.time()
                }

                return jsonify(data)

            @app.route('/api/session-demo')
            def session_demo():
                """会话管理演示"""
                # 使用session对象
                if 'visit_count' not in session:
                    session['visit_count'] = 0

                session['visit_count'] += 1
                session['last_visit'] = time.time()

                # 设置会话过期时间
                session.permanent = True

                return jsonify({
                    'message': 'Session demo',
                    'visit_count': session['visit_count'],
                    'last_visit': session['last_visit'],
                    'session_id': session.get('_id', 'no-id')
                })
        b.应用上下文使用
            from flask import Flask, current_app, g, jsonify
            import sqlite3
            import os

            app = Flask(__name__)

            # 数据库配置
            app.config.update({
                'DATABASE': os.path.join(app.instance_path, 'app.db'),
                'DATABASE_TIMEOUT': 30,
                'DEBUG': True
            })

            def get_db():
                """获取数据库连接"""
                if 'db' not in g:
                    # 确保实例目录存在
                    os.makedirs(app.instance_path, exist_ok=True)

                    g.db = sqlite3.connect(
                        current_app.config['DATABASE'],
                        timeout=current_app.config['DATABASE_TIMEOUT']
                    )
                    g.db.row_factory = sqlite3.Row  # 使查询结果可以按列名访问

                return g.db

            def close_db():
                """关闭数据库连接"""
                db = g.pop('db', None)
                if db is not None:
                    db.close()

            @app.teardown_appcontext
            def teardown_db(exception):
                """在应用上下文结束时关闭数据库连接"""
                close_db()

            @app.cli.command('init-db')
            def init_db_command():
                """初始化数据库的CLI命令"""
                db = get_db()

                with current_app.open_resource('schema.sql') as f:
                    db.executescript(f.read().decode('utf8'))

                current_app.logger.info('Database initialized successfully')

            @app.route('/api/users')
            def get_users():
                """获取用户列表"""
                db = get_db()

                try:
                    # 使用应用配置
                    limit = current_app.config.get('DEFAULT_LIMIT', 10)
                    limit = min(limit, 100)  # 最大限制100条

                    cursor = db.execute(
                        'SELECT id, username, email, created_at FROM users LIMIT ?',
                        (limit,)
                    )
                    users = [dict(row) for row in cursor.fetchall()]

                    return jsonify({
                        'users': users,
                        'count': len(users),
                        'limit': limit,
                        'database_path': current_app.config['DATABASE']
                    })

                except sqlite3.Error as e:
                    current_app.logger.error(f"Database error: {e}")
                    return jsonify({'error': 'Database error occurred'}), 500

            @app.route('/api/config')
            def get_config():
                """获取应用配置信息"""
                safe_config = {
                    'debug': current_app.debug,
                    'testing': current_app.testing,
                    'instance_path': current_app.instance_path,
                    'root_path': current_app.root_path,
                    'default_limit': current_app.config.get('DEFAULT_LIMIT', 10),
                    'database_timeout': current_app.config.get('DATABASE_TIMEOUT', 30)
                }

                return jsonify({
                    'app_name': current_app.name,
                    'config': safe_config
                })

3.3 响应处理

01.响应对象基础
    a.基本响应创建
        a.文本响应
            from flask import Flask, Response, jsonify, make_response
            import json

            app = Flask(__name__)

            @app.route('/text/simple')
            def simple_text():
                """简单文本响应"""
                return 'Hello, Flask!'

            @app.route('/text/html')
            def html_response():
                """HTML响应"""
                html_content = '''
                <!DOCTYPE html>
                <html>
                <head>
                    <title>Flask Demo</title>
                    <style>
                        body { font-family: Arial, sans-serif; margin: 40px; }
                        .container { max-width: 800px; margin: 0 auto; }
                        .highlight { background-color: #f0f8ff; padding: 20px; border-radius: 5px; }
                    </style>
                </head>
                <body>
                    <div class="container">
                        <h1>Welcome to Flask Demo</h1>
                        <div class="highlight">
                            <p>This is a dynamically generated HTML response from Flask.</p>
                            <p>Current time: <span id="current-time"></span></p>
                        </div>
                        <script>
                            document.getElementById('current-time').textContent = new Date().toLocaleString();
                        </script>
                    </div>
                </body>
                </html>
                '''
                return html_content

            @app.route('/text/custom-headers')
            def custom_headers_response():
                """自定义响应头"""
                response = make_response('Response with custom headers')
                response.headers['X-Custom-Header'] = 'Custom Value'
                response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
                response.headers['Pragma'] = 'no-cache'
                response.headers['Expires'] = '0'
                return response
        b.JSON响应
            @app.route('/api/data')
            def json_data():
                """简单JSON响应"""
                data = {
                    'name': 'Flask Demo',
                    'version': '1.0.0',
                    'features': ['routing', 'templating', 'request handling'],
                    'status': 'active'
                }
                return jsonify(data)

            @app.route('/api/users')
            def users_json():
                """用户列表JSON响应"""
                users = [
                    {
                        'id': 1,
                        'name': 'Alice Johnson',
                        'email': '[email protected]',
                        'profile': {
                            'age': 28,
                            'city': 'New York',
                            'interests': ['coding', 'music', 'travel']
                        },
                        'created_at': '2024-01-01T10:00:00Z',
                        'last_login': '2024-01-15T14:30:00Z'
                    },
                    {
                        'id': 2,
                        'name': 'Bob Smith',
                        'email': '[email protected]',
                        'profile': {
                            'age': 32,
                            'city': 'San Francisco',
                            'interests': ['photography', 'hiking', 'cooking']
                        },
                        'created_at': '2024-01-02T09:15:00Z',
                        'last_login': '2024-01-16T16:45:00Z'
                    }
                ]

                response_data = {
                    'users': users,
                    'pagination': {
                        'page': 1,
                        'per_page': 10,
                        'total': 2,
                        'pages': 1
                    },
                    'filters': {
                        'active_only': True,
                        'sort_by': 'created_at',
                        'order': 'desc'
                    }
                }

                return jsonify(response_data)

            @app.route('/api/error')
            def error_response():
                """错误响应"""
                error_data = {
                    'error': {
                        'code': 'VALIDATION_ERROR',
                        'message': 'Invalid input parameters',
                        'details': {
                            'field': 'email',
                            'issue': 'Invalid email format',
                            'suggestion': 'Please provide a valid email address'
                        },
                        'timestamp': '2024-01-15T10:30:00Z',
                        'request_id': 'req_123456789'
                    }
                }
                response = jsonify(error_data)
                response.status_code = 400
                return response

02.响应状态码和HTTP特性
    a.状态码管理
        a.不同状态码响应
            from flask import Flask, Response, jsonify, abort
            import json

            app = Flask(__name__)

            @app.route('/status/success')
            def success_status():
                """成功响应(200)"""
                data = {
                    'status': 'success',
                    'message': 'Operation completed successfully',
                    'data': {'result': 'completed'}
                }
                return jsonify(data), 200

            @app.route('/status/created')
            def created_status():
                """资源创建成功(201)"""
                new_resource = {
                    'id': 123,
                    'name': 'New Resource',
                    'created_at': '2024-01-15T10:00:00Z',
                    'uri': '/api/resources/123'
                }
                response = jsonify(new_resource)
                response.status_code = 201
                response.headers['Location'] = new_resource['uri']
                return response

            @app.route('/status/no-content')
            def no_content_status():
                """无内容响应(204)"""
                response = Response('', status=204)
                return response

            @app.route('/status/bad-request')
            def bad_request_status():
                """客户端错误(400)"""
                error_info = {
                    'error': 'Bad Request',
                    'message': 'The request could not be understood or was missing required parameters',
                    'validation_errors': [
                        {'field': 'username', 'message': 'Username is required'},
                        {'field': 'password', 'message': 'Password must be at least 8 characters'}
                    ]
                }
                return jsonify(error_info), 400

            @app.route('/status/unauthorized')
            def unauthorized_status():
                """未授权访问(401)"""
                response = jsonify({
                    'error': 'Unauthorized',
                    'message': 'Authentication required to access this resource',
                    'auth_required': True,
                    'login_url': '/auth/login'
                })
                response.status_code = 401
                response.headers['WWW-Authenticate'] = 'Bearer realm="Restricted area"'
                return response

            @app.route('/status/forbidden')
            def forbidden_status():
                """禁止访问(403)"""
                return jsonify({
                    'error': 'Forbidden',
                    'message': 'You do not have permission to access this resource',
                    'required_permissions': ['admin', 'read'],
                    'current_permissions': ['user']
                }), 403

            @app.route('/status/not-found')
            def not_found_status():
                """资源未找到(404)"""
                return jsonify({
                    'error': 'Not Found',
                    'message': 'The requested resource was not found',
                    'requested_path': '/status/not-found',
                    'suggestion': 'Please check the URL and try again'
                }), 404

            @app.route('/status/server-error')
            def server_error_status():
                """服务器错误(500)"""
                return jsonify({
                    'error': 'Internal Server Error',
                    'message': 'An unexpected error occurred on the server',
                    'timestamp': '2024-01-15T10:30:00Z',
                    'request_id': 'req_987654321'
                }), 500
        b.重定向响应
            @app.route('/redirect/permanent')
            def permanent_redirect():
                """永久重定向(301)"""
                from flask import redirect
                return redirect('/api/v2/users', code=301)

            @app.route('/redirect/temporary')
            def temporary_redirect():
                """临时重定向(302)"""
                from flask import redirect
                return redirect('/login', code=302)

            @app.route('/redirect/see-other')
            def see_other_redirect():
                """重定向到其他方法(303)"""
                from flask import redirect, url_for
                # 在POST处理后重定向到GET请求
                return redirect(url_for('get_resource'), code=303)

            @app.route('/redirect/not-modified')
            def not_modified_response():
                """内容未修改(304)"""
                # 通常用于缓存机制
                response = Response('', status=304)
                response.headers['ETag'] = '123456789'
                response.headers['Cache-Control'] = 'max-age=3600'
                return response

03.高级响应处理
    a.流式响应
        a.大文件下载
            from flask import Flask, Response, stream_with_context
            import os
            import mimetypes
            from pathlib import Path

            app = Flask(__name__)

            def generate_file_stream(file_path, chunk_size=8192):
                """生成文件流"""
                with open(file_path, 'rb') as file:
                    while True:
                        chunk = file.read(chunk_size)
                        if not chunk:
                            break
                        yield chunk

            @app.route('/download/file/<filename>')
            def download_file(filename):
                """文件下载响应"""
                # 安全处理文件名
                safe_filename = os.path.basename(filename)
                file_path = os.path.join('uploads', safe_filename)

                if not os.path.exists(file_path):
                    return jsonify({'error': 'File not found'}), 404

                # 获取文件信息
                file_size = os.path.getsize(file_path)
                mime_type = mimetypes.guess_type(file_path)[0] or 'application/octet-stream'

                # 创建响应
                response = Response(
                    stream_with_context(generate_file_stream(file_path)),
                    mimetype=mime_type
                )

                # 设置响应头
                response.headers['Content-Length'] = str(file_size)
                response.headers['Content-Disposition'] = f'attachment; filename="{safe_filename}"'
                response.headers['Cache-Control'] = 'no-cache'

                return response

            @app.route('/stream/data')
            def stream_data():
                """数据流响应"""
                def generate_data():
                    import time
                    import json

                    for i in range(10):
                        data = {
                            'message': f'Processing step {i + 1}',
                            'progress': (i + 1) * 10,
                            'timestamp': time.time()
                        }
                        yield f"data: {json.dumps(data)}\n\n"
                        time.sleep(1)

                    # 完成消息
                    complete_data = {
                        'message': 'Processing completed',
                        'progress': 100,
                        'timestamp': time.time()
                    }
                    yield f"data: {json.dumps(complete_data)}\n\n"

                response = Response(
                    stream_with_context(generate_data()),
                    mimetype='text/event-stream'
                )
                response.headers['Cache-Control'] = 'no-cache'
                response.headers['Connection'] = 'keep-alive'
                return response
        b.实时数据推送
            @app.route('/stream/chat')
            def chat_stream():
                """聊天流式响应"""
                def generate_chat_response():
                    import time
                    import json

                    messages = [
                        "Hello! How can I help you today?",
                        "I'm processing your request...",
                        "Let me find the information you need...",
                        "Here's what I found for you!",
                        "Is there anything else I can help with?"
                    ]

                    for i, message in enumerate(messages):
                        chunk = {
                            'id': i + 1,
                            'message': message,
                            'timestamp': time.time(),
                            'is_complete': i == len(messages) - 1
                        }
                        yield f"data: {json.dumps(chunk)}\n\n"
                        time.sleep(2)  # 模拟处理时间

                response = Response(
                    stream_with_context(generate_chat_response()),
                    mimetype='text/event-stream'
                )
                response.headers['Cache-Control'] = 'no-cache'
                response.headers['Connection'] = 'keep-alive'
                response.headers['Access-Control-Allow-Origin'] = '*'
                return response

04.响应压缩和优化
    a.Gzip压缩
        a.响应压缩配置
            from flask import Flask, Response, jsonify
            import gzip
            import io

            app = Flask(__name__)

            def compress_response(data):
                """压缩响应数据"""
                if isinstance(data, str):
                    data_bytes = data.encode('utf-8')
                else:
                    data_bytes = data

                # 创建压缩缓冲区
                buffer = io.BytesIO()
                with gzip.GzipFile(fileobj=buffer, mode='wb') as gz_file:
                    gz_file.write(data_bytes)

                return buffer.getvalue()

            @app.route('/api/compressed-data')
            def compressed_data():
                """压缩的JSON响应"""
                # 生成大量数据
                large_data = []
                for i in range(1000):
                    large_data.append({
                        'id': i,
                        'name': f'Item {i}',
                        'description': f'This is a detailed description for item {i}',
                        'metadata': {
                            'created_at': '2024-01-15T10:00:00Z',
                            'category': 'demo',
                            'tags': [f'tag_{j}' for j in range(5)]
                        }
                    })

                # 转换为JSON
                json_data = json.dumps(large_data, ensure_ascii=False, indent=2)

                # 检查客户端是否支持压缩
                accept_encoding = request.headers.get('Accept-Encoding', '')
                if 'gzip' in accept_encoding:
                    # 压缩数据
                    compressed_data = compress_response(json_data)

                    response = Response(compressed_data)
                    response.headers['Content-Encoding'] = 'gzip'
                    response.headers['Content-Type'] = 'application/json; charset=utf-8'
                    response.headers['Vary'] = 'Accept-Encoding'
                else:
                    # 不压缩
                    response = Response(json_data)
                    response.headers['Content-Type'] = 'application/json; charset=utf-8'

                return response
        b.缓存控制和优化
            @app.route('/api/cached-data')
            def cached_data():
                """带缓存控制的响应"""
                data = {
                    'message': 'This data is cacheable',
                    'cache_info': {
                        'cache_control': 'public, max-age=3600',
                        'expires': '1 hour from now',
                        'etag': 'unique-data-identifier'
                    }
                }

                response = jsonify(data)

                # 设置缓存控制头
                response.headers['Cache-Control'] = 'public, max-age=3600'
                response.headers['Expires'] = 'Wed, 15 Jan 2025 11:30:00 GMT'
                response.headers['ETag'] = 'unique-data-identifier-12345'

                return response

            @app.route('/api/no-cache-data')
            def no_cache_data():
                """不缓存的数据响应"""
                data = {
                    'message': 'This data should not be cached',
                    'timestamp': datetime.now().isoformat(),
                    'random': random.randint(1, 1000)
                }

                response = jsonify(data)

                # 禁用缓存
                response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
                response.headers['Pragma'] = 'no-cache'
                response.headers['Expires'] = '0'

                return response

3.4 模板引擎

01.Jinja2模板基础
    a.模板语法和结构
        a.基本模板语法
            <!DOCTYPE html>
            <html>
            <head>
                <title>{{ title }} - Flask Demo</title>
                <meta charset="utf-8">
                <meta name="viewport" content="width=device-width, initial-scale=1">
                <style>
                    body { font-family: Arial, sans-serif; margin: 20px; }
                    .container { max-width: 1200px; margin: 0 auto; }
                    .card { border: 1px solid #ddd; border-radius: 5px; padding: 15px; margin: 10px 0; }
                    .highlight { background-color: #f8f9fa; padding: 15px; border-left: 4px solid #007bff; }
                </style>
            </head>
            <body>
                <div class="container">
                    <header>
                        <h1>{{ title }}</h1>
                        <nav>
                            <a href="/">Home</a> |
                            <a href="/about">About</a> |
                            <a href="/contact">Contact</a>
                        </nav>
                    </header>

                    <main>
                        {% block content %}{% endblock %}
                    </main>

                    <footer>
                        <p>&copy; 2024 Flask Demo. All rights reserved.</p>
                    </footer>
                </div>
            </body>
            </html>
        b.变量和过滤器
            <!-- 变量显示示例 -->
            <div class="card">
                <h2>用户信息</h2>
                <p><strong>姓名:</strong> {{ user.name | default('匿名用户') }}</p>
                <p><strong>邮箱:</strong> {{ user.email | lower }}</p>
                <p><strong>注册时间:</strong> {{ user.created_at | dateformat('%Y-%m-%d %H:%M') }}</p>
                <p><strong>状态:</strong>
                    <span class="status-{{ user.status | lower }}">
                        {{ user.status | title }}
                    </span>
                </p>
            </div>

            <!-- 过滤器链示例 -->
            <div class="highlight">
                <h3>处理长文本</h3>
                <p>{{ long_text | striptags | truncate(100, True, '...') }}</p>
            </div>

            <!-- 列表处理 -->
            <div class="card">
                <h3>用户列表 ({{ users | length }} 个用户)</h3>
                <ul>
                    {% for user in users | sort(attribute='name') %}
                    <li>{{ user.name }} - {{ user.email | lower }}</li>
                    {% endfor %}
                </ul>
            </div>

            <!-- 条件渲染 -->
            <div class="card">
                <h3>权限显示</h3>
                {% if user.role == 'admin' %}
                <p class="admin-badge">管理员权限</p>
                {% elif user.role == 'editor' %}
                <p class="editor-badge">编辑权限</p>
                {% else %}
                <p class="user-badge">普通用户</p>
                {% endif %}
            </div>
        c.控制结构
            <!-- 循环结构 -->
            <div class="products">
                <h2>产品列表</h2>

                <!-- 简单循环 -->
                <ul>
                    {% for product in products %}
                    <li>{{ product.name }} - ${{ product.price | round(2) }}</li>
                    {% endfor %}
                </ul>

                <!-- 带索引的循环 -->
                <table class="product-table">
                    <thead>
                        <tr>
                            <th>序号</th>
                            <th>产品名</th>
                            <th>价格</th>
                            <th>库存</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for product in products %}
                        <tr class="{% if loop.index is even %}even{% else %}odd{% endif %}">
                            <td>{{ loop.index }}</td>
                            <td>{{ product.name }}</td>
                            <td>${{ product.price | round(2) }}</td>
                            <td>
                                {% if product.stock > 0 %}
                                <span class="in-stock">{{ product.stock }}</span>
                                {% else %}
                                <span class="out-of-stock">缺货</span>
                                {% endif %}
                            </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>

                <!-- 循环控制 -->
                {% if products %}
                <p>显示第 1-{{ products | length }} 个产品</p>
                {% else %}
                <p>暂无产品</p>
                {% endif %}
            </div>

            <!-- 条件结构 -->
            <div class="user-profile">
                {% if user %}
                <h2>{{ user.name }}的个人资料</h2>

                <!-- 复杂条件判断 -->
                {% if user.age < 18 %}
                <p class="minor">未成年用户</p>
                {% elif user.age >= 18 and user.age < 65 %}
                <p class="adult">成年用户</p>
                {% else %}
                <p class="senior">老年用户</p>
                {% endif %}

                <!-- 检查用户特性 -->
                {% if user.is_verified %}
                <span class="verified-badge">✓ 已认证</span>
                {% endif %}

                {% if user.premium %}
                <span class="premium-badge">⭐ 高级会员</span>
                {% endif %}

                {% else %}
                <p>用户未登录</p>
                <a href="/login" class="login-btn">请登录</a>
                {% endif %}
            </div>
        d.模板继承
            <!-- base.html -->
            <!DOCTYPE html>
            <html lang="zh-CN">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>{% block title %}Flask Demo{% endblock %}</title>

                <!-- CSS -->
                {% block styles %}
                <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
                <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
                {% endblock %}
            </head>
            <body>
                <!-- 导航栏 -->
                <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
                    <div class="container">
                        <a class="navbar-brand" href="/">Flask Demo</a>

                        <div class="navbar-nav">
                            <a class="nav-link" href="/">首页</a>
                            <a class="nav-link" href="/about">关于</a>
                            <a class="nav-link" href="/contact">联系我们</a>
                        </div>

                        <!-- 用户菜单 -->
                        {% if current_user %}
                        <div class="user-menu">
                            <span class="navbar-text">
                                欢迎,{{ current_user.name }}
                            </span>
                            <a href="/logout" class="btn btn-outline-light btn-sm">退出</a>
                        </div>
                        {% else %}
                        <div class="auth-menu">
                            <a href="/login" class="btn btn-outline-light btn-sm">登录</a>
                            <a href="/register" class="btn btn-light btn-sm">注册</a>
                        </div>
                        {% endif %}
                    </div>
                </nav>

                <!-- 消息提示 -->
                {% with messages = get_flashed_messages(with_categories=true) %}
                    {% if messages %}
                        <div class="container mt-3">
                            {% for category, message in messages %}
                            <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show" role="alert">
                                {{ message }}
                                <button type="button" class="close" data-dismiss="alert">
                                    <span>&times;</span>
                                </button>
                            </div>
                            {% endfor %}
                        </div>
                    {% endif %}
                {% endwith %}

                <!-- 主要内容 -->
                <main class="container mt-4">
                    {% block content %}{% endblock %}
                </main>

                <!-- 页脚 -->
                <footer class="bg-light text-center py-3 mt-5">
                    <div class="container">
                        <p>&copy; 2024 Flask Demo. {% block footer %}{% endblock %}</p>
                    </div>
                </footer>

                <!-- JavaScript -->
                {% block scripts %}
                <script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
                <script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
                {% endblock %}

                <!-- 页面特定的脚本 -->
                {% block page_scripts %}{% endblock %}
            </body>
            </html>

            <!-- home.html -->
            {% extends "base.html" %}

            {% block title %}首页 - Flask Demo{% endblock %}

            {% block content %}
            <div class="hero-section text-center py-5 bg-light">
                <h1 class="display-4">欢迎来到 Flask Demo</h1>
                <p class="lead">这是一个展示 Flask 框架功能的演示网站</p>
                <a href="/about" class="btn btn-primary btn-lg">了解更多</a>
            </div>

            <div class="features-section py-5">
                <div class="row">
                    {% for feature in features %}
                    <div class="col-md-4 mb-4">
                        <div class="card h-100">
                            <div class="card-body text-center">
                                <h3 class="card-title">{{ feature.title }}</h3>
                                <p class="card-text">{{ feature.description }}</p>
                                <a href="{{ feature.link }}" class="btn btn-outline-primary">查看详情</a>
                            </div>
                        </div>
                    </div>
                    {% endfor %}
                </div>
            </div>

            <div class="recent-posts-section py-5">
                <h2 class="text-center mb-4">最新文章</h2>
                <div class="row">
                    {% for post in posts %}
                    <div class="col-md-6 mb-4">
                        <div class="card">
                            <div class="card-body">
                                <h5 class="card-title">{{ post.title }}</h5>
                                <p class="card-text">{{ post.summary | truncate(100) }}</p>
                                <small class="text-muted">
                                    发布于 {{ post.created_at | dateformat('%Y-%m-%d') }}
                                    by {{ post.author.name }}
                                </small>
                            </div>
                        </div>
                    </div>
                    {% endfor %}
                </div>
            </div>
            {% endblock %}

            {% block page_scripts %}
            <script>
                // 首页特定的JavaScript
                $(document).ready(function() {
                    console.log('Home page loaded');
                    // 可以添加更多交互功能
                });
            </script>
            {% endblock %}

02.高级模板功能
    a.宏和包含
        a.自定义宏定义
            <!-- macros.html -->
            {% macro render_field(field, label_class='', field_class='') %}
            <div class="form-group mb-3">
                <label for="{{ field.id }}" class="form-label {{ label_class }}">
                    {{ field.label.text }}
                    {% if field.flags.required %}
                    <span class="text-danger">*</span>
                    {% endif %}
                </label>

                {{ field(class=field_class + ' form-control' + (' is-invalid' if field.errors else '')) }}

                {% if field.description %}
                <small class="form-text text-muted">{{ field.description }}</small>
                {% endif %}

                {% if field.errors %}
                {% for error in field.errors %}
                <div class="invalid-feedback">{{ error }}</div>
                {% endfor %}
                {% endif %}
            </div>
            {% endmacro %}

            {% macro render_pagination(pagination, endpoint) %}
            <nav aria-label="Page navigation">
                <ul class="pagination justify-content-center">
                    {% if pagination.has_prev %}
                    <li class="page-item">
                        <a class="page-link" href="{{ url_for(endpoint, page=pagination.prev_num) }}">
                            <span aria-hidden="true">&laquo;</span>
                        </a>
                    </li>
                    {% else %}
                    <li class="page-item disabled">
                        <span class="page-link"><span aria-hidden="true">&laquo;</span></span>
                    </li>
                    {% endif %}

                    {% for page_num in pagination.iter_pages() %}
                        {% if page_num %}
                            {% if page_num != pagination.page %}
                            <li class="page-item">
                                <a class="page-link" href="{{ url_for(endpoint, page=page_num) }}">{{ page_num }}</a>
                            </li>
                            {% else %}
                            <li class="page-item active">
                                <span class="page-link">{{ page_num }}</span>
                            </li>
                            {% endif %}
                        {% else %}
                        <li class="page-item disabled">
                            <span class="page-link">…</span>
                        </li>
                        {% endif %}
                    {% endfor %}

                    {% if pagination.has_next %}
                    <li class="page-item">
                        <a class="page-link" href="{{ url_for(endpoint, page=pagination.next_num) }}">
                            <span aria-hidden="true">&raquo;</span>
                        </a>
                    </li>
                    {% else %}
                    <li class="page-item disabled">
                        <span class="page-link"><span aria-hidden="true">&raquo;</span></span>
                    </li>
                    {% endif %}
                </ul>
            </nav>
            {% endmacro %}

            {% macro user_card(user, show_details=False) %}
            <div class="card user-card mb-3">
                <div class="card-body">
                    <div class="row align-items-center">
                        <div class="col-auto">
                            <img src="{{ user.avatar or url_for('static', filename='img/default-avatar.png') }}"
                                 class="rounded-circle avatar"
                                 alt="{{ user.name }}"
                                 width="50" height="50">
                        </div>
                        <div class="col">
                            <h5 class="card-title mb-0">{{ user.name }}</h5>
                            <p class="card-text text-muted mb-0">{{ user.email }}</p>
                            {% if show_details %}
                            <small class="text-muted">
                                加入于 {{ user.created_at | dateformat('%Y-%m-%d') }}
                            </small>
                            {% endif %}
                        </div>
                        <div class="col-auto">
                            {% if user.is_online %}
                            <span class="badge bg-success">在线</span>
                            {% else %}
                            <span class="bg-secondary badge">离线</span>
                            {% endif %}
                        </div>
                    </div>
                </div>
            </div>
            {% endmacro %}

            <!-- 使用宏的模板 -->
            {% from "macros.html" import render_field, render_pagination, user_card %}

            <form method="POST" action="">
                {{ form.hidden_tag() }}

                {{ render_field(form.username, field_class="form-control-lg") }}
                {{ render_field(form.email, field_class="") }}
                {{ render_field(form.password, field_class="") }}
                {{ render_field(form.confirm_password, field_class="") }}

                <div class="form-group">
                    <button type="submit" class="btn btn-primary">注册</button>
                </div>
            </form>

            <!-- 用户列表 -->
            <div class="user-list">
                {% for user in users %}
                {{ user_card(user, show_details=True) }}
                {% endfor %}
            </div>

            <!-- 分页 -->
            {{ render_pagination(pagination, 'users.list') }}
        b.模板包含
            <!-- navbar.html -->
            <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
                <div class="container">
                    <a class="navbar-brand" href="/">
                        <img src="{{ url_for('static', filename='img/logo.png') }}"
                             height="30" class="d-inline-block align-top" alt="Logo">
                        Flask Demo
                    </a>

                    <button class="navbar-toggler" type="button" data-toggle="collapse"
                            data-target="#navbarNav" aria-controls="navbarNav"
                            aria-expanded="false" aria-label="Toggle navigation">
                        <span class="navbar-toggler-icon"></span>
                    </button>

                    <div class="collapse navbar-collapse" id="navbarNav">
                        <ul class="navbar-nav mr-auto">
                            {% for item in nav_items %}
                            <li class="nav-item {% if item.active %}active{% endif %}">
                                <a class="nav-link" href="{{ item.url }}">
                                    {{ item.label }}
                                    {% if item.active %}<span class="sr-only">(current)</span>{% endif %}
                                </a>
                            </li>
                            {% endfor %}
                        </ul>

                        <ul class="navbar-nav">
                            {% if current_user %}
                            <li class="nav-item dropdown">
                                <a class="nav-link dropdown-toggle" href="#" id="userDropdown"
                                   role="button" data-toggle="dropdown" aria-haspopup="true"
                                   aria-expanded="false">
                                    {{ current_user.name }}
                                </a>
                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="userDropdown">
                                    <a class="dropdown-item" href="/profile">个人资料</a>
                                    <a class="dropdown-item" href="/settings">设置</a>
                                    <div class="dropdown-divider"></div>
                                    <a class="dropdown-item" href="/logout">退出登录</a>
                                </div>
                            </li>
                            {% else %}
                            <li class="nav-item">
                                <a class="nav-link" href="/login">登录</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="/register">注册</a>
                            </li>
                            {% endif %}
                        </ul>
                    </div>
                </div>
            </nav>

            <!-- 使用包含的模板 -->
            {% include "navbar.html" %}

            <div class="container">
                <div class="row">
                    <div class="col-md-8">
                        <main>
                            {% block content %}{% endblock %}
                        </main>
                    </div>
                    <div class="col-md-4">
                        <!-- 侧边栏 -->
                        {% include "sidebar.html" %}
                    </div>
                </div>
            </div>

            {% include "footer.html" %}

3.5 静态文件管理

01.静态文件基础配置
    a.静态文件目录结构
        a.标准目录结构
            project/
            ├── app.py
            ├── static/
            │   ├── css/
            │   │   ├── main.css
            │   │   ├── bootstrap.min.css
            │   │   └── components/
            │   │       ├── navbar.css
            │   │       └── sidebar.css
            │   ├── js/
            │   │   ├── main.js
            │   │   ├── jquery.min.js
            │   │   ├── bootstrap.bundle.min.js
            │   │   └── modules/
            │   │       ├── api.js
            │   │       ├── utils.js
            │   │       └── charts.js
            │   ├── img/
            │   │   ├── logo.png
            │   │   ├── hero-banner.jpg
            │   │   ├── icons/
            │   │   │   ├── user.svg
            │   │   │   ├── settings.svg
            │   │   │   └── logout.svg
            │   │   └── uploads/
            │   │       ├── avatars/
            │   │       └── products/
            │   ├── fonts/
            │   │   ├── fontawesome-webfont.woff2
            │   │   └── custom-font.woff2
            │   └── docs/
            │       ├── user-guide.pdf
            │       └── api-spec.json
            └── templates/
        b.应用配置设置
            from flask import Flask, send_from_directory
            import os

            app = Flask(__name__)

            # 自定义静态文件配置
            app.config.update({
                # 静态文件目录
                'STATIC_FOLDER': 'static',
                'STATIC_URL_PATH': '/static',

                # 静态文件缓存控制
                'SEND_FILE_MAX_AGE_DEFAULT': 31536000,  # 1年

                # 开发环境下禁用缓存
                'DEBUG': True,
                'TEMPLATES_AUTO_RELOAD': True,

                # 文件上传配置
                'UPLOAD_FOLDER': 'static/uploads',
                'MAX_CONTENT_LENGTH': 16 * 1024 * 1024,  # 16MB
            })

            # 创建必要的目录
            os.makedirs(os.path.join(app.config['STATIC_FOLDER'], 'uploads'), exist_ok=True)
            os.makedirs(os.path.join(app.config['STATIC_FOLDER'], 'css'), exist_ok=True)
            os.makedirs(os.path.join(app.config['STATIC_FOLDER'], 'js'), exist_ok=True)
            os.makedirs(os.path.join(app.config['STATIC_FOLDER'], 'img'), exist_ok=True)
        c.静态文件访问示例
            from flask import Flask, render_template, url_for, send_from_directory
            import os

            app = Flask(__name__)

            @app.route('/')
            def home():
                """首页示例"""
                return render_template('home.html',
                    css_url=url_for('static', filename='css/main.css'),
                    js_url=url_for('static', filename='js/main.js'),
                    logo_url=url_for('static', filename='img/logo.png')
                )

            @app.route('/api/files')
            def list_static_files():
                """列出静态文件"""
                static_dir = app.static_folder
                files = []

                for root, dirs, filenames in os.walk(static_dir):
                    for filename in filenames:
                        rel_path = os.path.relpath(os.path.join(root, filename), static_dir)
                        file_url = url_for('static', filename=rel_path.replace('\\', '/'))

                        files.append({
                            'name': filename,
                            'path': rel_path,
                            'url': file_url,
                            'size': os.path.getsize(os.path.join(root, filename))
                        })

                return jsonify({'files': files, 'total_count': len(files)})

            @app.route('/static/uploads/<path:filename>')
            def custom_upload_serve(filename):
                """自定义上传文件服务"""
                upload_dir = os.path.join(app.static_folder, 'uploads')

                # 安全检查
                if '..' in filename or filename.startswith('/'):
                    return jsonify({'error': 'Invalid file path'}), 400

                # 检查文件是否存在
                file_path = os.path.join(upload_dir, filename)
                if not os.path.exists(file_path):
                    return jsonify({'error': 'File not found'}), 404

                # 检查文件类型
                allowed_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.pdf', '.txt'}
                file_ext = os.path.splitext(filename).lower()
                if file_ext not in allowed_extensions:
                    return jsonify({'error': 'File type not allowed'}), 403

                return send_from_directory(upload_dir, filename)

02.静态文件优化和压缩
    a.CSS和JS压缩
        a.开发环境vs生产环境
            from flask import Flask, render_template
            import os
            import gzip
            from pathlib import Path

            app = Flask(__name__)

            def is_development():
                """判断是否为开发环境"""
                return app.debug

            def get_css_url(filename):
                """获取CSS文件URL,考虑环境"""
                if is_development():
                    return url_for('static', filename=f'css/{filename}')
                else:
                    # 生产环境可以使用压缩版本
                    compressed_name = filename.replace('.css', '.min.css')
                    return url_for('static', filename=f'css/{compressed_name}')

            def get_js_url(filename):
                """获取JS文件URL,考虑环境"""
                if is_development():
                    return url_for('static', filename=f'js/{filename}')
                else:
                    # 生产环境可以使用压缩版本
                    compressed_name = filename.replace('.js', '.min.js')
                    return url_for('static', filename=f'js/{compressed_name}')

            # 模板中使用
            @app.route('/optimized')
            def optimized_page():
                return render_template('optimized.html',
                    main_css=get_css_url('main.css'),
                    app_js=get_js_url('app.js'),
                    is_dev=is_development()
                )

            <!-- optimized.html -->
            <!DOCTYPE html>
            <html>
            <head>
                <title>优化示例</title>
                <!-- 根据环境选择CSS文件 -->
                <link rel="stylesheet" href="{{ main_css }}">

                <!-- 开发环境使用单独文件,生产环境合并 -->
                {% if is_dev %}
                <link rel="stylesheet" href="{{ url_for('static', filename='css/components/navbar.css') }}">
                <link rel="stylesheet" href="{{ url_for('static', filename='css/components/sidebar.css') }}">
                {% endif %}
            </head>
            <body>
                <!-- 页面内容 -->

                <!-- 开发环境使用单独文件,生产环境合并 -->
                {% if is_dev %}
                <script src="{{ url_for('static', filename='js/modules/utils.js') }}"></script>
                <script src="{{ url_for('static', filename='js/modules/api.js') }}"></script>
                {% endif %}
                <script src="{{ app_js }}"></script>
            </body>
            </html>
        b.静态文件压缩中间件
            from flask import Flask, Response, request
            import gzip
            from io import BytesIO
            import os

            app = Flask(__name__)

            class StaticFileCompressor:
                def __init__(self, app=None):
                    self.app = app
                    if app is not None:
                        self.init_app(app)

                def init_app(self, app):
                    app.after_request(self.compress_response)

                def compress_response(self, response):
                    """压缩响应内容"""
                    # 只压缩文本文件
                    content_type = response.headers.get('Content-Type', '')
                    if not any(ct in content_type for ct in ['text/', 'application/json', 'application/javascript']):
                        return response

                    # 检查客户端是否支持gzip
                    accept_encoding = request.headers.get('Accept-Encoding', '')
                    if 'gzip' not in accept_encoding:
                        return response

                    # 压缩内容
                    if response.data:
                        compressed_data = self.compress_data(response.data)
                        if len(compressed_data) < len(response.data):
                            # 只有在压缩后体积更小的时候才使用压缩
                            response.data = compressed_data
                            response.headers['Content-Encoding'] = 'gzip'
                            response.headers['Vary'] = 'Accept-Encoding'
                            # 更新Content-Length
                            response.headers['Content-Length'] = str(len(compressed_data))

                    return response

                def compress_data(self, data):
                    """压缩数据"""
                    buffer = BytesIO()
                    with gzip.GzipFile(fileobj=buffer, mode='wb') as gz_file:
                        gz_file.write(data)
                    return buffer.getvalue()

            # 初始化压缩中间件
            compressor = StaticFileCompressor(app)

            @app.route('/static/compress-demo.css')
            def compress_demo_css():
                """压缩演示CSS"""
                css_content = """
                body {
                    font-family: Arial, sans-serif;
                    margin: 0;
                    padding: 20px;
                    background-color: #f5f5f5;
                }

                .container {
                    max-width: 1200px;
                    margin: 0 auto;
                    background-color: white;
                    padding: 20px;
                    border-radius: 8px;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
                }

                .header {
                    background-color: #333;
                    color: white;
                    padding: 20px;
                    margin: -20px -20px 20px -20px;
                    border-radius: 8px 8px 0 0;
                }

                .btn {
                    display: inline-block;
                    padding: 10px 20px;
                    background-color: #007bff;
                    color: white;
                    text-decoration: none;
                    border-radius: 4px;
                    border: none;
                    cursor: pointer;
                }

                .btn:hover {
                    background-color: #0056b3;
                }
                """

                response = Response(css_content)
                response.headers['Content-Type'] = 'text/css'
                return response

03.CDN和静态文件服务优化
    a.多CDN支持配置
        a.CDN配置管理
            from flask import Flask, current_app
            import random
            import os

            app = Flask(__name__)

            # CDN配置
            app.config['CDN_DOMAINS'] = [
                'https://cdn1.example.com',
                'https://cdn2.example.com',
                'https://cdn3.example.com'
            ]

            app.config['LOCAL_STATIC_URL'] = '/static'
            app.config['USE_CDN'] = os.environ.get('USE_CDN', 'false').lower() == 'true'

            def get_static_url(filename):
                """获取静态文件URL,支持CDN负载均衡"""
                if current_app.config['USE_CDN']:
                    # 随机选择一个CDN域名
                    cdn_domain = random.choice(current_app.config['CDN_DOMAINS'])
                    return f"{cdn_domain}/{filename}"
                else:
                    return url_for('static', filename=filename)

            # 注册为模板函数
            @app.context_processor
            def utility_processor():
                return {
                    'static_url': get_static_url,
                    'cdn_enabled': lambda: current_app.config['USE_CDN']
                }

            @app.route('/cdn-demo')
            def cdn_demo():
                """CDN演示页面"""
                return render_template('cdn_demo.html')

            <!-- cdn_demo.html -->
            <!DOCTYPE html>
            <html>
            <head>
                <title>CDN演示</title>
                <!-- CDN状态显示 -->
                {% if cdn_enabled() %}
                <meta name="cdn-status" content="enabled">
                {% else %}
                <meta name="cdn-status" content="disabled">
                {% endif %}

                <!-- 使用CDN的CSS文件 -->
                <link rel="stylesheet" href="{{ static_url('css/bootstrap.min.css') }}">
                <link rel="stylesheet" href="{{ static_url('css/main.css') }}">

                <!-- 使用CDN的字体文件 -->
                <link rel="preload" href="{{ static_url('fonts/fontawesome-webfont.woff2') }}"
                      as="font" type="font/woff2" crossorigin>
            </head>
            <body>
                <div class="container">
                    <h1>CDN静态文件演示</h1>

                    {% if cdn_enabled() %}
                    <div class="alert alert-success">
                        当前使用CDN加载静态文件
                    </div>
                    {% else %}
                    <div class="alert alert-info">
                        当前使用本地静态文件
                    </div>
                    {% endif %}

                    <!-- 使用CDN的图片 -->
                    <img src="{{ static_url('img/demo-banner.jpg') }}"
                         alt="Demo Banner" class="img-fluid">

                    <!-- CDN性能信息 -->
                    <div id="cdn-info">
                        <h3>静态文件加载信息</h3>
                        <div id="file-list">
                            <!-- JavaScript填充 -->
                        </div>
                    </div>
                </div>

                <!-- 使用CDN的JavaScript文件 -->
                <script src="{{ static_url('js/jquery.min.js') }}"></script>
                <script src="{{ static_url('js/bootstrap.bundle.min.js') }}"></script>
                <script src="{{ static_url('js/cdn-monitor.js') }}"></script>
            </body>
            </html>
        b.静态文件缓存策略
            from flask import Flask, send_from_directory, request, make_response
            import os
            import hashlib
            import time

            app = Flask(__name__)

            def get_file_hash(filepath):
                """获取文件内容的哈希值"""
                if not os.path.exists(filepath):
                    return None

                with open(filepath, 'rb') as f:
                    content = f.read()
                return hashlib.md5(content).hexdigest()[:8]

            def get_versioned_static_url(filename):
                """获取带版本号的静态文件URL"""
                filepath = os.path.join(app.static_folder, filename)
                file_hash = get_file_hash(filepath)

                if file_hash:
                    base_name, ext = os.path.splitext(filename)
                    versioned_filename = f"{base_name}.{file_hash}{ext}"
                    return url_for('static', filename=versioned_filename)
                else:
                    return url_for('static', filename=filename)

            @app.route('/static/<path:filename>')
            def serve_static_with_cache(filename):
                """带缓存控制的静态文件服务"""
                filepath = os.path.join(app.static_folder, filename)

                if not os.path.exists(filepath):
                    return jsonify({'error': 'File not found'}), 404

                # 获取文件信息
                file_stat = os.stat(filepath)
                etag = f'"{int(file_stat.st_mtime)}-{int(file_stat.st_size)}"'

                # 检查ETag
                if_none_match = request.headers.get('If-None-Match')
                if if_none_match == etag:
                    response = make_response('', 304)
                    return response

                # 检查Last-Modified
                if_modified_since = request.headers.get('If-Modified-Since')
                last_modified = file_stat.st_mtime
                if if_modified_since and last_modified <= if_modified_since:
                    response = make_response('', 304)
                    return response

                # 发送文件
                response = send_from_directory(app.static_folder, filename)

                # 设置缓存头
                file_ext = os.path.splitext(filename).lower()

                if file_ext in ['.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.woff', '.woff2']:
                    # 静态资源长期缓存
                    response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
                elif file_ext in ['.html', '.htm']:
                    # HTML文件短期缓存
                    response.headers['Cache-Control'] = 'public, max-age=3600'
                else:
                    # 其他文件中等缓存
                    response.headers['Cache-Control'] = 'public, max-age=86400'

                response.headers['ETag'] = etag
                response.headers['Last-Modified'] = time.strftime(
                    '%a, %d %b %Y %H:%M:%S GMT',
                    time.gmtime(last_modified)
                )

                return response

            @app.route('/versioned-static-demo')
            def versioned_static_demo():
                """版本化静态文件演示"""
                return render_template('versioned_static.html',
                    main_css=get_versioned_static_url('css/main.css'),
                    app_js=get_versioned_static_url('js/app.js'),
                    logo_url=get_versioned_static_url('img/logo.png')
                )

04.静态文件安全和管理
    a.安全访问控制
        a.访问权限控制
            from flask import Flask, send_from_directory, request, jsonify, abort
            from functools import wraps
            import os
            import secrets

            app = Flask(__name__)

            # 安全配置
            app.config['SECRET_KEY'] = 'your-very-secret-key'
            app.config['STATIC_TOKEN_EXPIRY'] = 3600  # 1小时

            # 生成安全的访问令牌
            def generate_access_token(filepath):
                """生成文件访问令牌"""
                timestamp = int(time.time())
                token_data = f"{filepath}:{timestamp}:{app.config['SECRET_KEY']}"
                token = hashlib.sha256(token_data.encode()).hexdigest()
                return f"{token}:{timestamp}"

            def verify_access_token(token, filepath):
                """验证文件访问令牌"""
                try:
                    token_hash, timestamp = token.rsplit(':', 1)
                    timestamp = int(timestamp)

                    # 检查过期时间
                    if time.time() - timestamp > app.config['STATIC_TOKEN_EXPIRY']:
                        return False

                    # 验证令牌
                    expected_data = f"{filepath}:{timestamp}:{app.config['SECRET_KEY']}"
                    expected_hash = hashlib.sha256(expected_data.encode()).hexdigest()

                    return secrets.compare_digest(token_hash, expected_hash)
                except (ValueError, AttributeError):
                    return False

            def protected_file_required(f):
                """保护文件访问的装饰器"""
                @wraps(f)
                def decorated_function(*args, **kwargs):
                    filepath = kwargs.get('filename', '')

                    # 检查是否为保护目录
                    if filepath.startswith('protected/'):
                        token = request.args.get('token')
                        if not token or not verify_access_token(token, filepath):
                            abort(403, description="Access denied: invalid or missing token")

                    return f(*args, **kwargs)
                return decorated_function

            @app.route('/static/protected/<path:filename>')
            @protected_file_required
            def serve_protected_file(filename):
                """服务受保护的静态文件"""
                protected_dir = os.path.join(app.static_folder, 'protected')

                # 安全路径检查
                safe_path = os.path.normpath(filename)
                if safe_path.startswith('..') or safe_path.startswith('/'):
                    abort(400, description="Invalid file path")

                filepath = os.path.join(protected_dir, safe_path)

                if not os.path.exists(filepath):
                    abort(404, description="File not found")

                return send_from_directory(protected_dir, safe_path)

            @app.route('/generate-access-token/<path:filename>')
            def generate_file_access_token(filename):
                """为受保护文件生成访问令牌"""
                if not filename.startswith('protected/'):
                    return jsonify({'error': 'File is not in protected directory'}), 400

                token = generate_access_token(filename)
                file_url = url_for('serve_protected_file', filename=filename, token=token)

                return jsonify({
                    'filename': filename,
                    'token': token,
                    'access_url': file_url,
                    'expires_in': app.config['STATIC_TOKEN_EXPIRY']
                })

            @app.route('/protected-demo')
            def protected_demo():
                """受保护文件访问演示"""
                protected_files = [
                    'protected/reports/annual-report-2024.pdf',
                    'protected/manuals/admin-guide.pdf',
                    'protected/data/sensitive-data.json'
                ]

                files_with_tokens = []
                for file_path in protected_files:
                    if os.path.exists(os.path.join(app.static_folder, file_path)):
                        token = generate_access_token(file_path)
                        access_url = url_for('serve_protected_file', filename=file_path, token=token)
                        files_with_tokens.append({
                            'path': file_path,
                            'access_url': access_url,
                            'expires_in': app.config['STATIC_TOKEN_EXPIRY']
                        })

                return render_template('protected_demo.html', files=files_with_tokens)
        b.文件类型限制和病毒扫描
            from flask import Flask, send_from_directory, request, jsonify
            import os
            import magic
            import subprocess
            from werkzeug.utils import secure_filename

            app = Flask(__name__)

            # 文件类型白名单
            ALLOWED_MIME_TYPES = {
                'image/jpeg', 'image/png', 'image/gif', 'image/webp',
                'text/plain', 'text/csv', 'text/html', 'text/css',
                'application/pdf', 'application/json', 'application/javascript',
                'font/woff', 'font/woff2', 'font/ttf', 'font/otf'
            }

            MAX_FILE_SIZE = 10 * 1024 * 1024  # 10MB

            def scan_file_for_viruses(filepath):
                """使用ClamAV扫描文件病毒"""
                try:
                    # 检查是否安装了ClamAV
                    subprocess.run(['clamscan', '--version'],
                                 check=True, capture_output=True)

                    # 扫描文件
                    result = subprocess.run(['clamscan', '--no-summary', filepath],
                                         capture_output=True, text=True)

                    return result.returncode == 0, result.stdout
                except (subprocess.CalledProcessError, FileNotFoundError):
                    # 如果没有安装ClamAV,返回未扫描状态
                    return True, "Virus scanner not available"

            def validate_file_type(filepath):
                """验证文件类型"""
                try:
                    # 使用python-magic库检测文件类型
                    file_type = magic.from_file(filepath, mime=True)
                    return file_type in ALLOWED_MIME_TYPES, file_type
                except Exception:
                    # 如果无法检测,返回未知
                    return False, 'unknown'

            @app.route('/static/secure/<path:filename>')
            def serve_secure_file(filename):
                """安全地服务静态文件"""
                static_dir = app.static_folder

                # 安全路径处理
                safe_filename = secure_filename(filename)
                safe_path = os.path.normpath(safe_filename)

                if safe_path.startswith('..') or safe_path.startswith('/'):
                    return jsonify({'error': 'Invalid file path'}), 400

                filepath = os.path.join(static_dir, safe_path)

                if not os.path.exists(filepath):
                    return jsonify({'error': 'File not found'}), 404

                # 文件大小检查
                file_size = os.path.getsize(filepath)
                if file_size > MAX_FILE_SIZE:
                    return jsonify({'error': 'File too large'}), 413

                # 文件类型验证
                is_allowed, detected_type = validate_file_type(filepath)
                if not is_allowed:
                    return jsonify({
                        'error': 'File type not allowed',
                        'detected_type': detected_type
                    }), 403

                # 病毒扫描(异步,不阻塞响应)
                scan_result = scan_file_for_viruses(filepath)

                response = send_from_directory(static_dir, safe_path)

                # 添加安全头
                response.headers['X-Content-Type-Options'] = 'nosniff'
                response.headers['X-Frame-Options'] = 'DENY'
                response.headers['X-File-Type'] = detected_type
                response.headers['X-Virus-Scanned'] = 'safe' if scan_result[0] else 'infected'

                return response

            @app.route('/upload-secure', methods=['POST'])
            def upload_secure_file():
                """安全文件上传"""
                if 'file' not in request.files:
                    return jsonify({'error': 'No file provided'}), 400

                file = request.files['file']
                if file.filename == '':
                    return jsonify({'error': 'No file selected'}), 400

                # 保存临时文件进行验证
                temp_dir = 'temp_uploads'
                os.makedirs(temp_dir, exist_ok=True)

                secure_name = secure_filename(file.filename)
                temp_path = os.path.join(temp_dir, secure_name)
                file.save(temp_path)

                try:
                    # 文件大小检查
                    if os.path.getsize(temp_path) > MAX_FILE_SIZE:
                        return jsonify({'error': 'File too large'}), 413

                    # 文件类型验证
                    is_allowed, detected_type = validate_file_type(temp_path)
                    if not is_allowed:
                        return jsonify({
                            'error': f'File type {detected_type} not allowed'
                        }), 403

                    # 病毒扫描
                    is_safe, scan_result = scan_file_for_viruses(temp_path)
                    if not is_safe:
                        return jsonify({
                            'error': 'File contains viruses',
                            'scan_result': scan_result
                        }), 403

                    # 移动到最终位置
                    final_path = os.path.join(app.static_folder, 'uploads', secure_name)
                    os.makedirs(os.path.dirname(final_path), exist_ok=True)
                    os.rename(temp_path, final_path)

                    return jsonify({
                        'message': 'File uploaded successfully',
                        'filename': secure_name,
                        'url': url_for('static', filename=f'uploads/{secure_name}'),
                        'file_type': detected_type,
                        'file_size': os.path.getsize(final_path)
                    })

                except Exception as e:
                    # 清理临时文件
                    if os.path.exists(temp_path):
                        os.remove(temp_path)
                    return jsonify({'error': f'Upload failed: {str(e)}'}), 500

4 Blueprint 架构

4.1 基础概念

01.什么是 Blueprint
    a.Blueprint 的定义和用途
        a.基础 Blueprint 概念
            from flask import Flask, Blueprint, jsonify, render_template
            import os

            app = Flask(__name__)

            # 创建一个基础 Blueprint
            user_bp = Blueprint('users', __name__)

            # 定义 Blueprint 路由
            @user_bp.route('/')
            def user_index():
                return jsonify({
                    'message': 'Welcome to Users Module',
                    'blueprint': 'users',
                    'endpoints': [
                        '/users/',
                        '/users/<int:user_id>',
                        '/users/create',
                        '/users/search'
                    ]
                })

            @user_bp.route('/<int:user_id>')
            def get_user(user_id):
                """获取用户信息"""
                # 模拟用户数据
                users = {
                    1: {'id': 1, 'name': 'Alice', 'email': '[email protected]'},
                    2: {'id': 2, 'name': 'Bob', 'email': '[email protected]'},
                    3: {'id': 3, 'name': 'Charlie', 'email': '[email protected]'}
                }

                user = users.get(user_id)
                if user:
                    return jsonify({
                        'user': user,
                        'blueprint': 'users',
                        'endpoint': 'get_user'
                    })
                else:
                    return jsonify({'error': 'User not found'}), 404

            # 注册 Blueprint 到应用
            app.register_blueprint(user_bp, url_prefix='/users')

            if __name__ == '__main__':
                app.run(debug=True)
        b.模块化应用结构
            # app.py - 主应用文件
            from flask import Flask
            from blueprints.users import user_bp
            from blueprints.products import product_bp
            from blueprints.admin import admin_bp

            def create_app():
                """应用工厂函数"""
                app = Flask(__name__)

                # 应用配置
                app.config.update({
                    'SECRET_KEY': 'your-secret-key-here',
                    'DEBUG': True
                })

                # 注册 Blueprint
                app.register_blueprint(user_bp, url_prefix='/users')
                app.register_blueprint(product_bp, url_prefix='/products')
                app.register_blueprint(admin_bp, url_prefix='/admin')

                # 主页路由
                @app.route('/')
                def index():
                    return jsonify({
                        'message': 'Welcome to Modular Flask App',
                        'modules': {
                            'users': '/users',
                            'products': '/products',
                            'admin': '/admin'
                        }
                    })

                return app

            if __name__ == '__main__':
                app = create_app()
                app.run(debug=True)

            # blueprints/users.py - 用户模块
            from flask import Blueprint, jsonify, request

            user_bp = Blueprint('users', __name__)

            # 模拟用户数据库
            users_db = {}
            next_user_id = 1

            @user_bp.route('/', methods=['GET', 'POST'])
            def handle_users():
                global next_user_id

                if request.method == 'GET':
                    # 获取用户列表
                    return jsonify({
                        'users': list(users_db.values()),
                        'total': len(users_db)
                    })

                elif request.method == 'POST':
                    # 创建新用户
                    data = request.get_json()
                    if not data or 'name' not in data:
                        return jsonify({'error': 'Name is required'}), 400

                    user = {
                        'id': next_user_id,
                        'name': data['name'],
                        'email': data.get('email', ''),
                        'created_at': '2024-01-15T10:00:00Z'
                    }

                    users_db[next_user_id] = user
                    next_user_id += 1

                    return jsonify(user), 201

            @user_bp.route('/<int:user_id>')
            def get_user(user_id):
                user = users_db.get(user_id)
                if user:
                    return jsonify(user)
                else:
                    return jsonify({'error': 'User not found'}), 404

            @user_bp.route('/search')
            def search_users():
                """搜索用户"""
                name_query = request.args.get('name', '').lower()
                results = [user for user in users_db.values()
                          if name_query in user['name'].lower()]

                return jsonify({
                    'query': name_query,
                    'results': results,
                    'count': len(results)
                })

02.创建和注册 Blueprint
    a.基础 Blueprint 创建
        a.简单 Blueprint 创建模式
            from flask import Blueprint, render_template, request, jsonify, redirect, url_for

            # 1. 基础 Blueprint
            auth_bp = Blueprint('auth', __name__,
                              template_folder='templates/auth',
                              static_folder='static/auth')

            @auth_bp.route('/login', methods=['GET', 'POST'])
            def login():
                if request.method == 'GET':
                    return render_template('login.html')
                else:
                    data = request.get_json()
                    username = data.get('username')
                    password = data.get('password')

                    # 简单验证逻辑
                    if username == 'admin' and password == 'password':
                        return jsonify({
                            'message': 'Login successful',
                            'user': {'username': username, 'role': 'admin'},
                            'token': 'fake-jwt-token-12345'
                        })
                    else:
                        return jsonify({'error': 'Invalid credentials'}), 401

            @auth_bp.route('/register', methods=['GET', 'POST'])
            def register():
                if request.method == 'GET':
                    return render_template('register.html')
                else:
                    data = request.get_json()
                    username = data.get('username')
                    email = data.get('email')
                    password = data.get('password')

                    # 验证输入
                    if not all([username, email, password]):
                        return jsonify({'error': 'All fields are required'}), 400

                    # 模拟用户注册
                    user = {
                        'id': len(users_db) + 1,
                        'username': username,
                        'email': email,
                        'password': password,  # 实际应用中需要加密
                        'created_at': '2024-01-15T10:00:00Z'
                    }

                    users_db[user['id']] = user

                    return jsonify({
                        'message': 'Registration successful',
                        'user': {'id': user['id'], 'username': username, 'email': email}
                    }), 201

            @auth_bp.route('/logout')
            def logout():
                return jsonify({'message': 'Logged out successfully'})

            # 2. 带前缀和子域名的 Blueprint
            api_bp = Blueprint('api', __name__, url_prefix='/api/v1')

            @api_bp.route('/status')
            def api_status():
                return jsonify({
                    'status': 'running',
                    'version': '1.0.0',
                    'timestamp': '2024-01-15T10:00:00Z'
                })

            # 3. 带模板和静态文件的 Blueprint
            dashboard_bp = Blueprint('dashboard', __name__,
                                    template_folder='templates/dashboard',
                                    static_folder='static/dashboard')

            @dashboard_bp.route('/')
            def dashboard():
                return render_template('dashboard.html',
                                     user_count=len(users_db),
                                     recent_users=list(users_db.values())[-5:])

            # 在主应用中注册
            app.register_blueprint(auth_bp, url_prefix='/auth')
            app.register_blueprint(api_bp)
            app.register_blueprint(dashboard_bp, url_prefix='/dashboard')
        b.高级 Blueprint 注册选项
            from flask import Flask, Blueprint, render_template_string

            app = Flask(__name__)

            # 创建带有自定义配置的 Blueprint
            blog_bp = Blueprint('blog', __name__)

            # 注册时指定多种参数
            app.register_blueprint(
                blog_bp,
                url_prefix='/blog',                    # URL 前缀
                subdomain='blog',                      # 子域名 (需要配置)
                name='blog_module',                    # 自定义名称
                static_folder='static',                # 静态文件文件夹
                template_folder='templates',           # 模板文件夹
                static_url_path='/blog/static',       # 静态文件URL路径
                url_defaults={'page': 1}              # URL默认参数
            )

            # 使用默认参数的路由
            @blog_bp.route('/posts')
            @blog_bp.route('/posts/<int:page>')
            def list_posts(page=1):
                posts = [
                    {'id': 1, 'title': 'First Post', 'content': 'This is the first post'},
                    {'id': 2, 'title': 'Second Post', 'content': 'This is the second post'},
                    {'id': 3, 'title': 'Third Post', 'content': 'This is the third post'}
                ]

                # 模拟分页
                per_page = 2
                start = (page - 1) * per_page
                end = start + per_page
                paginated_posts = posts[start:end]

                return render_template_string('''
                    <h1>Blog Posts (Page {{ page }})</h1>
                    <ul>
                        {% for post in posts %}
                        <li>{{ post.title }} - {{ post.content }}</li>
                        {% endfor %}
                    </ul>
                    {% if posts|length >= per_page %}
                    <a href="{{ url_for('blog.list_posts', page=page+1) }}">Next Page</a>
                    {% endif %}
                ''', posts=paginated_posts, page=page)

            # 动态注册 Blueprint
            def register_dynamic_blueprints():
                """动态注册多个 Blueprint"""
                modules = ['users', 'products', 'orders']

                for module in modules:
                    bp = Blueprint(module, __name__, url_prefix=f'/{module}')

                    # 为每个模块添加通用路由
                    @bp.route('/')
                    def module_index():
                        current_module = request.blueprint
                        return jsonify({
                            'module': current_module,
                            'endpoints': [
                                f'/{current_module}',
                                f'/{current_module}/<int:id>',
                                f'/{current_module}/search'
                            ]
                        })

                    @bp.route('/<int:id>')
                    def module_detail(id):
                        return jsonify({
                            'module': request.blueprint,
                            'id': id,
                            'detail': f'Details for {request.blueprint} {id}'
                        })

                    app.register_blueprint(bp)

            register_dynamic_blueprints()

03.Blueprint 配置和属性
    a.Blueprint 属性设置
        a.基本属性配置
            from flask import Blueprint, render_template, url_for

            # 带完整属性的 Blueprint
            admin_bp = Blueprint(
                'admin',                              # Blueprint 名称
                __name__,
                url_prefix='/admin',                  # URL 前缀
                subdomain='admin',                    # 子域名
                static_folder='static/admin',         # 静态文件目录
                static_url_path='/admin/static',      # 静态文件URL路径
                template_folder='templates/admin',    # 模板目录
                url_defaults={'role': 'admin'}        # URL 默认值
            )

            @admin_bp.before_request
            def admin_before_request():
                """Blueprint 级别的请求前处理"""
                # 模拟管理员权限检查
                auth_header = request.headers.get('Authorization')
                if not auth_header or not auth_header.startswith('Bearer '):
                    return jsonify({'error': 'Admin access required'}), 401

            @admin_bp.route('/dashboard/<role>')
            def dashboard(role):
                return render_template('admin_dashboard.html',
                                     role=role,
                                     blueprint_name=admin_bp.name,
                                     url_prefix=admin_bp.url_prefix)

            @admin_bp.context_processor
            def admin_context_processor():
                """Blueprint 级别的模板上下文处理器"""
                return {
                    'admin_menu_items': [
                        {'name': 'Dashboard', 'url': url_for('admin.dashboard', role='admin')},
                        {'name': 'Users', 'url': url_for('admin.users_list')},
                        {'name': 'Settings', 'url': url_for('admin.settings')}
                    ],
                    'current_year': 2024
                }

            # 带错误处理的 Blueprint
            api_bp = Blueprint('api', __name__, url_prefix='/api')

            @api_bp.errorhandler(404)
            def api_404(error):
                return jsonify({
                    'error': 'API resource not found',
                    'path': request.path,
                    'blueprint': 'api'
                }), 404

            @api_bp.errorhandler(500)
            def api_500(error):
                return jsonify({
                    'error': 'Internal server error',
                    'message': 'Something went wrong on the server',
                    'blueprint': 'api'
                }), 500
        b.Blueprint 信息获取
            from flask import Blueprint, jsonify, current_app
            import inspect

            info_bp = Blueprint('info', __name__, url_prefix='/info')

            @info_bp.route('/blueprint-details')
            def blueprint_details():
                """获取当前 Blueprint 的详细信息"""
                current_bp = current_app.blueprints.get(request.blueprint)

                if current_bp:
                    return jsonify({
                        'blueprint_name': current_bp.name,
                        'url_prefix': current_bp.url_prefix,
                        'subdomain': getattr(current_bp, 'subdomain', None),
                        'static_folder': current_bp.static_folder,
                        'static_url_path': current_bp.static_url_path,
                        'template_folder': current_bp.template_folder,
                        'url_defaults': getattr(current_bp, 'url_defaults', {}),
                        'deferred_functions': len(current_bp.deferred_functions),
                        'has_request_handlers': hasattr(current_bp, 'deferred_functions')
                    })
                else:
                    return jsonify({'error': 'Blueprint not found'}), 404

            @info_bp.route('/all-blueprints')
            def all_blueprints_info():
                """获取应用中所有 Blueprint 的信息"""
                blueprints_info = {}

                for name, bp in current_app.blueprints.items():
                    blueprints_info[name] = {
                        'name': bp.name,
                        'url_prefix': bp.url_prefix,
                        'subdomain': getattr(bp, 'subdomain', None),
                        'static_folder': bp.static_folder,
                        'template_folder': bp.template_folder
                    }

                return jsonify({
                    'total_blueprints': len(blueprints_info),
                    'blueprints': blueprints_info
                })

            @info_bp.route('/routes')
            def list_routes():
                """列出当前 Blueprint 的所有路由"""
                routes = []

                for rule in current_app.url_map.iter_rules():
                    if request.blueprint and rule.endpoint.startswith(f"{request.blueprint}."):
                        routes.append({
                            'endpoint': rule.endpoint,
                            'methods': list(rule.methods - {'HEAD', 'OPTIONS'}),
                            'rule': str(rule),
                            'arguments': list(rule.arguments)
                        })

                return jsonify({
                    'blueprint': request.blueprint,
                    'total_routes': len(routes),
                    'routes': routes
                })

04.多 Blueprint 协作
    a.Blueprint 间通信
        a.共享数据和方法
            from flask import Blueprint, g, current_app
            functools

            app = Flask(__name__)

            # 共享服务类
            class UserService:
                def __init__(self):
                    self.users = {}

                def get_user(self, user_id):
                    return self.users.get(user_id)

                def create_user(self, user_data):
                    user_id = len(self.users) + 1
                    user = {'id': user_id, **user_data}
                    self.users[user_id] = user
                    return user

                def update_user(self, user_id, updates):
                    if user_id in self.users:
                        self.users[user_id].update(updates)
                        return self.users[user_id]
                    return None

            # 初始化共享服务
            user_service = UserService()

            # 用户管理 Blueprint
            users_bp = Blueprint('users', __name__, url_prefix='/users')

            @users_bp.before_request
            def inject_user_service():
                """在请求前注入服务到 g 对象"""
                g.user_service = user_service

            @users_bp.route('/', methods=['GET', 'POST'])
            def handle_users():
                if request.method == 'GET':
                    return jsonify({
                        'users': list(g.user_service.users.values()),
                        'total': len(g.user_service.users)
                    })

                elif request.method == 'POST':
                    data = request.get_json()
                    user = g.user_service.create_user(data)
                    return jsonify(user), 201

            @users_bp.route('/<int:user_id>')
            def get_user(user_id):
                user = g.user_service.get_user(user_id)
                if user:
                    return jsonify(user)
                else:
                    return jsonify({'error': 'User not found'}), 404

            # 订单管理 Blueprint
            orders_bp = Blueprint('orders', __name__, url_prefix='/orders')

            # 模拟订单数据库
            orders_db = {}

            @orders_bp.before_request
            def inject_services():
                """订单服务也需要用户服务"""
                g.user_service = user_service
                g.orders_db = orders_db

            @orders_bp.route('/', methods=['GET', 'POST'])
            def handle_orders():
                if request.method == 'GET':
                    return jsonify({
                        'orders': list(g.orders_db.values()),
                        'total': len(g.orders_db)
                    })

                elif request.method == 'POST':
                    data = request.get_json()
                    user_id = data.get('user_id')

                    # 使用用户服务验证用户
                    user = g.user_service.get_user(user_id)
                    if not user:
                        return jsonify({'error': 'User not found'}), 404

                    # 创建订单
                    order_id = len(g.orders_db) + 1
                    order = {
                        'id': order_id,
                        'user_id': user_id,
                        'user_name': user['name'],
                        'items': data.get('items', []),
                        'total': data.get('total', 0),
                        'status': 'pending',
                        'created_at': '2024-01-15T10:00:00Z'
                    }

                    g.orders_db[order_id] = order
                    return jsonify(order), 201

            @orders_bp.route('/user/<int:user_id>')
            def get_user_orders(user_id):
                """获取特定用户的订单"""
                # 验证用户存在
                user = g.user_service.get_user(user_id)
                if not user:
                    return jsonify({'error': 'User not found'}), 404

                # 获取用户订单
                user_orders = [
                    order for order in g.orders_db.values()
                    if order['user_id'] == user_id
                ]

                return jsonify({
                    'user': user,
                    'orders': user_orders,
                    'order_count': len(user_orders)
                })

            # 注册 Blueprint
            app.register_blueprint(users_bp)
            app.register_blueprint(orders_bp)
        b.Blueprint 事件钩子
            from flask import Blueprint, request, g, current_app
            import time

            app = Flask(__name__)

            # 日志记录 Blueprint
            logging_bp = Blueprint('logging', __name__)

            @logging_bp.before_app_request
            def log_before_request():
                """应用级别的请求前日志"""
                g.start_time = time.time()
                g.request_id = request.headers.get('X-Request-ID', 'unknown')

                current_app.logger.info(
                    f"Request {g.request_id} started: {request.method} {request.path}"
                )

            @logging_bp.after_app_request
            def log_after_request(response):
                """应用级别的请求后日志"""
                if hasattr(g, 'start_time'):
                    duration = time.time() - g.start_time
                    current_app.logger.info(
                        f"Request {getattr(g, 'request_id', 'unknown')} completed "
                        f"in {duration:.3f}s with status {response.status_code}"
                    )
                return response

            # 性能监控 Blueprint
            monitoring_bp = Blueprint('monitoring', __name__)

            @monitoring_bp.before_request
            def start_monitoring():
                """开始性能监控"""
                g.monitor_start = time.time()

            @monitoring_bp.after_request
            def end_monitoring(response):
                """结束性能监控"""
                if hasattr(g, 'monitor_start'):
                    duration = time.time() - g.monitor_start
                    response.headers['X-Monitoring-Time'] = str(duration)

                    # 记录慢请求
                    if duration > 1.0:
                        current_app.logger.warning(
                            f"Slow request detected: {request.path} took {duration:.3f}s"
                        )

                return response

            @monitoring_bp.route('/performance-stats')
            def performance_stats():
                """性能统计信息"""
                return jsonify({
                    'blueprint': monitoring_bp.name,
                    'endpoints': [str(rule) for rule in current_app.url_map.iter_rules()
                                 if rule.endpoint.startswith(f'{monitoring_bp.name}.')],
                    'timestamp': time.time()
                })

            # 安全检查 Blueprint
            security_bp = Blueprint('security', __name__)

            @security_bp.before_request
            def security_check():
                """安全检查"""
                # 检查必需的安全头
                required_headers = ['X-Requested-With']
                missing_headers = [h for h in required_headers if h not in request.headers]

                if missing_headers and request.method in ['POST', 'PUT', 'DELETE']:
                    return jsonify({
                        'error': 'Security check failed',
                        'missing_headers': missing_headers
                    }), 400

            @security_bp.route('/secure-data')
            def secure_data():
                """需要安全检查的端点"""
                return jsonify({
                    'message': 'Secure data accessed successfully',
                    'security_status': 'passed',
                    'timestamp': time.time()
                })

            # 注册所有 Blueprint
            app.register_blueprint(logging_bp)
            app.register_blueprint(monitoring_bp, url_prefix='/monitoring')
            app.register_blueprint(security_bp, url_prefix='/secure')

            @app.route('/')
            def index():
                return jsonify({
                    'message': 'Multi-Blueprint Application',
                    'modules': {
                        'monitoring': '/monitoring/performance-stats',
                        'secure': '/secure/secure-data'
                    }
                })

4.2 路由管理

01.路由定义和 URL 规则
    a.动态路由注册
        a.程序化路由注册
            from flask import Blueprint, Flask, jsonify
            import inspect

            # 创建 Blueprint
            products_bp = Blueprint('products', __name__, url_prefix='/products')

            # 方法 1: 装饰器方式注册路由
            @products_bp.route('/')
            def product_list():
                return jsonify({
                    'products': [
                        {'id': 1, 'name': 'Laptop', 'price': 999.99},
                        {'id': 2, 'name': 'Phone', 'price': 699.99},
                        {'id': 3, 'name': 'Tablet', 'price': 399.99}
                    ]
                })

            # 方法 2: 直接注册视图函数
            def get_product_details(product_id):
                products = {
                    1: {'id': 1, 'name': 'Laptop', 'price': 999.99, 'stock': 50},
                    2: {'id': 2, 'name': 'Phone', 'price': 699.99, 'stock': 100},
                    3: {'id': 3, 'name': 'Tablet', 'price': 399.99, 'stock': 75}
                }

                product = products.get(product_id)
                if product:
                    return jsonify(product)
                else:
                    return jsonify({'error': 'Product not found'}), 404

            # 手动注册路由
            products_bp.add_url_rule(
                '/<int:product_id>',
                'product_details',
                get_product_details,
                methods=['GET']
            )

            # 方法 3: 基于类的视图路由
            class ProductAPI:
                def __init__(self):
                    self.products = {}

                def get(self, product_id=None):
                    if product_id:
                        return self.products.get(product_id) or {'error': 'Not found'}
                    else:
                        return list(self.products.values())

                def post(self, product_data):
                    product_id = len(self.products) + 1
                    product = {'id': product_id, **product_data}
                    self.products[product_id] = product
                    return product

                def put(self, product_id, product_data):
                    if product_id in self.products:
                        self.products[product_id].update(product_data)
                        return self.products[product_id]
                    else:
                        return {'error': 'Product not found'}

                def delete(self, product_id):
                    return self.products.pop(product_id, None)

            # 创建实例并注册多个路由
            product_api = ProductAPI()

            # 注册不同的路由到同一个类方法
            products_bp.add_url_rule(
                '/api',
                'product_api_list',
                product_api.get,
                methods=['GET']
            )

            products_bp.add_url_rule(
                '/api',
                'product_api_create',
                lambda: product_api.post(request.get_json()),
                methods=['POST']
            )

            products_bp.add_url_rule(
                '/api/<int:product_id>',
                'product_api_detail',
                lambda product_id: product_api.get(product_id),
                methods=['GET']
            )

            # 动态路由生成器
            def generate_crud_routes(bp, resource_name, resource_class):
                """为资源类生成 CRUD 路由"""
                resource_instance = resource_class()

                # 资源列表 (GET) 和创建 (POST)
                def resource_list():
                    if request.method == 'GET':
                        return jsonify(resource_instance.get_all())
                    else:
                        return jsonify(resource_instance.create(request.get_json()))

                # 资源详情 (GET), 更新 (PUT), 删除 (DELETE)
                def resource_detail(resource_id):
                    if request.method == 'GET':
                        return jsonify(resource_instance.get(resource_id))
                    elif request.method == 'PUT':
                        return jsonify(resource_instance.update(resource_id, request.get_json()))
                    elif request.method == 'DELETE':
                        return jsonify(resource_instance.delete(resource_id))

                # 注册路由
                bp.add_url_rule(
                    f'/{resource_name}',
                    f'{resource_name}_list',
                    resource_list,
                    methods=['GET', 'POST']
                )

                bp.add_url_rule(
                    f'/{resource_name}/<int:resource_id>',
                    f'{resource_name}_detail',
                    resource_detail,
                    methods=['GET', 'PUT', 'DELETE']
                )

            # 示例资源类
            class CategoryManager:
                def __init__(self):
                    self.categories = {
                        1: {'id': 1, 'name': 'Electronics', 'description': 'Electronic devices'},
                        2: {'id': 2, 'name': 'Books', 'description': 'Printed books and e-books'}
                    }

                def get_all(self):
                    return list(self.categories.values())

                def create(self, data):
                    cat_id = len(self.categories) + 1
                    category = {'id': cat_id, **data}
                    self.categories[cat_id] = category
                    return category

                def get(self, cat_id):
                    return self.categories.get(cat_id)

                def update(self, cat_id, data):
                    if cat_id in self.categories:
                        self.categories[cat_id].update(data)
                        return self.categories[cat_id]
                    return None

                def delete(self, cat_id):
                    return self.categories.pop(cat_id, None)

            # 生成分类资源的 CRUD 路由
            generate_crud_routes(products_bp, 'categories', CategoryManager)
        b.条件路由注册
            from flask import Blueprint, request, current_app, abort

            conditional_bp = Blueprint('conditional', __name__)

            # 条件路由注册器
            class ConditionalRouter:
                def __init__(self, blueprint):
                    self.blueprint = blueprint
                    self.routes = []

                def add_conditional_route(self, rule, condition, endpoint=None, **options):
                    """添加条件路由"""
                    def conditional_view(*args, **kwargs):
                        if condition():
                            # 满足条件时执行
                            return self.routes[-1]['view'](*args, **kwargs)
                        else:
                            # 不满足条件时返回404或自定义响应
                            abort(404, description="Route not available")

                    # 包装原视图函数
                    original_view = self.routes[-1]['view'] if self.routes else (lambda: "OK")

                    # 保存原视图函数
                    self.routes.append({
                        'view': original_view,
                        'condition': condition
                    })

                    self.blueprint.add_url_rule(rule, endpoint, conditional_view, **options)

            # 使用条件路由
            router = ConditionalRouter(conditional_bp)

            # 只在开发环境可用的路由
            @conditional_bp.route('/debug')
            def debug_info():
                return jsonify({
                    'debug_mode': current_app.debug,
                    'routes': [str(rule) for rule in current_app.url_map.iter_rules()],
                    'config': {k: str(v) for k, v in current_app.config.items()}
                })

            # 只有管理员可访问的路由
            @conditional_bp.route('/admin-only')
            def admin_only_route():
                return jsonify({
                    'message': 'Admin only content',
                    'user_role': 'admin'
                })

            # 修改路由注册以添加条件
            def is_admin():
                """检查是否为管理员"""
                auth_header = request.headers.get('Authorization', '')
                return 'admin' in auth_header

            # 手动添加条件检查
            conditional_bp.add_url_rule(
                '/admin-only',
                'admin_only_route',
                lambda: admin_only_route() if is_admin() else abort(403),
                methods=['GET']
            )

            # 基于时间的条件路由
            @conditional_bp.route('/limited-time')
            def limited_time_offer():
                return jsonify({
                    'message': 'Limited time offer available!',
                    'valid_until': '2024-12-31T23:59:59Z'
                })

            def is_offer_valid():
                """检查优惠是否有效"""
                from datetime import datetime
                return datetime.now().year < 2025

            conditional_bp.add_url_rule(
                '/limited-time',
                'limited_time_offer',
                lambda: limited_time_offer() if is_offer_valid() else abort(404),
                methods=['GET']
            )

02.路由优先级和冲突处理
    a.路由冲突解决
        a.路由优先级系统
            from flask import Blueprint, Flask, jsonify, request, abort
            import re

            app = Flask(__name__)

            # 路由优先级管理器
            class RoutePriorityManager:
                def __init__(self, app):
                    self.app = app
                    self.priority_routes = {}

                def add_priority_route(self, blueprint, rule, view_func, priority=0, **options):
                    """添加优先级路由"""
                    endpoint = f"priority_{len(self.priority_routes)}"

                    # 保存路由信息
                    self.priority_routes[endpoint] = {
                        'blueprint': blueprint,
                        'rule': rule,
                        'view_func': view_func,
                        'priority': priority,
                        'options': options
                    }

                    # 注册路由
                    blueprint.add_url_rule(rule, endpoint, view_func, **options)

                def reorder_routes(self):
                    """重新排序路由"""
                    # 按优先级排序
                    sorted_routes = sorted(
                        self.priority_routes.items(),
                        key=lambda x: x['priority'],
                        reverse=True
                    )

                    # 重新注册路由
                    for endpoint, route_info in sorted_routes:
                        route_info['blueprint'].add_url_rule(
                            route_info['rule'],
                            endpoint,
                            route_info['view_func'],
                            **route_info['options']
                        )

            # 创建带优先级的 Blueprint
            api_bp = Blueprint('api', __name__, url_prefix='/api')

            priority_manager = RoutePriorityManager(app)

            # 高优先级路由 - 具体用户ID
            @api_bp.route('/users/<int:user_id>')
            def get_specific_user(user_id):
                users = {
                    1: {'id': 1, 'name': 'Alice', 'type': 'premium'},
                    2: {'id': 2, 'name': 'Bob', 'type': 'regular'}
                }
                user = users.get(user_id)
                if user:
                    return jsonify({'user': user, 'endpoint': 'specific'})
                else:
                    return jsonify({'error': 'User not found'}), 404

            # 中等优先级路由 - 用户名
            @api_bp.route('/users/<username>')
            def get_user_by_name(username):
                users = {
                    'alice': {'name': 'Alice', 'type': 'premium'},
                    'bob': {'name': 'Bob', 'type': 'regular'}
                }
                user = users.get(username.lower())
                if user:
                    return jsonify({'user': user, 'endpoint': 'by_name'})
                else:
                    return jsonify({'error': 'User not found'}), 404

            # 低优先级路由 - 用户搜索
            @api_bp.route('/users')
            def search_users():
                query = request.args.get('q', '').lower()
                all_users = [
                    {'id': 1, 'name': 'Alice', 'type': 'premium'},
                    {'id': 2, 'name': 'Bob', 'type': 'regular'}
                ]

                if query:
                    filtered = [u for u in all_users if query in u['name'].lower()]
                else:
                    filtered = all_users

                return jsonify({'users': filtered, 'endpoint': 'search'})

            # 注册优先级路由
            priority_manager.add_priority_route(api_bp, '/users/<int:user_id>', get_specific_user, priority=3)
            priority_manager.add_priority_route(api_bp, '/users/<username>', get_user_by_name, priority=2)
            priority_manager.add_priority_route(api_bp, '/users', search_users, priority=1)

            # 智能路由匹配系统
            class SmartRouter:
                def __init__(self):
                    self.routes = []

                def add_smart_route(self, pattern, view_func, priority=0):
                    """添加智能路由"""
                    self.routes.append({
                        'pattern': pattern,
                        'view_func': view_func,
                        'priority': priority,
                        'compiled_pattern': re.compile(pattern)
                    })

                def match_and_execute(self, path):
                    """匹配并执行路由"""
                    # 按优先级排序
                    sorted_routes = sorted(self.routes, key=lambda x: x['priority'], reverse=True)

                    for route in sorted_routes:
                        match = route['compiled_pattern'].match(path)
                        if match:
                            return route['view_func'](*match.groups())

                    return None  # 没有匹配的路由

            # 创建智能路由器
            smart_bp = Blueprint('smart', __name__, url_prefix='/smart')
            smart_router = SmartRouter()

            # 定义智能路由模式
            def product_handler(product_id=None, action=None):
                if product_id and action:
                    return jsonify({
                        'product_id': int(product_id),
                        'action': action,
                        'message': f'Action {action} on product {product_id}'
                    })
                elif product_id:
                    return jsonify({
                        'product_id': int(product_id),
                        'message': f'Details for product {product_id}'
                    })
                else:
                    return jsonify({'message': 'Product list'})

            def search_handler(term=None):
                if term:
                    return jsonify({
                        'search_term': term,
                        'results': [f'Product {term}', f'Item {term}']
                    })
                else:
                    return jsonify({'message': 'Search page'})

            # 添加智能路由
            smart_router.add_smart_route(
                r'/products/(\d+)/(edit|delete|view)/?$',
                lambda pid, act: product_handler(pid, act),
                priority=3
            )

            smart_router.add_smart_route(
                r'/products/(\d+)/?$',
                lambda pid: product_handler(pid),
                priority=2
            )

            smart_router.add_smart_route(
                r'/products/search/([^/]+)/?$',
                lambda term: search_handler(term),
                priority=1
            )

            @smart_bp.route('/products/<path:subpath>')
            def smart_product_handler(subpath):
                """智能产品路由处理器"""
                full_path = f'/products/{subpath}'
                result = smart_router.match_and_execute(full_path)

                if result:
                    return result
                else:
                    return jsonify({'error': 'Product not found'}), 404

            app.register_blueprint(api_bp)
            app.register_blueprint(smart_bp)

4.3 模板和静态文件

01.模板管理
    a.Blueprint 专属模板目录
        a.模板目录结构设置
            from flask import Flask, Blueprint, render_template, current_app
            import os

            app = Flask(__name__)

            # Blueprint 1: 用户模块 - 使用独立模板目录
            user_bp = Blueprint('user', __name__,
                              template_folder='templates/users',     # 独立模板目录
                              static_folder='static/users')         # 独立静态文件目录

            @user_bp.route('/profile')
            def user_profile():
                # Blueprint 会先在 templates/users 目录查找模板
                return render_template('profile.html',
                                     user={'name': 'Alice', 'email': '[email protected]'})

            @user_bp.route('/dashboard')
            def user_dashboard():
                return render_template('dashboard.html',
                                     stats={'posts': 15, 'comments': 42, 'likes': 128})

            # Blueprint 2: 管理模块 - 使用独立模板目录
            admin_bp = Blueprint('admin', __name__,
                                template_folder='templates/admin',
                                static_folder='static/admin')

            @admin_bp.route('/dashboard')
            def admin_dashboard():
                return render_template('dashboard.html',
                                     user_count=150,
                                     active_sessions=25,
                                     system_status='healthy')

            @admin_bp.route('/users')
            def admin_users():
                users = [
                    {'id': 1, 'name': 'Alice', 'role': 'user', 'status': 'active'},
                    {'id': 2, 'name': 'Bob', 'role': 'editor', 'status': 'active'},
                    {'id': 3, 'name': 'Charlie', 'role': 'admin', 'status': 'inactive'}
                ]
                return render_template('users.html', users=users)

            # Blueprint 3: 公共模块 - 共享主应用模板
            public_bp = Blueprint('public', __name__)  # 不指定模板目录,使用主应用模板

            @public_bp.route('/about')
            def about():
                return render_template('public/about.html')

            @public_bp.route('/contact')
            def contact():
                return render_template('public/contact.html')

            # 注册 Blueprint
            app.register_blueprint(user_bp, url_prefix='/user')
            app.register_blueprint(admin_bp, url_prefix='/admin')
            app.register_blueprint(public_bp)

            # 模板搜索路径测试路由
            @app.route('/template-info')
            def template_info():
                """显示模板搜索路径信息"""
                template_folders = []

                # 主应用模板目录
                if hasattr(app, 'template_folder') and app.template_folder:
                    main_template_folder = os.path.join(app.root_path, app.template_folder)
                    template_folders.append({
                        'name': 'Main App Templates',
                        'path': main_template_folder,
                        'exists': os.path.exists(main_template_folder)
                    })

                # Blueprint 模板目录
                for name, blueprint in app.blueprints.items():
                    if hasattr(blueprint, 'template_folder') and blueprint.template_folder:
                        bp_template_folder = os.path.join(
                            blueprint.root_path, blueprint.template_folder
                        )
                        template_folders.append({
                            'name': f'{name} Blueprint Templates',
                            'path': bp_template_folder,
                            'exists': os.path.exists(bp_template_folder)
                        })

                return render_template('template_info.html', folders=template_folders)
        b.模板继承和覆盖
            from flask import Blueprint, render_template
            import os

            # 创建复杂的模板继承结构

            # 基础 Blueprint 模板
            <!-- templates/base/base_layout.html -->
            <!DOCTYPE html>
            <html lang="zh-CN">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>{% block title %}Flask App{% endblock %}</title>

                <!-- 基础CSS -->
                {% block base_styles %}
                <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
                <link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
                {% endblock %}

                <!-- Blueprint 特定CSS -->
                {% block styles %}{% endblock %}
            </head>
            <body>
                <!-- 导航栏 -->
                <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
                    <div class="container">
                        <a class="navbar-brand" href="/">Flask Demo</a>

                        <div class="navbar-nav">
                            <a class="nav-link" href="/">首页</a>
                            <a class="nav-link" href="{{ url_for('user.user_profile') }}">用户</a>
                            <a class="nav-link" href="{{ url_for('admin.admin_dashboard') }}">管理</a>
                        </div>

                        <!-- 用户菜单 -->
                        {% block user_menu %}
                        <div class="navbar-nav ms-auto">
                            <a class="nav-link" href="/login">登录</a>
                        </div>
                        {% endblock %}
                    </div>
                </nav>

                <!-- 消息提示 -->
                {% with messages = get_flashed_messages(with_categories=true) %}
                    {% if messages %}
                    <div class="container mt-3">
                        {% for category, message in messages %}
                        <div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
                            {{ message }}
                            <button type="button" class="close" data-dismiss="alert">
                                <span>&times;</span>
                            </button>
                        </div>
                        {% endfor %}
                    </div>
                    {% endif %}
                {% endwith %}

                <!-- 主要内容区域 -->
                <main class="container mt-4">
                    {% block content %}{% endblock %}
                </main>

                <!-- 页脚 -->
                <footer class="bg-light text-center py-3 mt-5">
                    <div class="container">
                        {% block footer %}
                        <p>&copy; 2024 Flask Demo. All rights reserved.</p>
                        {% endblock %}
                    </div>
                </footer>

                <!-- 基础JavaScript -->
                {% block base_scripts %}
                <script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
                <script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
                {% endblock %}

                <!-- 页面特定JavaScript -->
                {% block scripts %}{% endblock %}
            </body>
            </html>

            <!-- templates/base/admin_base.html -->
            {% extends "base/base_layout.html" %}

            {% block styles %}
            <link rel="stylesheet" href="{{ url_for('static', filename='css/admin.css') }}">
            {% endblock %}

            {% block title %}管理面板 - {% endblock %}

            {% block user_menu %}
            <div class="navbar-nav ms-auto">
                <div class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" id="adminDropdown" role="button" data-toggle="dropdown">
                        <i class="fas fa-user-shield"></i> 管理员
                    </a>
                    <div class="dropdown-menu dropdown-menu-right">
                        <a class="dropdown-item" href="/admin/profile">个人资料</a>
                        <a class="dropdown-item" href="/admin/settings">系统设置</a>
                        <div class="dropdown-divider"></div>
                        <a class="dropdown-item" href="/logout">退出登录</a>
                    </div>
                </div>
            </div>
            {% endblock %}

            {% block content %}
            <div class="row">
                <div class="col-md-3">
                    <!-- 管理员侧边栏 -->
                    {% include 'admin/sidebar.html' %}
                </div>
                <div class="col-md-9">
                    <!-- 主要内容 -->
                    {% block admin_content %}{% endblock %}
                </div>
            </div>
            {% endblock %}

            <!-- templates/admin/dashboard.html -->
            {% extends "base/admin_base.html" %}

            {% block title %}管理仪表板 - {% endblock %}

            {% block admin_content %}
            <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
                <h1 class="h2">管理仪表板</h1>
                <div class="btn-toolbar mb-2 mb-md-0">
                    <div class="btn-group me-2">
                        <button type="button" class="btn btn-sm btn-outline-secondary">导出数据</button>
                        <button type="button" class="btn btn-sm btn-outline-secondary">刷新</button>
                    </div>
                </div>
            </div>

            <!-- 统计卡片 -->
            <div class="row">
                <div class="col-xl-3 col-md-6 mb-4">
                    <div class="card border-left-primary shadow h-100 py-2">
                        <div class="card-body">
                            <div class="row no-gutters align-items-center">
                                <div class="col mr-2">
                                    <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
                                        用户总数
                                    </div>
                                    <div class="h5 mb-0 font-weight-bold text-gray-800">{{ user_count }}</div>
                                </div>
                                <div class="col-auto">
                                    <i class="fas fa-users fa-2x text-gray-300"></i>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="col-xl-3 col-md-6 mb-4">
                    <div class="card border-left-success shadow h-100 py-2">
                        <div class="card-body">
                            <div class="row no-gutters align-items-center">
                                <div class="col mr-2">
                                    <div class="text-xs font-weight-bold text-success text-uppercase mb-1">
                                        活跃会话
                                    </div>
                                    <div class="h5 mb-0 font-weight-bold text-gray-800">{{ active_sessions }}</div>
                                </div>
                                <div class="col-auto">
                                    <i class="fas fa-signal fa-2x text-gray-300"></i>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="col-xl-3 col-md-6 mb-4">
                    <div class="card border-left-info shadow h-100 py-2">
                        <div class="card-body">
                            <div class="row no-gutters align-items-center">
                                <div class="col mr-2">
                                    <div class="text-xs font-weight-bold text-info text-uppercase mb-1">
                                        系统状态
                                    </div>
                                    <div class="h5 mb-0 font-weight-bold text-gray-800">
                                        <span class="badge bg-success">{{ system_status }}</span>
                                    </div>
                                </div>
                                <div class="col-auto">
                                    <i class="fas fa-server fa-2x text-gray-300"></i>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="col-xl-3 col-md-6 mb-4">
                    <div class="card border-left-warning shadow h-100 py-2">
                        <div class="card-body">
                            <div class="row no-gutters align-items-center">
                                <div class="col mr-2">
                                    <div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
                                        待处理任务
                                    </div>
                                    <div class="h5 mb-0 font-weight-bold text-gray-800">{{ pending_tasks }}</div>
                                </div>
                                <div class="col-auto">
                                    <i class="fas fa-tasks fa-2x text-gray-300"></i>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- 最近活动 -->
            <div class="card shadow mb-4">
                <div class="card-header py-3">
                    <h6 class="m-0 font-weight-bold text-primary">最近活动</h6>
                </div>
                <div class="card-body">
                    <div class="table-responsive">
                        <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
                            <thead>
                                <tr>
                                    <th>时间</th>
                                    <th>用户</th>
                                    <th>操作</th>
                                    <th>状态</th>
                                </tr>
                            </thead>
                            <tbody>
                                <tr>
                                    <td>2024-01-15 10:30</td>
                                    <td>Alice</td>
                                    <td>登录系统</td>
                                    <td><span class="badge bg-success">成功</span></td>
                                </tr>
                                <tr>
                                    <td>2024-01-15 10:25</td>
                                    <td>Bob</td>
                                    <td>更新资料</td>
                                    <td><span class="badge bg-success">成功</span></td>
                                </tr>
                                <tr>
                                    <td>2024-01-15 10:20</td>
                                    <td>Charlie</td>
                                    <td>上传文件</td>
                                    <td><span class="badge bg-warning">待处理</span></td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
            {% endblock %}

            <!-- templates/users/base.html -->
            {% extends "base/base_layout.html" %}

            {% block styles %}
            <link rel="stylesheet" href="{{ url_for('static', filename='css/users.css') }}">
            {% endblock %}

            {% block title %}用户中心 - {% endblock %}

            {% block user_menu %}
            <div class="navbar-nav ms-auto">
                <div class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown">
                        <i class="fas fa-user"></i> {{ current_user.name }}
                    </a>
                    <div class="dropdown-menu dropdown-menu-right">
                        <a class="dropdown-item" href="{{ url_for('user.user_profile') }}">个人资料</a>
                        <a class="dropdown-item" href="{{ url_for('user.user_dashboard') }}">我的仪表板</a>
                        <a class="dropdown-item" href="{{ url_for('user.settings') }}">设置</a>
                        <div class="dropdown-divider"></div>
                        <a class="dropdown-item" href="/logout">退出登录</a>
                    </div>
                </div>
            </div>
            {% endblock %}

            {% block content %}
            <div class="container-fluid">
                <div class="row">
                    <!-- 用户侧边栏 -->
                    <div class="col-md-3">
                        {% include 'users/sidebar.html' %}
                    </div>
                    <!-- 主要内容区域 -->
                    <div class="col-md-9">
                        {% block user_content %}{% endblock %}
                    </div>
                </div>
            </div>
            {% endblock %}

02.静态文件管理
    a.Blueprint 静态文件服务
        a.独立静态文件配置
            from flask import Flask, Blueprint, send_from_directory, url_for, current_app
            import os

            app = Flask(__name__)

            # 1. 带独立静态文件的 Blueprint
            user_bp = Blueprint('user', __name__,
                              static_folder='static/users',
                              static_url_path='/static/users')

            # 2. 带自定义静态文件路径的 Blueprint
            admin_bp = Blueprint('admin', __name__,
                                static_folder='static/admin',
                                static_url_path='/static/admin')

            # 3. 静态文件路由管理器
            class StaticFileManager:
                def __init__(self, app=None):
                    self.app = app
                    self.custom_routes = {}

                def init_app(self, app):
                    self.app = app

                def add_custom_static_route(self, blueprint, endpoint, directory):
                    """添加自定义静态文件路由"""
                    if hasattr(blueprint, 'deferred_functions'):
                        # Blueprint 注册后添加路由
                        blueprint.record_once(lambda state: self._add_route(state, endpoint, directory))

                def _add_route(self, state, endpoint, directory):
                    """内部方法:添加静态文件路由"""
                    app = state.app
                    url_prefix = state.url_prefix or ''

                    def serve_file(filename):
                        safe_path = os.path.normpath(filename)
                        if safe_path.startswith('..'):
                            return "Access denied", 403

                        file_path = os.path.join(directory, safe_path)
                        if not os.path.exists(file_path):
                            return "File not found", 404

                        return send_from_directory(directory, safe_path)

                    app.add_url_rule(
                        f'{url_prefix}/{endpoint}/<path:filename>',
                        f'{state.blueprint.name}_static_{endpoint}',
                        serve_file
                    )

            # 创建静态文件管理器
            static_manager = StaticFileManager(app)

            # 用户 Blueprint 路由
            @user_bp.route('/')
            def user_home():
                return f'''
                <html>
                <head>
                    <title>User Module</title>
                    <link rel="stylesheet" href="{url_for('user.static', filename='css/user.css')}">
                </head>
                <body>
                    <h1>User Module</h1>
                    <p>This page uses user-specific static files.</p>
                    <img src="{url_for('user.static', filename='img/avatar-default.png')}" alt="Default Avatar">
                    <script src="{url_for('user.static', filename='js/user.js')}"></script>
                </body>
                </html>
                '''

            @user_bp.route('/static-info')
            def user_static_info():
                """显示用户模块静态文件信息"""
                static_info = {
                    'blueprint': 'user',
                    'static_folder': user_bp.static_folder,
                    'static_url_path': user_bp.static_url_path,
                    'static_files': []
                }

                # 列出静态文件
                if os.path.exists(user_bp.static_folder):
                    for root, dirs, files in os.walk(user_bp.static_folder):
                        for file in files:
                            rel_path = os.path.relpath(os.path.join(root, file), user_bp.static_folder)
                            static_info['static_files'].append({
                                'name': file,
                                'path': rel_path,
                                'url': url_for('user.static', filename=rel_path.replace('\\', '/'))
                            })

                return jsonify(static_info)

            # 管理 Blueprint 路由
            @admin_bp.route('/')
            def admin_home():
                return f'''
                <html>
                <head>
                    <title>Admin Module</title>
                    <link rel="stylesheet" href="{url_for('admin.static', filename='css/admin.css')}">
                </head>
                <body class="admin-theme">
                    <h1>Admin Module</h1>
                    <p>This page uses admin-specific static files.</p>
                    <img src="{url_for('admin.static', filename='img/admin-logo.png')}" alt="Admin Logo">
                    <script src="{url_for('admin.static', filename='js/admin.js')}"></script>
                </body>
                </html>
                '''

            @admin_bp.route('/static-browser')
            def static_browser():
                """静态文件浏览器"""
                return render_template('admin/static_browser.html',
                                     admin_static_url=url_for('admin.static', filename=''),
                                     user_static_url=url_for('user.static', filename=''))

            # 创建资源优化 Blueprint
            assets_bp = Blueprint('assets', __name__, url_prefix='/assets')

            @assets_bp.route('/css/<filename>')
            def optimized_css(filename):
                """优化的 CSS 服务"""
                # CSS 压缩逻辑
                css_file = os.path.join('static/css', filename)
                if os.path.exists(css_file):
                    with open(css_file, 'r', encoding='utf-8') as f:
                        css_content = f.read()

                    # 简单的 CSS 压缩
                    compressed = re.sub(r'/\*.*?\*/', '', css_content)  # 移除注释
                    compressed = re.sub(r'\s+', ' ', compressed)       # 压缩空白
                    compressed = compressed.strip()

                    response = Response(compressed, mimetype='text/css')
                    response.headers['Cache-Control'] = 'public, max-age=31536000'
                    return response
                else:
                    return "CSS file not found", 404

            @assets_bp.route('/js/<filename>')
            def optimized_js(filename):
                """优化的 JavaScript 服务"""
                js_file = os.path.join('static/js', filename)
                if os.path.exists(js_file):
                    with open(js_file, 'r', encoding='utf-8') as f:
                        js_content = f.read()

                    # 简单的 JS 压缩
                    compressed = re.sub(r'//.*', '', js_content)      # 移除单行注释
                    compressed = re.sub(r'/\*.*?\*/', '', compressed) # 移除多行注释
                    compressed = re.sub(r'\s+', ' ', compressed)     # 压缩空白
                    compressed = compressed.strip()

                    response = Response(compressed, mimetype='application/javascript')
                    response.headers['Cache-Control'] = 'public, max-age=31536000'
                    return response
                else:
                    return "JavaScript file not found", 404

            @assets_bp.route('/img/<filename>')
            def optimized_image(filename):
                """优化的图片服务"""
                img_file = os.path.join('static/img', filename)
                if os.path.exists(img_file):
                    # 获取文件信息
                    file_size = os.path.getsize(img_file)
                    file_mtime = os.path.getmtime(img_file)

                    # 生成 ETag
                    etag = f'"{int(file_mtime)}-{file_size}"'

                    # 检查 If-None-Match
                    if request.headers.get('If-None-Match') == etag:
                        response = Response('', status=304)
                        return response

                    # 读取文件
                    with open(img_file, 'rb') as f:
                        img_data = f.read()

                    # 检测文件类型
                    from PIL import Image
                    import io

                    try:
                        with Image.open(io.BytesIO(img_data)) as img:
                            # 根据浏览器支持优化图片
                            accept_header = request.headers.get('Accept', '')

                            if 'image/webp' in accept_header and not filename.endswith('.webp'):
                                # 转换为 WebP
                                output = io.BytesIO()
                                img.save(output, format='WebP', quality=85)
                                img_data = output.getvalue()
                                mimetype = 'image/webp'
                            else:
                                # 使用原始格式
                                ext = filename.lower().split('.')[-1]
                                mimetype = {
                                    'jpg': 'image/jpeg',
                                    'jpeg': 'image/jpeg',
                                    'png': 'image/png',
                                    'gif': 'image/gif'
                                }.get(ext, 'application/octet-stream')

                            response = Response(img_data, mimetype=mimetype)
                            response.headers['ETag'] = etag
                            response.headers['Cache-Control'] = 'public, max-age=31536000'
                            return response

                    except Exception:
                        # 如果图片处理失败,返回原始文件
                        pass

                    # 返回原始文件
                    ext = filename.lower().split('.')[-1]
                    mimetype = {
                        'jpg': 'image/jpeg',
                        'jpeg': 'image/jpeg',
                        'png': 'image/png',
                        'gif': 'image/gif'
                    }.get(ext, 'application/octet-stream')

                    response = Response(img_data, mimetype=mimetype)
                    response.headers['ETag'] = etag
                    response.headers['Cache-Control'] = 'public, max-age=31536000'
                    return response
                else:
                    return "Image file not found", 404

            # 注册 Blueprint
            app.register_blueprint(user_bp, url_prefix='/user')
            app.register_blueprint(admin_bp, url_prefix='/admin')
            app.register_blueprint(assets_bp)
        b.静态文件版本控制和缓存
            from flask import Blueprint, url_for, current_app
            import hashlib
            import os
            import time

            app = Flask(__name__)

            class VersionedStaticFiles:
                """版本化静态文件管理器"""
                def __init__(self, app=None):
                    self.app = app
                    self.version_cache = {}

                def init_app(self, app):
                    app.jinja_env.globals['static_versioned'] = self.versioned_url
                    app.context_processor(self.context_processor)

                def versioned_url(self, endpoint, filename):
                    """生成带版本号的静态文件 URL"""
                    blueprint_name, static_endpoint = endpoint.split('.')

                    # 获取 Blueprint 对象
                    blueprint = current_app.blueprints.get(blueprint_name)
                    if not blueprint:
                        return url_for(endpoint, filename=filename)

                    # 构建文件路径
                    file_path = os.path.join(blueprint.static_folder, filename)

                    # 获取或计算文件版本
                    if file_path not in self.version_cache:
                        self.version_cache[file_path] = self._get_file_version(file_path)

                    version = self.version_cache[file_path]

                    # 分离文件名和扩展名
                    name, ext = os.path.splitext(filename)
                    versioned_filename = f"{name}.{version}{ext}"

                    return url_for(endpoint, filename=versioned_filename)

                def _get_file_version(self, filepath):
                    """获取文件版本(基于内容哈希或修改时间)"""
                    if not os.path.exists(filepath):
                        return 'unknown'

                    try:
                        # 基于文件内容生成哈希
                        with open(filepath, 'rb') as f:
                            content = f.read()
                        return hashlib.md5(content).hexdigest()[:8]
                    except Exception:
                        # 如果无法读取内容,使用修改时间
                        return str(int(os.path.getmtime(filepath)))

                def context_processor(self):
                    """模板上下文处理器"""
                    return {
                        'versioned_static': self.versioned_url,
                        'asset_url': lambda filename: self.versioned_url('main.static', filename)
                    }

            # 初始化版本化静态文件管理器
            versioned_static = VersionedStaticFiles(app)

            # 主应用 Blueprint
            main_bp = Blueprint('main', __name__,
                              static_folder='static',
                              static_url_path='/static')

            @main_bp.app_template_global()
            def asset_url(filename):
                """全局模板函数:生成资源 URL"""
                return versioned_static.versioned_url('main.static', filename)

            @main_bp.route('/versioned-demo')
            def versioned_demo():
                """版本化静态文件演示"""
                return render_template('versioned_demo.html',
                                     css_url=asset_url('css/main.css'),
                                     js_url=asset_url('js/app.js'),
                                     logo_url=asset_url('img/logo.png'))

            # 缓存控制 Blueprint
            cache_bp = Blueprint('cache', __name__, url_prefix='/cache')

            @cache_bp.route('/static-with-cache/<path:filename>')
            def static_with_cache(filename):
                """带缓存控制的静态文件服务"""
                static_path = os.path.join(app.static_folder, filename)

                if not os.path.exists(static_path):
                    return jsonify({'error': 'File not found'}), 404

                # 获取文件信息
                file_stat = os.stat(static_path)
                last_modified = file_stat.st_mtime
                file_size = file_stat.st_size

                # 生成 ETag
                etag = f'"{int(last_modified)}-{file_size}"'

                # 检查请求头
                if_none_match = request.headers.get('If-None-Match')
                if_modified_since = request.headers.get('If-Modified-Since')

                # ETag 匹配检查
                if if_none_match == etag:
                    response = Response('', status=304)
                    return response

                # 修改时间检查
                if if_modified_since:
                    import time
                    if_modified_since_time = time.strptime(if_modified_since, '%a, %d %b %Y %H:%M:%S GMT')
                    if_modified_since_timestamp = time.mktime(if_modified_since_time)
                    if last_modified <= if_modified_since_timestamp:
                        response = Response('', status=304)
                        return response

                # 读取文件
                with open(static_path, 'rb') as f:
                    file_content = f.read()

                # 确定内容类型
                ext = os.path.splitext(filename).lower()
                content_types = {
                    '.css': 'text/css',
                    '.js': 'application/javascript',
                    '.png': 'image/png',
                    '.jpg': 'image/jpeg',
                    '.jpeg': 'image/jpeg',
                    '.gif': 'image/gif',
                    '.svg': 'image/svg+xml'
                }
                content_type = content_types.get(ext, 'application/octet-stream')

                # 创建响应
                response = Response(file_content, content_type=content_type)

                # 设置缓存头
                if ext in ['.css', '.js', '.png', '.jpg', '.jpeg', '.gif']:
                    # 静态资源长期缓存
                    response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
                else:
                    # 其他文件中等缓存
                    response.headers['Cache-Control'] = 'public, max-age=86400'

                response.headers['ETag'] = etag
                response.headers['Last-Modified'] = time.strftime(
                    '%a, %d %b %Y %H:%M:%S GMT',
                    time.gmtime(last_modified)
                )
                response.headers['Vary'] = 'Accept-Encoding'

                return response

            @cache_bp.route('/cache-info')
            def cache_info():
                """缓存信息测试页面"""
                return render_template('cache_info.html',
                    test_images=[
                        cache_static_url('img/test1.jpg'),
                        cache_static_url('img/test2.png'),
                        cache_static_url('img/test3.svg')
                    ],
                    test_styles=[
                        cache_static_url('css/style1.css'),
                        cache_static_url('css/style2.css')
                    ],
                    test_scripts=[
                        cache_static_url('js/script1.js'),
                        cache_static_url('js/script2.js')
                    ]
                )

            def cache_static_url(filename):
                """生成缓存静态文件 URL"""
                return url_for('cache.static_with_cache', filename=filename)

            app.register_blueprint(main_bp)
            app.register_blueprint(cache_bp)

            if __name__ == '__main__':
                # 确保静态文件目录存在
                os.makedirs('static/css', exist_ok=True)
                os.makedirs('static/js', exist_ok=True)
                os.makedirs('static/img', exist_ok=True)
                os.makedirs('static/users/css', exist_ok=True)
                os.makedirs('static/users/js', exist_ok=True)
                os.makedirs('static/admin/css', exist_ok=True)
                os.makedirs('static/admin/js', exist_ok=True)

                app.run(debug=True)

4.4 中间件和钩子

01.请求前处理
    a.Before Request 钩子
        a.单 Blueprint 的请求前处理
            from flask import Blueprint, request, g, jsonify, redirect, url_for
            import time
            import uuid
            import jwt

            # 1. 认证 Blueprint - 请求前验证
            auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

            @auth_bp.before_request
            def require_authentication():
                """认证前的验证"""
                # 排除登录和注册页面
                if request.endpoint in ['auth.login', 'auth.register']:
                    return

                # 检查 Authorization 头
                auth_header = request.headers.get('Authorization')
                if not auth_header or not auth_header.startswith('Bearer '):
                    return jsonify({
                        'error': 'Authentication required',
                        'message': 'Please provide a valid Bearer token'
                    }), 401

                # 提取和验证 token
                token = auth_header[7:]  # 去掉 'Bearer '
                try:
                    # 验证 JWT token (简化示例)
                    payload = jwt.decode(token, 'your-secret-key', algorithms=['HS256'])
                    g.current_user = payload
                    g.user_id = payload['user_id']
                except jwt.ExpiredSignatureError:
                    return jsonify({'error': 'Token has expired'}), 401
                except jwt.InvalidTokenError:
                    return jsonify({'error': 'Invalid token'}), 401

            @auth_bp.before_request
            def log_api_request():
                """记录 API 请求日志"""
                if hasattr(g, 'user_id'):
                    g.request_id = str(uuid.uuid4())
                    g.start_time = time.time()

                    print(f"[{g.request_id}] User {g.user_id} accessing {request.method} {request.path}")

            @auth_bp.route('/protected-data')
            def protected_data():
                """受保护的数据"""
                if not hasattr(g, 'current_user'):
                    return jsonify({'error': 'Not authenticated'}), 401

                return jsonify({
                    'user': g.current_user,
                    'data': 'This is protected data',
                    'request_id': getattr(g, 'request_id', 'unknown')
                })

            @auth_bp.route('/profile')
            def user_profile():
                """用户资料"""
                return jsonify({
                    'user': g.current_user,
                    'profile': {
                        'email': f"user{g.user_id}@example.com",
                        'join_date': '2024-01-01',
                        'status': 'active'
                    }
                })

            # 2. API Blueprint - 速率限制和版本控制
            api_bp = Blueprint('api', __name__, url_prefix='/api/v1')

            @api_bp.before_request
            def api_rate_limiting():
                """API 速率限制"""
                # 模拟简单的速率限制
                client_ip = request.remote_addr
                current_time = time.time()

                if not hasattr(api_bp, 'rate_limiter'):
                    api_bp.rate_limiter = {}

                if client_ip not in api_bp.rate_limiter:
                    api_bp.rate_limiter[client_ip] = []

                # 清理过期的请求记录
                api_bp.rate_limiter[client_ip] = [
                    req_time for req_time in api_bp.rate_limiter[client_ip]
                    if current_time - req_time < 60  # 1分钟内
                ]

                # 检查是否超过限制
                if len(api_bp.rate_limiter[client_ip]) >= 100:  # 每分钟100次请求
                    return jsonify({
                        'error': 'Rate limit exceeded',
                        'message': 'Too many requests. Please try again later.',
                        'limit': 100,
                        'window': 60
                    }), 429

                # 记录当前请求
                api_bp.rate_limiter[client_ip].append(current_time)

            @api_bp.before_request
            def validate_api_version():
                """验证 API 版本"""
                version_header = request.headers.get('API-Version')
                if version_header and version_header != '1.0':
                    return jsonify({
                        'error': 'Unsupported API version',
                        'supported_versions': ['1.0'],
                        'current_version': version_header
                    }), 400

            @api_bp.before_request
            def set_api_context():
                """设置 API 上下文"""
                g.api_request = True
                g.api_version = request.headers.get('API-Version', '1.0')
                g.request_start_time = time.time()

            @api_bp.route('/users')
            def api_users():
                """API 用户列表"""
                users = [
                    {'id': 1, 'name': 'Alice', 'email': '[email protected]'},
                    {'id': 2, 'name': 'Bob', 'email': '[email protected]'}
                ]

                return jsonify({
                    'users': users,
                    'pagination': {
                        'page': 1,
                        'per_page': 10,
                        'total': len(users)
                    },
                    'meta': {
                        'api_version': g.api_version,
                        'request_id': getattr(g, 'request_id', 'none')
                    }
                })

            # 3. 管理员 Blueprint - 权限检查
            admin_bp = Blueprint('admin', __name__, url_prefix='/admin')

            @admin_bp.before_request
            def require_admin_permissions():
                """管理员权限检查"""
                # 检查是否有管理员权限
                auth_header = request.headers.get('Authorization')
                if not auth_header:
                    return jsonify({'error': 'Admin access required'}), 401

                # 模拟权限验证
                if 'admin' not in auth_header:
                    return jsonify({'error': 'Insufficient permissions'}), 403

                # 设置管理员上下文
                g.is_admin = True
                g.admin_id = 1  # 模拟管理员ID

            @admin_bp.before_request
            def admin_audit_log():
                """管理员操作审计日志"""
                if hasattr(g, 'admin_id'):
                    audit_data = {
                        'admin_id': g.admin_id,
                        'action': f"{request.method} {request.path}",
                        'ip': request.remote_addr,
                        'timestamp': time.time(),
                        'user_agent': request.headers.get('User-Agent', '')
                    }

                    # 在实际应用中,这里会写入日志文件或数据库
                    print(f"[AUDIT] {audit_data}")

            @admin_bp.route('/dashboard')
            def admin_dashboard():
                """管理员仪表板"""
                return jsonify({
                    'admin_id': g.admin_id,
                    'stats': {
                        'total_users': 150,
                        'active_sessions': 25,
                        'system_status': 'healthy'
                    }
                })

            @admin_bp.route('/users/<int:user_id>/suspend')
            def suspend_user(user_id):
                """暂停用户"""
                # 模拟用户暂停操作
                return jsonify({
                    'message': f'User {user_id} suspended successfully',
                    'admin_id': g.admin_id,
                    'timestamp': time.time()
                })
        b.多 Blueprint 协调处理
            from flask import Blueprint, g, request, current_app
            import functools

            # 全局请求前处理器
            @app.before_request
            def global_before_request():
                """全局请求前处理"""
                g.global_start_time = time.time()
                g.request_id = request.headers.get('X-Request-ID', str(uuid.uuid4()))

            # 特定 Blueprint 的请求前处理器
            def blueprint_before_request(blueprint_name):
                """Blueprint 请求前处理装饰器"""
                def decorator(f):
                    @functools.wraps(f)
                    def decorated_function(*args, **kwargs):
                        if request.blueprint == blueprint_name:
                            # Blueprint 特定的请求前处理
                            g.blueprint_context = blueprint_name
                            print(f"Before request for {blueprint_name}")
                        return f(*args, **kwargs)
                    return decorated_function
                return decorator

            # 动态 Blueprint 钩子管理器
            class BlueprintHookManager:
                def __init__(self, app=None):
                    self.app = app
                    self.before_request_hooks = {}
                    self.after_request_hooks = {}

                def init_app(self, app):
                    self.app = app

                    # 注册全局钩子
                    app.before_request(self.global_before_request_handler)
                    app.after_request(self.global_after_request_handler)

                def add_before_request_hook(self, blueprint_name, hook_func):
                    """添加 Blueprint 的 before_request 钩子"""
                    if blueprint_name not in self.before_request_hooks:
                        self.before_request_hooks[blueprint_name] = []
                    self.before_request_hooks[blueprint_name].append(hook_func)

                def add_after_request_hook(self, blueprint_name, hook_func):
                    """添加 Blueprint 的 after_request 钩子"""
                    if blueprint_name not in self.after_request_hooks:
                        self.after_request_hooks[blueprint_name] = []
                    self.after_request_hooks[blueprint_name].append(hook_func)

                def global_before_request_handler(self):
                    """全局 before_request 处理器"""
                    if request.blueprint:
                        # 执行特定 Blueprint 的 before_request 钩子
                        hooks = self.before_request_hooks.get(request.blueprint, [])
                        for hook in hooks:
                            result = hook()
                            if result:  # 如果钩子返回响应,立即返回
                                return result

                def global_after_request_handler(self, response):
                    """全局 after_request 处理器"""
                    if request.blueprint:
                        # 执行特定 Blueprint 的 after_request 钩子
                        hooks = self.after_request_hooks.get(request.blueprint, [])
                        for hook in hooks:
                            response = hook(response)
                    return response

            # 创建钩子管理器
            hook_manager = BlueprintHookManager(app)

            # 定义 Blueprint
            products_bp = Blueprint('products', __name__, url_prefix='/products')

            # 添加自定义钩子
            def products_auth_check():
                """产品模块的认证检查"""
                if not request.headers.get('X-API-Key'):
                    return jsonify({'error': 'API key required'}), 401

            def products_logging():
                """产品模块的日志记录"""
                print(f"[Products] {request.method} {request.path} from {request.remote_addr}")

            def products_response_headers(response):
                """产品模块的响应头处理"""
                response.headers['X-API-Version'] = '1.0'
                response.headers['X-Module'] = 'products'
                return response

            # 注册钩子
            hook_manager.add_before_request_hook('products', products_auth_check)
            hook_manager.add_before_request_hook('products', products_logging)
            hook_manager.add_after_request_hook('products', products_response_headers)

            # 产品路由
            @products_bp.route('/')
            def product_list():
                return jsonify({
                    'products': [
                        {'id': 1, 'name': 'Laptop', 'price': 999.99},
                        {'id': 2, 'name': 'Mouse', 'price': 29.99}
                    ]
                })

            @products_bp.route('/<int:product_id>')
            def product_detail(product_id):
                return jsonify({
                    'id': product_id,
                    'name': f'Product {product_id}',
                    'price': 99.99
                })

            # 订单 Blueprint
            orders_bp = Blueprint('orders', __name__, url_prefix='/orders')

            def orders_rate_limit():
                """订单模块的速率限制"""
                # 模拟速率限制检查
                print(f"[Orders] Rate limit check for {request.remote_addr}")

            def orders_cors_headers(response):
                """订单模块的 CORS 头"""
                response.headers['Access-Control-Allow-Origin'] = '*'
                response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
                response.headers['Access-Control-Allow-Headers'] = 'Content-Type, X-API-Key'
                return response

            hook_manager.add_before_request_hook('orders', orders_rate_limit)
            hook_manager.add_after_request_hook('orders', orders_cors_headers)

            @orders_bp.route('/')
            def order_list():
                return jsonify({
                    'orders': [
                        {'id': 1, 'product_id': 1, 'quantity': 1},
                        {'id': 2, 'product_id': 2, 'quantity': 2}
                    ]
                })

            # 注册 Blueprint
            app.register_blueprint(products_bp)
            app.register_blueprint(orders_bp)

02.响应后处理
    a.After Request 钩子
        a.响应修改和增强
            from flask import Blueprint, request, g, jsonify, Response
            import time
            import json
            from datetime import datetime

            app = Flask(__name__)

            # 1. API Blueprint - 响应格式化
            api_v1_bp = Blueprint('api_v1', __name__, url_prefix='/api/v1')

            @api_v1_bp.after_request
            def format_api_response(response):
                """格式化 API 响应"""
                # 只处理 JSON 响应
                if response.content_type.startswith('application/json'):
                    try:
                        data = json.loads(response.get_data(as_text=True))

                        # 包装响应数据
                        wrapped_data = {
                            'success': response.status_code < 400,
                            'status_code': response.status_code,
                            'timestamp': datetime.utcnow().isoformat(),
                            'request_id': getattr(g, 'request_id', 'unknown'),
                            'data': data if isinstance(data, (dict, list)) else {}
                        }

                        # 添加错误信息(如果有)
                        if response.status_code >= 400:
                            wrapped_data['error'] = {
                                'code': response.status_code,
                                'message': data.get('error', 'Unknown error')
                            }

                        response.set_data(json.dumps(wrapped_data, ensure_ascii=False))
                        response.headers['Content-Type'] = 'application/json; charset=utf-8'

                    except (json.JSONDecodeError, UnicodeDecodeError):
                        # 如果解析失败,保持原响应
                        pass

                # 添加性能头
                if hasattr(g, 'start_time'):
                    processing_time = time.time() - g.start_time
                    response.headers['X-Processing-Time'] = f"{processing_time:.3f}s"

                return response

            @api_v1_bp.before_request
            def set_request_context():
                """设置请求上下文"""
                g.start_time = time.time()
                g.request_id = request.headers.get('X-Request-ID', f"req_{int(time.time())}")

            @api_v1_bp.route('/users')
            def get_users():
                """获取用户列表"""
                return jsonify([
                    {'id': 1, 'name': 'Alice', 'email': '[email protected]'},
                    {'id': 2, 'name': 'Bob', 'email': '[email protected]'}
                ])

            @api_v1_bp.route('/error-demo')
            def error_demo():
                """错误演示"""
                return jsonify({'error': 'This is a demo error'}), 500

            # 2. 日志记录 Blueprint - 响应日志
            logging_bp = Blueprint('logging', __name__)

            @logging_bp.after_request
            def log_response(response):
                """记录响应日志"""
                if hasattr(g, 'start_time'):
                    duration = time.time() - g.start_time
                    log_data = {
                        'timestamp': datetime.utcnow().isoformat(),
                        'method': request.method,
                        'path': request.path,
                        'status_code': response.status_code,
                        'duration_ms': round(duration * 1000, 2),
                        'user_agent': request.headers.get('User-Agent', ''),
                        'ip': request.remote_addr
                    }

                    # 在实际应用中,这里会写入日志系统
                    print(f"[RESPONSE_LOG] {json.dumps(log_data)}")

                return response

            @logging_bp.before_request
            def log_request():
                """记录请求日志"""
                g.start_time = time.time()
                log_data = {
                    'timestamp': datetime.utcnow().isoformat(),
                    'method': request.method,
                    'path': request.path,
                    'headers': dict(request.headers),
                    'args': dict(request.args)
                }

                print(f"[REQUEST_LOG] {json.dumps(log_data)}")

            @logging_bp.route('/test-endpoint')
            def test_endpoint():
                return jsonify({'message': 'Logging test endpoint'})

            # 3. 缓存 Blueprint - 响应缓存
            cache_bp = Blueprint('cache', __name__, url_prefix='/cache')

            @cache_bp.after_request
            def add_cache_headers(response):
                """添加缓存头"""
                # 根据内容类型和状态码设置缓存策略
                if response.status_code == 200:
                    if request.path.startswith('/cache/static/'):
                        # 静态资源长期缓存
                        response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
                        response.headers['ETag'] = f'"{hash(response.get_data())}"'

                    elif request.path.startswith('/cache/data/'):
                        # 数据短期缓存
                        response.headers['Cache-Control'] = 'public, max-age=300, must-revalidate'
                        response.headers['Vary'] = 'Accept-Encoding'

                    else:
                        # 默认缓存
                        response.headers['Cache-Control'] = 'public, max-age=60'

                else:
                    # 错误响应不缓存
                    response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
                    response.headers['Pragma'] = 'no-cache'
                    response.headers['Expires'] = '0'

                return response

            @cache_bp.route('/static-data')
            def static_data():
                """静态数据 - 适合长期缓存"""
                return jsonify({
                    'version': '1.0.0',
                    'features': ['user-management', 'product-catalog', 'order-processing'],
                    'last_updated': '2024-01-15T10:00:00Z'
                })

            @cache_bp.route('/dynamic-data')
            def dynamic_data():
                """动态数据 - 短期缓存"""
                return jsonify({
                    'timestamp': datetime.utcnow().isoformat(),
                    'active_users': 25,
                    'current_load': 0.65,
                    'server_time': time.time()
                })

            @cache_bp.route('/error-example')
            def error_example():
                """错误响应 - 不缓存"""
                return jsonify({'error': 'Example error'}), 500

            # 4. 安全 Blueprint - 安全头处理
            security_bp = Blueprint('security', __name__, url_prefix='/secure')

            @security_bp.after_request
            def add_security_headers(response):
                """添加安全头"""
                # 内容安全策略
                response.headers['Content-Security-Policy'] = (
                    "default-src 'self'; "
                    "script-src 'self' 'unsafe-inline' https://cdn.example.com; "
                    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
                    "font-src 'self' https://fonts.gstatic.com; "
                    "img-src 'self' data: https:; "
                    "connect-src 'self'; "
                    "frame-ancestors 'none'; "
                    "base-uri 'self'; "
                    "form-action 'self'"
                )

                # X-Frame-Options
                response.headers['X-Frame-Options'] = 'DENY'

                # X-Content-Type-Options
                response.headers['X-Content-Type-Options'] = 'nosniff'

                # X-XSS-Protection
                response.headers['X-XSS-Protection'] = '1; mode=block'

                # Referrer-Policy
                response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'

                # Permissions-Policy
                response.headers['Permissions-Policy'] = (
                    'geolocation=(), '
                    'microphone=(), '
                    'camera=(), '
                    'payment=(), '
                    'usb=()'
                )

                # Strict-Transport-Security (仅 HTTPS)
                if request.is_secure:
                    response.headers['Strict-Transport-Security'] = (
                        'max-age=31536000; includeSubDomains; preload'
                    )

                return response

            @security_bp.route('/protected-content')
            def protected_content():
                """受保护的内容"""
                return jsonify({
                    'message': 'This content is served with security headers',
                    'security_features': [
                        'Content Security Policy',
                        'X-Frame-Options',
                        'X-Content-Type-Options',
                        'X-XSS-Protection',
                        'Referrer Policy',
                        'Permissions Policy'
                    ]
                })

            @security_bp.route('/form-data', methods=['GET', 'POST'])
            def form_data():
                """表单数据处理"""
                if request.method == 'POST':
                    return jsonify({
                        'message': 'Form submitted securely',
                        'received_data': request.get_json()
                    })
                else:
                    return jsonify({
                        'form_url': '/secure/form-data',
                        'method': 'POST',
                        'content_type': 'application/json',
                        'csrf_protection': 'enabled'
                    })

            # 5. 监控 Blueprint - 性能监控
            monitoring_bp = Blueprint('monitoring', __name__, url_prefix='/monitor')

            @monitoring_bp.after_request
            def monitor_performance(response):
                """性能监控"""
                if hasattr(g, 'start_time'):
                    duration = time.time() - g.start_time

                    # 记录慢请求
                    if duration > 1.0:
                        print(f"[SLOW_REQUEST] {request.method} {request.path} took {duration:.3f}s")

                    # 性能分类
                    if duration < 0.1:
                        perf_category = 'fast'
                    elif duration < 0.5:
                        perf_category = 'normal'
                    elif duration < 1.0:
                        perf_category = 'slow'
                    else:
                        perf_category = 'very_slow'

                    response.headers['X-Performance-Category'] = perf_category

                return response

            @monitoring_bp.before_request
            def start_monitoring():
                """开始监控"""
                g.start_time = time.time()

            @monitoring_bp.route('/health-check')
            def health_check():
                """健康检查端点"""
                # 模拟各种健康指标
                health_data = {
                    'status': 'healthy',
                    'timestamp': datetime.utcnow().isoformat(),
                    'services': {
                        'database': 'connected',
                        'cache': 'connected',
                        'external_api': 'connected'
                    },
                    'metrics': {
                        'uptime': 86400,  # 秒
                        'memory_usage': 0.65,  # 65%
                        'cpu_usage': 0.23,  # 23%
                        'disk_usage': 0.45   # 45%
                    }
                }

                return jsonify(health_data)

            @monitoring_bp.route('/metrics')
            def get_metrics():
                """获取性能指标"""
                # 模拟性能指标数据
                return jsonify({
                    'request_stats': {
                        'total_requests': 1000,
                        'requests_per_second': 5.2,
                        'average_response_time': 0.125,
                        'error_rate': 0.02
                    },
                    'system_stats': {
                        'memory_usage': 0.65,
                        'cpu_usage': 0.23,
                        'active_connections': 25,
                        'disk_io': {
                            'reads_per_sec': 120,
                            'writes_per_sec': 45
                        }
                    },
                    'application_stats': {
                        'active_users': 150,
                        'database_connections': 8,
                        'cache_hit_rate': 0.85
                    }
                })

            # 注册所有 Blueprint
            app.register_blueprint(api_v1_bp)
            app.register_blueprint(logging_bp)
            app.register_blueprint(cache_bp)
            app.register_blueprint(security_bp)
            app.register_blueprint(monitoring_bp)

            # 主页路由 - 展示各个 Blueprint 的功能
            @app.route('/')
            def index():
                return jsonify({
                    'message': 'Flask Blueprint Middleware Demo',
                    'endpoints': {
                        'api_v1': '/api/v1/users',
                        'logging': '/logging/test-endpoint',
                        'cache': '/cache/static-data',
                        'security': '/secure/protected-content',
                        'monitoring': '/monitor/health-check'
                    }
                })

            if __name__ == '__main__':
                app.run(debug=True)

5 数据库集成

5.1 SQLAlchemy 集成

01.SQLAlchemy 基础配置
    a.Flask-SQLAlchemy 初始化
        a.基础 SQLAlchemy 配置
            from flask import Flask, jsonify, request
            from flask_sqlalchemy import SQLAlchemy
            from sqlalchemy import create_engine, text
            from sqlalchemy.orm import sessionmaker, scoped_session
            from sqlalchemy.ext.declarative import declarative_base
            import os

            # 创建 Flask 应用
            app = Flask(__name__)

            # 数据库配置 - 支持多种数据库类型
            def configure_database(app, db_type='sqlite'):
                """配置数据库连接"""
                db_configs = {
                    'sqlite': {
                        'SQLALCHEMY_DATABASE_URI': 'sqlite:///flask_demo.db',
                        'SQLALCHEMY_BINDS': {
                            'logs': 'sqlite:///logs.db',
                            'cache': 'sqlite:///cache.db'
                        }
                    },
                    'postgresql': {
                        'SQLALCHEMY_DATABASE_URI': 'postgresql://user:password@localhost/flask_demo',
                        'SQLALCHEMY_BINDS': {
                            'logs': 'postgresql://user:password@localhost/logs',
                            'cache': 'postgresql://user:password@localhost/cache'
                        }
                    },
                    'mysql': {
                        'SQLALCHEMY_DATABASE_URI': 'mysql+pymysql://user:password@localhost/flask_demo',
                        'SQLALCHEMY_BINDS': {
                            'logs': 'mysql+pymysql://user:password@localhost/logs',
                            'cache': 'mysql+pymysql://user:password@localhost/cache'
                        }
                    }
                }

                config = db_configs.get(db_type, db_configs['sqlite'])
                app.config.update(config)

                # SQLAlchemy 特定配置
                app.config.update({
                    'SQLALCHEMY_TRACK_MODIFICATIONS': False,  # 禁用事件系统以提高性能
                    'SQLALCHEMY_ECHO': False,                  # 生产环境设为 False
                    'SQLALCHEMY_ENGINE_OPTIONS': {
                        'pool_size': 10,                      # 连接池大小
                        'pool_recycle': 120,                   # 连接回收时间(秒)
                        'pool_pre_ping': True,                 # 连接预检
                        'max_overflow': 20                     # 最大溢出连接数
                    }
                })

            # 初始化数据库配置
            configure_database(app, 'sqlite')

            # 创建 SQLAlchemy 实例
            db = SQLAlchemy(app)

            # 创建数据库名称管理器
            class DatabaseManager:
                def __init__(self, db_instance):
                    self.db = db_instance
                    self.engines = {}
                    self.sessions = {}

                def get_engine(self, bind_key=None):
                    """获取数据库引擎"""
                    key = bind_key or 'default'
                    if key not in self.engines:
                        if bind_key:
                            self.engines[key] = create_engine(
                                self.db.get_app().config['SQLALCHEMY_BINDS'][bind_key]
                            )
                        else:
                            self.engines[key] = self.db.engine
                    return self.engines[key]

                def get_session(self, bind_key=None):
                    """获取数据库会话"""
                    key = bind_key or 'default'
                    if key not in self.sessions:
                        engine = self.get_engine(bind_key)
                        Session = scoped_session(sessionmaker(bind=engine))
                        self.sessions[key] = Session
                    return self.sessions[key]

                def close_sessions(self):
                    """关闭所有会话"""
                    for session in self.sessions.values():
                        session.remove()
                    self.sessions.clear()

            # 创建数据库管理器实例
            db_manager = DatabaseManager(db)

            # 数据库连接测试
            @app.route('/db/test-connection')
            def test_database_connection():
                """测试数据库连接"""
                try:
                    # 测试主数据库连接
                    with db.engine.connect() as conn:
                        result = conn.execute(text('SELECT 1'))
                        main_db_status = 'connected'
                    except Exception as e:
                        main_db_status = f'error: {str(e)}'

                    # 测试绑定数据库连接
                    bind_status = {}
                    for bind_key in app.config.get('SQLALCHEMY_BINDS', {}):
                        try:
                            bind_engine = db_manager.get_engine(bind_key)
                            with bind_engine.connect() as conn:
                                conn.execute(text('SELECT 1'))
                            bind_status[bind_key] = 'connected'
                        except Exception as e:
                            bind_status[bind_key] = f'error: {str(e)}'

                    return jsonify({
                        'main_database': main_db_status,
                        'bind_databases': bind_status,
                        'connection_pool_size': db.engine.pool.size(),
                        'connection_pool_checked_in': db.engine.pool.checkedin(),
                        'connection_pool_checked_out': db.engine.pool.checkedout()
                    })

            @app.route('/db/info')
            def get_database_info():
                """获取数据库信息"""
                info = {
                    'database_uri': app.config['SQLALCHEMY_DATABASE_URI'],
                    'bind_databases': app.config.get('SQLALCHEMY_BINDS', {}),
                    'engine_info': {
                        'driver': str(db.engine.driver),
                        'name': str(db.engine.name),
                        'dialect': str(db.engine.dialect)
                    },
                    'pool_status': {
                        'size': db.engine.pool.size(),
                        'checked_in': db.engine.pool.checkedin(),
                        'checked_out': db.engine.pool.checkedout(),
                        'invalid': db.engine.pool.invalid()
                    }
                }
                return jsonify(info)

            # 数据库健康检查
            @app.route('/db/health')
            def database_health_check():
                """数据库健康检查"""
                health_status = {
                    'database': 'unknown',
                    'timestamp': '2024-01-15T10:00:00Z',
                    'checks': {}
                }

                # 主数据库健康检查
                try:
                    with db.engine.connect() as conn:
                        result = conn.execute(text('SELECT 1 as health_check'))
                        health_check_result = result.fetchone()
                        if health_check_result and health_check_result[0] == 1:
                            health_status['database'] = 'healthy'
                            health_status['checks']['main_db'] = 'pass'
                        else:
                            health_status['database'] = 'unhealthy'
                            health_status['checks']['main_db'] = 'fail'
                except Exception as e:
                    health_status['database'] = 'error'
                    health_status['checks']['main_db'] = f'error: {str(e)}'

                # 绑定数据库健康检查
                for bind_key in app.config.get('SQLALCHEMY_BINDS', {}):
                    try:
                        bind_engine = db_manager.get_engine(bind_key)
                        with bind_engine.connect() as conn:
                            conn.execute(text('SELECT 1'))
                        health_status['checks'][f'{bind_key}_db'] = 'pass'
                    except Exception as e:
                        health_status['checks'][f'{bind_key}_db'] = f'error: {str(e)}'

                return jsonify(health_status)
        b.多数据库配置和绑定
            from flask import Flask
            from flask_sqlalchemy import SQLAlchemy
            from sqlalchemy import create_engine, MetaData, Table
            import os

            app = Flask(__name__)

            # 高级多数据库配置
            def setup_multi_database(app):
                """设置多数据库配置"""
                # 主数据库配置
                app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:password@localhost:5432/flask_main'

                # 绑定数据库配置
                app.config['SQLALCHEMY_BINDS'] = {
                    # 用户数据库
                    'users': 'postgresql://user:password@localhost:5432/flask_users',

                    # 日志数据库(可能使用不同的数据库类型)
                    'logs': 'mysql+pymysql://user:password@localhost:3306/flask_logs',

                    # 缓存数据库
                    'cache': 'sqlite:///cache.db',

                    # 分析数据库
                    'analytics': 'postgresql://user:password@localhost:5432/flask_analytics',

                    # 文件存储数据库
                    'files': 'postgresql://user:password@localhost:5432/flask_files'
                }

                # 优化配置
                app.config.update({
                    'SQLALCHEMY_TRACK_MODIFICATIONS': False,
                    'SQLALCHEMY_POOL_SIZE': 20,
                    'SQLALCHEMY_POOL_TIMEOUT': 30,
                    'SQLALCHEMY_POOL_RECYCLE': 3600,
                    'SQLALCHEMY_MAX_OVERFLOW': 30,
                    'SQLALCHEMY_POOL_PRE_PING': True
                })

            setup_multi_database(app)

            # 创建 SQLAlchemy 实例
            db = SQLAlchemy(app)

            # 混合模型基类
            class BaseModel(db.Model):
                """基础模型类"""
                __abstract__ = True

                id = db.Column(db.Integer, primary_key=True)
                created_at = db.Column(db.DateTime, server_default=db.func.now())
                updated_at = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now())

                def save(self):
                    """保存模型"""
                    try:
                        db.session.add(self)
                        db.session.commit()
                    except Exception as e:
                        db.session.rollback()
                        raise e

                def delete(self):
                    """删除模型"""
                    try:
                        db.session.delete(self)
                        db.session.commit()
                    except Exception as e:
                        db.session.rollback()
                        raise e

                def to_dict(self):
                    """转换为字典"""
                    result = {}
                    for column in self.__table__.columns:
                        value = getattr(self, column.name)
                        if isinstance(value, datetime.datetime):
                            value = value.isoformat()
                        result[column.name] = value
                    return result

            # 主数据库模型
            class User(BaseModel):
                __tablename__ = 'users'
                __bind_key__ = 'users'

                username = db.Column(db.String(80), unique=True, nullable=False, index=True)
                email = db.Column(db.String(120), unique=True, nullable=False, index=True)
                password_hash = db.Column(db.String(255), nullable=False)
                is_active = db.Column(db.Boolean, default=True, nullable=False)
                last_login = db.Column(db.DateTime)

                # 关系定义
                posts = db.relationship('Post', backref='author', lazy='dynamic', cascade='all, delete-orphan')
                comments = db.relationship('Comment', backref='author', lazy='dynamic')

                def __repr__(self):
                    return f'<User {self.username}>'

            class Post(BaseModel):
                __tablename__ = 'posts'

                title = db.Column(db.String(200), nullable=False)
                content = db.Column(db.Text, nullable=False)
                author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
                is_published = db.Column(db.Boolean, default=False)
                view_count = db.Column(db.Integer, default=0)

                # 关系定义
                comments = db.relationship('Comment', backref='post', lazy='dynamic', cascade='all, delete-orphan')
                tags = db.relationship('Tag', secondary='post_tags', backref='posts')

                def __repr__(self):
                    return f'<Post {self.title}>'

            class Comment(BaseModel):
                __tablename__ = 'comments'

                content = db.Column(db.Text, nullable=False)
                author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
                post_id = db.Column(db.Integer, db.ForeignKey('posts.id'), nullable=False)
                is_approved = db.Column(db.Boolean, default=False)

                def __repr__(self):
                    return f'<Comment {self.id}>'

            # 日志数据库模型
            class SystemLog(db.Model):
                __tablename__ = 'system_logs'
                __bind_key__ = 'logs'

                id = db.Column(db.Integer, primary_key=True)
                level = db.Column(db.String(20), nullable=False, index=True)
                message = db.Column(db.Text, nullable=False)
                module = db.Column(db.String(50), nullable=False, index=True)
                user_id = db.Column(db.Integer, nullable=True)
                ip_address = db.Column(db.String(45), nullable=True)
                timestamp = db.Column(db.DateTime, server_default=db.func.now(), index=True)
                extra_data = db.Column(db.JSON)

                def __repr__(self):
                    return f'<SystemLog {self.level}: {self.message[:50]}>'

            class UserActivityLog(db.Model):
                __tablename__ = 'user_activity_logs'
                __bind_key__ = 'logs'

                id = db.Column(db.Integer, primary_key=True)
                user_id = db.Column(db.Integer, nullable=False, index=True)
                action = db.Column(db.String(100), nullable=False, index=True)
                resource_type = db.Column(db.String(50), nullable=False)
                resource_id = db.Column(db.Integer, nullable=True)
                ip_address = db.Column(db.String(45), nullable=False)
                user_agent = db.Column(db.Text, nullable=True)
                timestamp = db.Column(db.DateTime, server_default=db.func.now(), index=True)

                def __repr__(self):
                    return f'<UserActivityLog {self.action} by User {self.user_id}>'

            # 分析数据库模型
            class PageView(db.Model):
                __tablename__ = 'page_views'
                __bind_key__ = 'analytics'

                id = db.Column(db.Integer, primary_key=True)
                path = db.Column(db.String(500), nullable=False, index=True)
                user_id = db.Column(db.Integer, nullable=True, index=True)
                ip_address = db.Column(db.String(45), nullable=False, index=True)
                referrer = db.Column(db.String(500), nullable=True)
                user_agent = db.Column(db.Text, nullable=True)
                timestamp = db.Column(db.DateTime, server_default=db.func.now(), index=True)
                session_id = db.Column(db.String(255), nullable=True, index=True)
                duration_seconds = db.Column(db.Integer, nullable=True)

                def __repr__(self):
                    return f'<PageView {self.path}>'

            # 文件存储数据库模型
            class UploadedFile(db.Model):
                __tablename__ = 'uploaded_files'
                __bind_key__ = 'files'

                id = db.Column(db.Integer, primary_key=True)
                original_filename = db.Column(db.String(255), nullable=False)
                stored_filename = db.Column(db.String(255), nullable=False, unique=True)
                file_path = db.Column(db.String(500), nullable=False)
                file_size = db.Column(db.Integer, nullable=False)
                mime_type = db.Column(db.String(100), nullable=False)
                checksum = db.Column(db.String(64), nullable=False, index=True)
                uploader_id = db.Column(db.Integer, nullable=False, index=True)
                is_public = db.Column(db.Boolean, default=False)
                download_count = db.Column(db.Integer, default=0)

                def __repr__(self):
                    return f'<UploadedFile {self.original_filename}>'

            # 数据库操作工具类
            class DatabaseOperations:
                """数据库操作工具类"""

                @staticmethod
                def create_tables(app):
                    """创建所有数据库表"""
                    with app.app_context():
                        try:
                            # 创建主数据库表
                            db.create_all()
                            print("Main database tables created successfully")

                            # 创建绑定数据库表
                            for bind_key in app.config.get('SQLALCHEMY_BINDS', {}):
                                db.create_all(bind=bind_key)
                                print(f"Bind database '{bind_key}' tables created successfully")

                            return True
                        except Exception as e:
                            print(f"Error creating tables: {e}")
                            return False

                @staticmethod
                def drop_tables(app):
                    """删除所有数据库表"""
                    with app.app_context():
                        try:
                            # 删除绑定数据库表
                            for bind_key in app.config.get('SQLALCHEMY_BINDS', {}):
                                db.drop_all(bind=bind_key)
                                print(f"Bind database '{bind_key}' tables dropped successfully")

                            # 删除主数据库表
                            db.drop_all()
                            print("Main database tables dropped successfully")

                            return True
                        except Exception as e:
                            print(f"Error dropping tables: {e}")
                            return False

                @staticmethod
                def backup_database(bind_key=None):
                    """备份数据库(简化示例)"""
                    # 在实际应用中,这里会使用适当的备份工具
                    backup_info = {
                        'bind_key': bind_key or 'main',
                        'timestamp': '2024-01-15T10:00:00Z',
                        'status': 'completed',
                        'backup_file': f'backup_{bind_key or "main"}_20240115.db'
                    }
                    return backup_info

                @staticmethod
                def get_database_stats(bind_key=None):
                    """获取数据库统计信息"""
                    try:
                        if bind_key:
                            engine = db.get_engine(bind=bind_key)
                        else:
                            engine = db.engine

                        with engine.connect() as conn:
                            # 获取表统计
                            if bind_key == 'main' or bind_key is None:
                                result = conn.execute(text("""
                                    SELECT
                                        table_name,
                                        (SELECT COUNT(*) FROM information_schema.columns
                                         WHERE table_name = t.table_name) as column_count
                                    FROM information_schema.tables t
                                    WHERE table_schema = 'public'
                                """))
                                tables_info = [dict(row) for row in result]
                            else:
                                tables_info = [{'table_name': 'data', 'column_count': 5}]

                        return {
                            'bind_key': bind_key or 'main',
                            'tables': tables_info,
                            'connection_pool': {
                                'size': engine.pool.size(),
                                'checked_in': engine.pool.checkedin(),
                                'checked_out': engine.pool.checkedout()
                            }
                        }
                    except Exception as e:
                        return {'error': str(e)}

            # 创建数据库操作实例
            db_ops = DatabaseOperations()

            @app.route('/db/setup')
            def setup_database():
                """设置数据库"""
                success = db_ops.create_tables(app)
                return jsonify({
                    'message': 'Database setup completed' if success else 'Database setup failed',
                    'bind_databases': list(app.config.get('SQLALCHEMY_BINDS', {}).keys())
                })

            @app.route('/db/stats')
            def get_database_stats():
                """获取所有数据库统计信息"""
                stats = {
                    'main': db_ops.get_database_stats()
                }

                for bind_key in app.config.get('SQLALCHEMY_BINDS', {}):
                    stats[bind_key] = db_ops.get_database_stats(bind_key)

                return jsonify(stats)

            if __name__ == '__main__':
                with app.app_context():
                    db.create_all()
                app.run(debug=True)

02.模型定义和关系
    a.基础模型类设计
        a.用户和内容模型
            from flask import Flask
            from flask_sqlalchemy import SQLAlchemy
            from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, Table
            from sqlalchemy.orm import relationship, backref
            from sqlalchemy.sql import func
            from datetime import datetime
            import hashlib
            secrets

            app = Flask(__name__)
            app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog_demo.db'
            app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
            db = SQLAlchemy(app)

            # 用户角色关联表(多对多)
            user_roles = Table(
                'user_roles',
                db.Model.metadata,
                Column('user_id', Integer, ForeignKey('users.id'), primary_key=True),
                Column('role_id', Integer, ForeignKey('roles.id'), primary_key=True)
            )

            # 文章标签关联表(多对多)
            post_tags = Table(
                'post_tags',
                db.Model.metadata,
                Column('post_id', Integer, ForeignKey('posts.id'), primary_key=True),
                Column('tag_id', Integer, ForeignKey('tags.id'), primary_key=True)
            )

            # 用户收藏文章关联表(多对多)
            user_favorites = Table(
                'user_favorites',
                db.Model.metadata,
                Column('user_id', Integer, ForeignKey('users.id'), primary_key=True),
                Column('post_id', Integer, ForeignKey('posts.id'), primary_key=True),
                Column('created_at', DateTime, default=datetime.utcnow)
            )

            # 基础模型类
            class BaseModel(db.Model):
                """基础模型类,包含通用字段和方法"""
                __abstract__ = True

                id = Column(Integer, primary_key=True)
                created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
                updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
                is_active = Column(Boolean, default=True, nullable=False)

                def save(self):
                    """保存模型到数据库"""
                    try:
                        db.session.add(self)
                        db.session.commit()
                        return self
                    except Exception as e:
                        db.session.rollback()
                        raise e

                def delete(self):
                    """从数据库删除模型"""
                    try:
                        db.session.delete(self)
                        db.session.commit()
                    except Exception as e:
                        db.session.rollback()
                        raise e

                def to_dict(self, include_relationships=False):
                    """将模型转换为字典"""
                    result = {}
                    for column in self.__table__.columns:
                        value = getattr(self, column.name)
                        if isinstance(value, datetime):
                            value = value.isoformat()
                        result[column.name] = value

                    if include_relationships:
                        for relationship in self.__mapper__.relationships:
                            if relationship.uselist:  # 一对多或多对多关系
                                result[relationship.key] = [item.to_dict() for item in getattr(self, relationship.key)]
                            else:  # 多对一或一对一关系
                                related_obj = getattr(self, relationship.key)
                                result[relationship.key] = related_obj.to_dict() if related_obj else None

                    return result

                @classmethod
                def create(cls, **kwargs):
                    """创建新实例"""
                    instance = cls(**kwargs)
                    return instance.save()

                @classmethod
                def get_by_id(cls, id):
                    """根据ID获取实例"""
                    return cls.query.filter_by(id=id, is_active=True).first()

                @classmethod
                def get_all(cls, limit=None, offset=None):
                    """获取所有活跃实例"""
                    query = cls.query.filter_by(is_active=True)
                    if offset:
                        query = query.offset(offset)
                    if limit:
                        query = query.limit(limit)
                    return query.all()

                def soft_delete(self):
                    """软删除(标记为不活跃)"""
                    self.is_active = False
                    self.save()

            # 角色模型
            class Role(BaseModel):
                __tablename__ = 'roles'

                name = Column(String(50), unique=True, nullable=False, index=True)
                description = Column(String(200))
                permissions = Column(Text)  # JSON格式的权限列表

                # 关系
                users = relationship('User', secondary=user_roles, back_populates='roles')

                def __repr__(self):
                    return f'<Role {self.name}>'

                def has_permission(self, permission):
                    """检查是否具有特定权限"""
                    if not self.permissions:
                        return False
                    try:
                        import json
                        permissions = json.loads(self.permissions)
                        return permission in permissions
                    except:
                        return False

            # 用户模型
            class User(BaseModel):
                __tablename__ = 'users'

                # 基本信息
                username = Column(String(80), unique=True, nullable=False, index=True)
                email = Column(String(120), unique=True, nullable=False, index=True)
                password_hash = Column(String(255), nullable=False)

                # 个人信息
                first_name = Column(String(50))
                last_name = Column(String(50))
                bio = Column(Text)
                avatar_url = Column(String(500))

                # 状态信息
                is_verified = Column(Boolean, default=False)
                last_login = Column(DateTime)
                login_count = Column(Integer, default=0)

                # 关系定义
                roles = relationship('Role', secondary=user_roles, back_populates='users')
                posts = relationship('Post', back_populates='author', lazy='dynamic', cascade='all, delete-orphan')
                comments = relationship('Comment', back_populates='author', lazy='dynamic', cascade='all, delete-orphan')
                favorites = relationship('Post', secondary=user_favorites, backref='favorited_by')

                def __repr__(self):
                    return f'<User {self.username}>'

                @property
                def full_name(self):
                    """获取全名"""
                    if self.first_name and self.last_name:
                        return f"{self.first_name} {self.last_name}"
                    return self.username

                @property
                def is_admin(self):
                    """检查是否为管理员"""
                    admin_role = Role.query.filter_by(name='admin').first()
                    if admin_role:
                        return admin_role in self.roles
                    return False

                def has_role(self, role_name):
                    """检查是否具有特定角色"""
                    return any(role.name == role_name for role in self.roles)

                def has_permission(self, permission):
                    """检查是否具有特定权限"""
                    return any(role.has_permission(permission) for role in self.roles)

                def set_password(self, password):
                    """设置密码哈希"""
                    from werkzeug.security import generate_password_hash
                    self.password_hash = generate_password_hash(password)

                def check_password(self, password):
                    """验证密码"""
                    from werkzeug.security import check_password_hash
                    return check_password_hash(self.password_hash, password)

                def update_last_login(self):
                    """更新最后登录时间"""
                    self.last_login = datetime.utcnow()
                    self.login_count += 1
                    self.save()

                def get_posts_count(self):
                    """获取文章数量"""
                    return self.posts.filter_by(is_active=True).count()

                def get_favorites_count(self):
                    """获取收藏数量"""
                    return len(self.favorites)

            # 分类模型
            class Category(BaseModel):
                __tablename__ = 'categories'

                name = Column(String(100), unique=True, nullable=False, index=True)
                slug = Column(String(100), unique=True, nullable=False, index=True)
                description = Column(Text)
                parent_id = Column(Integer, ForeignKey('categories.id'))
                sort_order = Column(Integer, default=0)

                # 自引用关系(父子分类)
                children = relationship('Category',
                                     backref=backref('parent', remote_side=[id]),
                                     lazy='dynamic')

                # 一对多关系
                posts = relationship('Post', back_populates='category', lazy='dynamic')

                def __repr__(self):
                    return f'<Category {self.name}>'

                @property
                def posts_count(self):
                    """获取分类下的文章数量"""
                    return self.posts.filter_by(is_active=True).count()

                def get_hierarchy_path(self):
                    """获取分类层级路径"""
                    if self.parent:
                        return f"{self.parent.get_hierarchy_path()}/{self.slug}"
                    return self.slug

                def get_all_children(self):
                    """获取所有子分类(递归)"""
                    children = list(self.children)
                    for child in self.children:
                        children.extend(child.get_all_children())
                    return children

            # 标签模型
            class Tag(BaseModel):
                __tablename__ = 'tags'

                name = Column(String(50), unique=True, nullable=False, index=True)
                slug = Column(String(50), unique=True, nullable=False, index=True)
                description = Column(Text)
                color = Column(String(7))  # 十六进制颜色代码

                def __repr__(self):
                    return f'<Tag {self.name}>'

                @property
                def posts_count(self):
                    """获取使用该标签的文章数量"""
                    return len(self.posts)

                def get_related_tags(self, limit=10):
                    """获取相关标签(基于共同文章)"""
                    # 找到有共同文章的标签
                    related_tags = db.session.query(Tag).join(post_tags).join(Post).filter(
                        post_tags.c.tag_id == self.id,
                        Post.is_active == True
                    ).join(post_tags, Post.id == post_tags.c.post_id).filter(
                        post_tags.c.tag_id != self.id
                    ).group_by(Tag.id).order_by(
                        db.func.count(Tag.id).desc()
                    ).limit(limit).all()

                    return related_tags

            # 文章模型
            class Post(BaseModel):
                __tablename__ = 'posts'

                # 基本信息
                title = Column(String(200), nullable=False, index=True)
                slug = Column(String(200), unique=True, nullable=False, index=True)
                content = Column(Text, nullable=False)
                excerpt = Column(Text)

                # 作者和分类
                author_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True)
                category_id = Column(Integer, ForeignKey('categories.id'), nullable=False, index=True)

                # 状态信息
                is_published = Column(Boolean, default=False, index=True)
                is_featured = Column(Boolean, default=False)
                view_count = Column(Integer, default=0)
                like_count = Column(Integer, default=0)
                comment_count = Column(Integer, default=0)

                # 时间信息
                published_at = Column(DateTime)
                expires_at = Column(DateTime)

                # SEO信息
                meta_title = Column(String(200))
                meta_description = Column(String(500))
                meta_keywords = Column(String(500))

                # 关系定义
                author = relationship('User', back_populates='posts')
                category = relationship('Category', back_populates='posts')
                tags = relationship('Tag', secondary=post_tags, back_populates='posts')
                comments = relationship('Comment', back_populates='post', lazy='dynamic', cascade='all, delete-orphan')

                def __repr__(self):
                    return f'<Post {self.title}>'

                @property
                def url(self):
                    """获取文章URL"""
                    return f"/posts/{self.slug}"

                @property
                def word_count(self):
                    """获取文章字数"""
                    return len(self.content.split()) if self.content else 0

                @property
                def reading_time(self):
                    """估算阅读时间(分钟)"""
                    words_per_minute = 200
                    return max(1, round(self.word_count / words_per_minute))

                def increment_view_count(self):
                    """增加浏览次数"""
                    self.view_count += 1
                    db.session.commit()

                def increment_like_count(self):
                    """增加点赞次数"""
                    self.like_count += 1
                    db.session.commit()

                def update_comment_count(self):
                    """更新评论数量"""
                    self.comment_count = self.comments.filter_by(is_active=True, is_approved=True).count()
                    db.session.commit()

                def is_accessible_by_user(self, user):
                    """检查用户是否可以访问此文章"""
                    if self.is_published:
                        return True
                    if user and (user == self.author or user.has_permission('manage_posts')):
                        return True
                    return False

                def get_related_posts(self, limit=5):
                    """获取相关文章"""
                    # 基于标签和分类获取相关文章
                    related_posts = Post.query.filter(
                        Post.id != self.id,
                        Post.is_published == True,
                        Post.is_active == True,
                        db.or_(
                            Post.category_id == self.category_id,
                            Post.tags.any(Tag.id.in_([tag.id for tag in self.tags]))
                        )
                    ).order_by(
                        Post.published_at.desc()
                    ).limit(limit).all()

                    return related_posts

                def add_tag(self, tag):
                    """添加标签"""
                    if tag not in self.tags:
                        self.tags.append(tag)
                        self.save()

                def remove_tag(self, tag):
                    """移除标签"""
                    if tag in self.tags:
                        self.tags.remove(tag)
                        self.save()

                def publish(self):
                    """发布文章"""
                    self.is_published = True
                    self.published_at = datetime.utcnow()
                    self.save()

                def unpublish(self):
                    """取消发布文章"""
                    self.is_published = False
                    self.save()

            # 评论模型
            class Comment(BaseModel):
                __tablename__ = 'comments'

                # 评论内容
                content = Column(Text, nullable=False)
                author_name = Column(String(100))  # 游客评论时使用
                author_email = Column(String(120))  # 游客评论时使用
                author_ip = Column(String(45))

                # 关联信息
                author_id = Column(Integer, ForeignKey('users.id'), nullable=True, index=True)
                post_id = Column(Integer, ForeignKey('posts.id'), nullable=False, index=True)
                parent_id = Column(Integer, ForeignKey('comments.id'), nullable=True)

                # 状态信息
                is_approved = Column(Boolean, default=False, index=True)
                is_spam = Column(Boolean, default=False)

                # 关系定义
                author = relationship('User', back_populates='comments')
                post = relationship('Post', back_populates='comments')
                replies = relationship('Comment', backref=backref('parent', remote_side=[id]), lazy='dynamic')

                def __repr__(self):
                    return f'<Comment {self.id}>'

                @property
                def author_display_name(self):
                    """获取显示的作者名称"""
                    if self.author:
                        return self.author.full_name
                    return self.author_name or 'Anonymous'

                @property
                def replies_count(self):
                    """获取回复数量"""
                    return self.replies.filter_by(is_active=True).count()

                def approve(self):
                    """批准评论"""
                    self.is_approved = True
                    self.is_spam = False
                    self.save()
                    # 更新文章评论数
                    self.post.update_comment_count()

                def reject(self):
                    """拒绝评论(标记为垃圾)"""
                    self.is_approved = False
                    self.is_spam = True
                    self.save()

                def add_reply(self, content, author=None):
                    """添加回复"""
                    reply = Comment(
                        content=content,
                        author_id=author.id if author else None,
                        post_id=self.post_id,
                        parent_id=self.id,
                        is_approved=True  # 回复默认自动批准
                    )
                    return reply.save()

            # 数据库初始化函数
            def init_database():
                """初始化数据库,创建表和默认数据"""
                with app.app_context():
                    # 创建所有表
                    db.create_all()

                    # 创建默认角色
                    admin_role = Role.query.filter_by(name='admin').first()
                    if not admin_role:
                        admin_role = Role(
                            name='admin',
                            description='系统管理员',
                            permissions='["manage_users", "manage_posts", "manage_comments", "manage_settings"]'
                        )
                        admin_role.save()

                    editor_role = Role.query.filter_by(name='editor').first()
                    if not editor_role:
                        editor_role = Role(
                            name='editor',
                            description='编辑',
                            permissions='["manage_posts", "manage_comments"]'
                        )
                        editor_role.save()

                    user_role = Role.query.filter_by(name='user').first()
                    if not user_role:
                        user_role = Role(
                            name='user',
                            description='普通用户',
                            permissions='["read_posts", "create_comments"]'
                        )
                        user_role.save()

                    # 创建默认管理员用户
                    admin_user = User.query.filter_by(username='admin').first()
                    if not admin_user:
                        admin_user = User(
                            username='admin',
                            email='[email protected]',
                            first_name='Admin',
                            last_name='User',
                            is_verified=True
                        )
                        admin_user.set_password('admin123')
                        admin_user.save()
                        admin_user.roles.append(admin_role)
                        admin_user.save()

                    # 创建默认分类
                    default_category = Category.query.filter_by(slug='uncategorized').first()
                    if not default_category:
                        default_category = Category(
                            name='未分类',
                            slug='uncategorized',
                            description='默认分类'
                        )
                        default_category.save()

                    # 创建一些默认标签
                    default_tags = ['技术', '生活', '教程', '新闻']
                    for tag_name in default_tags:
                        tag = Tag.query.filter_by(name=tag_name).first()
                        if not tag:
                            tag = Tag(
                                name=tag_name,
                                slug=tag_name.lower(),
                                color='#007bff'
                            )
                            tag.save()

                print("Database initialized successfully!")

            if __name__ == '__main__':
                init_database()
                app.run(debug=True)

5.2 数据库操作和查询

01.CRUD 操作实现
    a.基础增删改查
        a.用户管理 CRUD 操作
            from flask import Flask, jsonify, request, g
            from flask_sqlalchemy import SQLAlchemy
            from sqlalchemy import and_, or_, not_, func, desc, asc, text
            from sqlalchemy.exc import SQLAlchemyError
            from datetime import datetime, timedelta
            import re

            app = Flask(__name__)
            app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog_crud.db'
            app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
            db = SQLAlchemy(app)

            # 使用前面定义的模型类
            from models import User, Post, Comment, Category, Tag, BaseModel

            class UserService:
                """用户服务类,包含完整的CRUD操作"""

                @staticmethod
                def create_user(user_data):
                    """创建新用户"""
                    try:
                        # 验证必填字段
                        required_fields = ['username', 'email', 'password']
                        for field in required_fields:
                            if not user_data.get(field):
                                return {'success': False, 'error': f'{field} is required'}

                        # 检查用户名和邮箱是否已存在
                        if User.query.filter_by(username=user_data['username']).first():
                            return {'success': False, 'error': 'Username already exists'}

                        if User.query.filter_by(email=user_data['email']).first():
                            return {'success': False, 'error': 'Email already exists'}

                        # 创建新用户
                        user = User(
                            username=user_data['username'],
                            email=user_data['email'],
                            first_name=user_data.get('first_name', ''),
                            last_name=user_data.get('last_name', ''),
                            bio=user_data.get('bio', '')
                        )
                        user.set_password(user_data['password'])

                        # 分配默认角色
                        default_role = Role.query.filter_by(name='user').first()
                        if default_role:
                            user.roles.append(default_role)

                        user.save()

                        return {
                            'success': True,
                            'user': user.to_dict(include_relationships=False)
                        }

                    except SQLAlchemyError as e:
                        db.session.rollback()
                        return {'success': False, 'error': f'Database error: {str(e)}'}
                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def get_user(user_id, include_sensitive=False):
                    """获取单个用户信息"""
                    try:
                        user = User.query.filter_by(id=user_id, is_active=True).first()
                        if not user:
                            return {'success': False, 'error': 'User not found'}

                        user_data = user.to_dict(include_relationships=True)

                        # 移除敏感信息
                        if not include_sensitive:
                            user_data.pop('password_hash', None)

                        return {'success': True, 'user': user_data}

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def get_users(filters=None, pagination=None, sorting=None):
                    """获取用户列表,支持过滤、分页和排序"""
                    try:
                        query = User.query.filter_by(is_active=True)

                        # 应用过滤器
                        if filters:
                            if filters.get('search'):
                                search_term = f"%{filters['search']}%"
                                query = query.filter(
                                    or_(
                                        User.username.ilike(search_term),
                                        User.email.ilike(search_term),
                                        User.first_name.ilike(search_term),
                                        User.last_name.ilike(search_term)
                                    )
                                )

                            if filters.get('role'):
                                query = query.filter(User.roles.any(Role.name == filters['role']))

                            if filters.get('is_verified') is not None:
                                query = query.filter(User.is_verified == filters['is_verified'])

                            if filters.get('last_login_from'):
                                query = query.filter(User.last_login >= filters['last_login_from'])

                            if filters.get('last_login_to'):
                                query = query.filter(User.last_login <= filters['last_login_to'])

                        # 应用排序
                        if sorting:
                            field = sorting.get('field', 'created_at')
                            direction = desc if sorting.get('direction') == 'desc' else asc

                            if hasattr(User, field):
                                query = query.order_by(direction(getattr(User, field)))
                        else:
                            query = query.order_by(desc(User.created_at))

                        # 应用分页
                        total = query.count()
                        if pagination:
                            page = pagination.get('page', 1)
                            per_page = min(pagination.get('per_page', 20), 100)  # 限制每页最大数量

                            offset = (page - 1) * per_page
                            query = query.offset(offset).limit(per_page)

                        users = query.all()
                        users_data = [user.to_dict(include_relationships=False) for user in users]

                        return {
                            'success': True,
                            'users': users_data,
                            'total': total,
                            'page': pagination.get('page', 1) if pagination else 1,
                            'per_page': pagination.get('per_page', 20) if pagination else total
                        }

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def update_user(user_id, update_data):
                    """更新用户信息"""
                    try:
                        user = User.query.filter_by(id=user_id, is_active=True).first()
                        if not user:
                            return {'success': False, 'error': 'User not found'}

                        # 更新基本信息
                        updatable_fields = ['username', 'email', 'first_name', 'last_name', 'bio', 'avatar_url']
                        for field in updatable_fields:
                            if field in update_data:
                                # 检查唯一性约束
                                if field in ['username', 'email']:
                                    existing_user = User.query.filter(
                                        getattr(User, field) == update_data[field],
                                        User.id != user_id
                                    ).first()
                                    if existing_user:
                                        return {'success': False, 'error': f'{field} already exists'}

                                setattr(user, field, update_data[field])

                        # 更新密码
                        if 'password' in update_data:
                            user.set_password(update_data['password'])

                        # 更新角色
                        if 'roles' in update_data:
                            # 清除现有角色
                            user.roles.clear()

                            # 添加新角色
                            for role_name in update_data['roles']:
                                role = Role.query.filter_by(name=role_name).first()
                                if role:
                                    user.roles.append(role)

                        # 更新其他字段
                        if 'is_verified' in update_data:
                            user.is_verified = update_data['is_verified']

                        if 'is_active' in update_data:
                            user.is_active = update_data['is_active']

                        user.save()

                        return {
                            'success': True,
                            'user': user.to_dict(include_relationships=True)
                        }

                    except SQLAlchemyError as e:
                        db.session.rollback()
                        return {'success': False, 'error': f'Database error: {str(e)}'}
                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def delete_user(user_id, soft_delete=True):
                    """删除用户"""
                    try:
                        user = User.query.filter_by(id=user_id).first()
                        if not user:
                            return {'success': False, 'error': 'User not found'}

                        if soft_delete:
                            # 软删除
                            user.soft_delete()
                            return {'success': True, 'message': 'User soft deleted successfully'}
                        else:
                            # 硬删除
                            # 检查是否有关联数据
                            posts_count = user.posts.count()
                            comments_count = user.comments.count()

                            if posts_count > 0 or comments_count > 0:
                                return {
                                    'success': False,
                                    'error': 'Cannot delete user with associated posts or comments',
                                    'posts_count': posts_count,
                                    'comments_count': comments_count
                                }

                            user.delete()
                            return {'success': True, 'message': 'User deleted successfully'}

                    except SQLAlchemyError as e:
                        db.session.rollback()
                        return {'success': False, 'error': f'Database error: {str(e)}'}
                    except Exception as e:
                        return {'success': False, 'error': str(e)}

            # 文章服务类
            class PostService:
                """文章服务类"""

                @staticmethod
                def create_post(post_data, author_id):
                    """创建新文章"""
                    try:
                        # 验证必填字段
                        required_fields = ['title', 'content', 'category_id']
                        for field in required_fields:
                            if not post_data.get(field):
                                return {'success': False, 'error': f'{field} is required'}

                        # 验证作者是否存在
                        author = User.query.filter_by(id=author_id, is_active=True).first()
                        if not author:
                            return {'success': False, 'error': 'Author not found'}

                        # 验证分类是否存在
                        category = Category.query.filter_by(id=post_data['category_id'], is_active=True).first()
                        if not category:
                            return {'success': False, 'error': 'Category not found'}

                        # 生成唯一的slug
                        slug = post_data['title'].lower().replace(' ', '-').replace(',', '').replace('.', '')
                        slug = re.sub(r'[^a-z0-9-]', '', slug)

                        # 确保slug唯一
                        original_slug = slug
                        counter = 1
                        while Post.query.filter_by(slug=slug).first():
                            slug = f"{original_slug}-{counter}"
                            counter += 1

                        # 创建文章
                        post = Post(
                            title=post_data['title'],
                            slug=slug,
                            content=post_data['content'],
                            excerpt=post_data.get('excerpt', ''),
                            author_id=author_id,
                            category_id=post_data['category_id'],
                            is_published=post_data.get('is_published', False),
                            is_featured=post_data.get('is_featured', False),
                            meta_title=post_data.get('meta_title', ''),
                            meta_description=post_data.get('meta_description', ''),
                            meta_keywords=post_data.get('meta_keywords', '')
                        )

                        post.save()

                        # 添加标签
                        if 'tag_ids' in post_data:
                            for tag_id in post_data['tag_ids']:
                                tag = Tag.query.filter_by(id=tag_id, is_active=True).first()
                                if tag:
                                    post.tags.append(tag)

                        post.save()

                        return {
                            'success': True,
                            'post': post.to_dict(include_relationships=True)
                        }

                    except SQLAlchemyError as e:
                        db.session.rollback()
                        return {'success': False, 'error': f'Database error: {str(e)}'}
                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def get_post(post_id, user=None):
                    """获取单个文章"""
                    try:
                        post = Post.query.filter_by(id=post_id, is_active=True).first()
                        if not post:
                            return {'success': False, 'error': 'Post not found'}

                        # 检查访问权限
                        if not post.is_accessible_by_user(user):
                            return {'success': False, 'error': 'Access denied'}

                        # 增加浏览次数
                        post.increment_view_count()

                        return {
                            'success': True,
                            'post': post.to_dict(include_relationships=True)
                        }

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def get_posts(filters=None, pagination=None, sorting=None):
                    """获取文章列表"""
                    try:
                        query = Post.query.filter_by(is_active=True)

                        # 应用过滤器
                        if filters:
                            # 根据用户权限过滤
                            if filters.get('user_id'):
                                query = query.filter(Post.author_id == filters['user_id'])

                            if filters.get('category_id'):
                                query = query.filter(Post.category_id == filters['category_id'])

                            if filters.get('tag_id'):
                                query = query.filter(Post.tags.any(Tag.id == filters['tag_id']))

                            if filters.get('is_published') is not None:
                                query = query.filter(Post.is_published == filters['is_published'])

                            if filters.get('is_featured') is not None:
                                query = query.filter(Post.is_featured == filters['is_featured'])

                            if filters.get('search'):
                                search_term = f"%{filters['search']}%"
                                query = query.filter(
                                    or_(
                                        Post.title.ilike(search_term),
                                        Post.content.ilike(search_term),
                                        Post.excerpt.ilike(search_term)
                                    )
                                )

                            if filters.get('date_from'):
                                query = query.filter(Post.created_at >= filters['date_from'])

                            if filters.get('date_to'):
                                query = query.filter(Post.created_at <= filters['date_to'])

                        # 应用排序
                        if sorting:
                            field = sorting.get('field', 'created_at')
                            direction = desc if sorting.get('direction') == 'desc' else asc

                            if hasattr(Post, field):
                                query = query.order_by(direction(getattr(Post, field)))
                        else:
                            query = query.order_by(desc(Post.published_at))

                        # 应用分页
                        total = query.count()
                        if pagination:
                            page = pagination.get('page', 1)
                            per_page = min(pagination.get('per_page', 10), 50)

                            offset = (page - 1) * per_page
                            query = query.offset(offset).limit(per_page)

                        posts = query.all()
                        posts_data = [post.to_dict(include_relationships=False) for post in posts]

                        return {
                            'success': True,
                            'posts': posts_data,
                            'total': total,
                            'page': pagination.get('page', 1) if pagination else 1,
                            'per_page': pagination.get('per_page', 10) if pagination else total
                        }

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def update_post(post_id, update_data, user=None):
                    """更新文章"""
                    try:
                        post = Post.query.filter_by(id=post_id, is_active=True).first()
                        if not post:
                            return {'success': False, 'error': 'Post not found'}

                        # 检查权限
                        if not post.is_accessible_by_user(user):
                            return {'success': False, 'error': 'Access denied'}

                        # 更新基本信息
                        updatable_fields = ['title', 'content', 'excerpt', 'category_id',
                                          'is_published', 'is_featured', 'meta_title',
                                          'meta_description', 'meta_keywords']

                        for field in updatable_fields:
                            if field in update_data:
                                # 特殊处理:如果更改标题,重新生成slug
                                if field == 'title' and update_data[field] != post.title:
                                    new_slug = update_data[field].lower().replace(' ', '-').replace(',', '').replace('.', '')
                                    new_slug = re.sub(r'[^a-z0-9-]', '', new_slug)

                                    # 确保新slug唯一
                                    original_slug = new_slug
                                    counter = 1
                                    while Post.query.filter(Post.slug == new_slug, Post.id != post_id).first():
                                        new_slug = f"{original_slug}-{counter}"
                                        counter += 1

                                    post.slug = new_slug

                                setattr(post, field, update_data[field])

                        # 验证分类
                        if 'category_id' in update_data:
                            category = Category.query.filter_by(
                                id=update_data['category_id'],
                                is_active=True
                            ).first()
                            if not category:
                                return {'success': False, 'error': 'Category not found'}

                        # 更新标签
                        if 'tag_ids' in update_data:
                            post.tags.clear()
                            for tag_id in update_data['tag_ids']:
                                tag = Tag.query.filter_by(id=tag_id, is_active=True).first()
                                if tag:
                                    post.tags.append(tag)

                        # 发布/取消发布处理
                        if 'is_published' in update_data:
                            if update_data['is_published'] and not post.is_published:
                                post.publish()
                            elif not update_data['is_published'] and post.is_published:
                                post.unpublish()

                        post.save()

                        return {
                            'success': True,
                            'post': post.to_dict(include_relationships=True)
                        }

                    except SQLAlchemyError as e:
                        db.session.rollback()
                        return {'success': False, 'error': f'Database error: {str(e)}'}
                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def delete_post(post_id, user=None):
                    """删除文章"""
                    try:
                        post = Post.query.filter_by(id=post_id, is_active=True).first()
                        if not post:
                            return {'success': False, 'error': 'Post not found'}

                        # 检查权限
                        if not post.is_accessible_by_user(user):
                            return {'success': False, 'error': 'Access denied'}

                        # 软删除
                        post.soft_delete()

                        return {'success': True, 'message': 'Post deleted successfully'}

                    except SQLAlchemyError as e:
                        db.session.rollback()
                        return {'success': False, 'error': f'Database error: {str(e)}'}
                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def like_post(post_id):
                    """点赞文章"""
                    try:
                        post = Post.query.filter_by(id=post_id, is_active=True).first()
                        if not post:
                            return {'success': False, 'error': 'Post not found'}

                        post.increment_like_count()

                        return {
                            'success': True,
                            'like_count': post.like_count
                        }

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

            # 评论服务类
            class CommentService:
                """评论服务类"""

                @staticmethod
                def create_comment(comment_data, post_id, author_id=None):
                    """创建新评论"""
                    try:
                        # 验证文章是否存在
                        post = Post.query.filter_by(id=post_id, is_active=True).first()
                        if not post:
                            return {'success': False, 'error': 'Post not found'}

                        # 验证必填字段
                        if not comment_data.get('content'):
                            return {'success': False, 'error': 'Content is required'}

                        # 验证作者
                        author = None
                        if author_id:
                            author = User.query.filter_by(id=author_id, is_active=True).first()
                            if not author:
                                return {'success': False, 'error': 'Author not found'}
                        else:
                            # 游客评论,需要姓名和邮箱
                            if not comment_data.get('author_name') or not comment_data.get('author_email'):
                                return {'success': False, 'error': 'Author name and email required for guest comments'}

                        # 验证父评论(如果是回复)
                        parent_comment = None
                        if comment_data.get('parent_id'):
                            parent_comment = Comment.query.filter_by(
                                id=comment_data['parent_id'],
                                post_id=post_id,
                                is_active=True
                            ).first()
                            if not parent_comment:
                                return {'success': False, 'error': 'Parent comment not found'}

                        # 创建评论
                        comment = Comment(
                            content=comment_data['content'],
                            author_id=author_id,
                            post_id=post_id,
                            parent_id=comment_data.get('parent_id'),
                            author_name=comment_data.get('author_name') if not author else None,
                            author_email=comment_data.get('author_email') if not author else None,
                            author_ip=comment_data.get('author_ip'),
                            is_approved=author is not None  # 注册用户评论自动批准
                        )

                        comment.save()

                        # 更新文章评论数
                        post.update_comment_count()

                        return {
                            'success': True,
                            'comment': comment.to_dict(include_relationships=True)
                        }

                    except SQLAlchemyError as e:
                        db.session.rollback()
                        return {'success': False, 'error': f'Database error: {str(e)}'}
                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                @staticmethod
                def get_comments(post_id, filters=None, pagination=None):
                    """获取评论列表"""
                    try:
                        query = Comment.query.filter_by(post_id=post_id, is_active=True)

                        # 应用过滤器
                        if filters:
                            if filters.get('is_approved') is not None:
                                query = query.filter(Comment.is_approved == filters['is_approved'])

                            if filters.get('author_id'):
                                query = query.filter(Comment.author_id == filters['author_id'])

                            if filters.get('parent_id') is not None:
                                query = query.filter(Comment.parent_id == filters['parent_id'])

                        # 默认排序:顶级评论在前,按时间排序
                        query = query.order_by(asc(Comment.parent_id.nullslast()), desc(Comment.created_at))

                        # 应用分页
                        total = query.count()
                        if pagination:
                            page = pagination.get('page', 1)
                            per_page = min(pagination.get('per_page', 20), 100)

                            offset = (page - 1) * per_page
                            query = query.offset(offset).limit(per_page)

                        comments = query.all()
                        comments_data = [comment.to_dict(include_relationships=True) for comment in comments]

                        return {
                            'success': True,
                            'comments': comments_data,
                            'total': total,
                            'page': pagination.get('page', 1) if pagination else 1,
                            'per_page': pagination.get('per_page', 20) if pagination else total
                        }

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

            # Flask 路由定义
            @app.route('/api/users', methods=['POST'])
            def create_user():
                """创建用户API"""
                user_data = request.get_json()
                result = UserService.create_user(user_data)

                if result['success']:
                    return jsonify(result), 201
                else:
                    return jsonify(result), 400

            @app.route('/api/users/<int:user_id>')
            def get_user(user_id):
                """获取用户信息API"""
                result = UserService.get_user(user_id)

                if result['success']:
                    return jsonify(result)
                else:
                    return jsonify(result), 404

            @app.route('/api/users')
            def get_users():
                """获取用户列表API"""
                filters = {}
                pagination = {}
                sorting = {}

                # 解析查询参数
                if request.args.get('search'):
                    filters['search'] = request.args.get('search')

                if request.args.get('role'):
                    filters['role'] = request.args.get('role')

                if request.args.get('is_verified'):
                    filters['is_verified'] = request.args.get('is_verified').lower() == 'true'

                if request.args.get('page'):
                    pagination['page'] = int(request.args.get('page'))

                if request.args.get('per_page'):
                    pagination['per_page'] = int(request.args.get('per_page'))

                if request.args.get('sort'):
                    sorting['field'] = request.args.get('sort')

                if request.args.get('order'):
                    sorting['direction'] = request.args.get('order')

                result = UserService.get_users(filters, pagination, sorting)
                return jsonify(result)

            @app.route('/api/users/<int:user_id>', methods=['PUT'])
            def update_user(user_id):
                """更新用户信息API"""
                update_data = request.get_json()
                result = UserService.update_user(user_id, update_data)

                if result['success']:
                    return jsonify(result)
                else:
                    return jsonify(result), 400

            @app.route('/api/users/<int:user_id>', methods=['DELETE'])
            def delete_user(user_id):
                """删除用户API"""
                soft_delete = request.args.get('soft', 'true').lower() == 'true'
                result = UserService.delete_user(user_id, soft_delete)

                if result['success']:
                    return jsonify(result)
                else:
                    return jsonify(result), 400

            @app.route('/api/posts', methods=['POST'])
            def create_post():
                """创建文章API"""
                post_data = request.get_json()
                author_id = post_data.get('author_id')  # 在实际应用中应该从认证中获取

                result = PostService.create_post(post_data, author_id)

                if result['success']:
                    return jsonify(result), 201
                else:
                    return jsonify(result), 400

            @app.route('/api/posts/<int:post_id>')
            def get_post(post_id):
                """获取文章API"""
                # 在实际应用中应该从认证中获取用户信息
                user = None  # User.query.get(request.user.id) if request.user else None

                result = PostService.get_post(post_id, user)

                if result['success']:
                    return jsonify(result)
                else:
                    return jsonify(result), 404

            @app.route('/api/posts')
            def get_posts():
                """获取文章列表API"""
                filters = {}
                pagination = {}
                sorting = {}

                # 解析查询参数
                if request.args.get('search'):
                    filters['search'] = request.args.get('search')

                if request.args.get('category_id'):
                    filters['category_id'] = int(request.args.get('category_id'))

                if request.args.get('tag_id'):
                    filters['tag_id'] = int(request.args.get('tag_id'))

                if request.args.get('is_published'):
                    filters['is_published'] = request.args.get('is_published').lower() == 'true'

                if request.args.get('page'):
                    pagination['page'] = int(request.args.get('page'))

                if request.args.get('per_page'):
                    pagination['per_page'] = int(request.args.get('per_page'))

                if request.args.get('sort'):
                    sorting['field'] = request.args.get('sort')

                if request.args.get('order'):
                    sorting['direction'] = request.args.get('order')

                result = PostService.get_posts(filters, pagination, sorting)
                return jsonify(result)

            @app.route('/api/posts/<int:post_id>/like', methods=['POST'])
            def like_post(post_id):
                """点赞文章API"""
                result = PostService.like_post(post_id)

                if result['success']:
                    return jsonify(result)
                else:
                    return jsonify(result), 404

            @app.route('/api/posts/<int:post_id>/comments', methods=['POST'])
            def create_comment(post_id):
                """创建评论API"""
                comment_data = request.get_json()
                author_id = comment_data.get('author_id')  # 可选,从认证中获取

                result = CommentService.create_comment(comment_data, post_id, author_id)

                if result['success']:
                    return jsonify(result), 201
                else:
                    return jsonify(result), 400

            @app.route('/api/posts/<int:post_id>/comments')
            def get_comments(post_id):
                """获取评论列表API"""
                filters = {}
                pagination = {}

                if request.args.get('is_approved'):
                    filters['is_approved'] = request.args.get('is_approved').lower() == 'true'

                if request.args.get('page'):
                    pagination['page'] = int(request.args.get('page'))

                if request.args.get('per_page'):
                    pagination['per_page'] = int(request.args.get('per_page'))

                result = CommentService.get_comments(post_id, filters, pagination)
                return jsonify(result)

            # 数据库统计API
            @app.route('/api/stats/dashboard')
            def get_dashboard_stats():
                """获取仪表板统计数据"""
                try:
                    stats = {
                        'users': {
                            'total': User.query.filter_by(is_active=True).count(),
                            'verified': User.query.filter_by(is_active=True, is_verified=True).count(),
                            'new_this_month': User.query.filter(
                                User.is_active == True,
                                User.created_at >= datetime.utcnow() - timedelta(days=30)
                            ).count()
                        },
                        'posts': {
                            'total': Post.query.filter_by(is_active=True).count(),
                            'published': Post.query.filter_by(is_active=True, is_published=True).count(),
                            'featured': Post.query.filter_by(is_active=True, is_featured=True).count(),
                            'new_this_month': Post.query.filter(
                                Post.is_active == True,
                                Post.created_at >= datetime.utcnow() - timedelta(days=30)
                            ).count()
                        },
                        'comments': {
                            'total': Comment.query.filter_by(is_active=True).count(),
                            'approved': Comment.query.filter_by(is_active=True, is_approved=True).count(),
                            'pending': Comment.query.filter_by(is_active=True, is_approved=False).count(),
                            'new_this_month': Comment.query.filter(
                                Comment.is_active == True,
                                Comment.created_at >= datetime.utcnow() - timedelta(days=30)
                            ).count()
                        },
                        'categories': {
                            'total': Category.query.filter_by(is_active=True).count()
                        },
                        'tags': {
                            'total': Tag.query.filter_by(is_active=True).count()
                        }
                    }

                    # 最受欢迎的文章
                    popular_posts = Post.query.filter_by(is_active=True, is_published=True).order_by(
                        desc(Post.view_count)
                    ).limit(5).all()

                    stats['popular_posts'] = [post.to_dict(include_relationships=False) for post in popular_posts]

                    return jsonify({'success': True, 'stats': stats})

                except Exception as e:
                    return jsonify({'success': False, 'error': str(e)}), 500

            if __name__ == '__main__':
                # 初始化数据库
                init_database()
                app.run(debug=True)

5.3 数据库迁移和优化

01.Flask-Migrate 数据库迁移
    a.基础迁移配置
        a.设置 Flask-Migrate
            from flask import Flask
            from flask_sqlalchemy import SQLAlchemy
            from flask_migrate import Migrate, MigrateCommand
            from flask_script import Manager
            import os

            app = Flask(__name__)

            # 数据库配置
            def get_database_url():
                """获取数据库URL,支持不同环境"""
                env = os.environ.get('FLASK_ENV', 'development')

                if env == 'testing':
                    return 'sqlite:///test_blog.db'
                elif env == 'production':
                    # 生产环境使用PostgreSQL
                    db_host = os.environ.get('DB_HOST', 'localhost')
                    db_port = os.environ.get('DB_PORT', '5432')
                    db_name = os.environ.get('DB_NAME', 'blog_prod')
                    db_user = os.environ.get('DB_USER', 'postgres')
                    db_password = os.environ.get('DB_PASSWORD', '')

                    return f'postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}'
                else:
                    # 开发环境使用SQLite
                    return 'sqlite:///blog_dev.db'

            app.config['SQLALCHEMY_DATABASE_URI'] = get_database_url()
            app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

            # 创建数据库实例
            db = SQLAlchemy(app)

            # 创建迁移实例
            migrate = Migrate(app, db)

            # 创建命令管理器
            manager = Manager(app)
            manager.add_command('db', MigrateCommand)

            # 导入模型(确保所有模型都被导入)
            from models import User, Post, Comment, Category, Tag, Role

            class DatabaseMigrationManager:
                """数据库迁移管理器"""

                def __init__(self, app=None, db=None):
                    self.app = app
                    self.db = db
                    self.migration_history = []

                def init_app(self, app, db):
                    """初始化迁移管理器"""
                    self.app = app
                    self.db = db

                def create_migration(self, message=None):
                    """创建新的迁移文件"""
                    import subprocess
                    import sys

                    try:
                        cmd = [sys.executable, '-m', 'flask', 'db', 'revision', '--autogenerate']
                        if message:
                            cmd.extend(['-m', message])

                        result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.getcwd())

                        if result.returncode == 0:
                            return {'success': True, 'message': 'Migration created successfully', 'output': result.stdout}
                        else:
                            return {'success': False, 'error': result.stderr}

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                def apply_migrations(self, target='head'):
                    """应用迁移"""
                    import subprocess
                    import sys

                    try:
                        cmd = [sys.executable, '-m', 'flask', 'db', 'upgrade', target]
                        result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.getcwd())

                        if result.returncode == 0:
                            return {'success': True, 'message': 'Migrations applied successfully', 'output': result.stdout}
                        else:
                            return {'success': False, 'error': result.stderr}

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                def rollback_migration(self, revision):
                    """回滚到指定版本"""
                    import subprocess
                    import sys

                    try:
                        cmd = [sys.executable, '-m', 'flask', 'db', 'downgrade', revision]
                        result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.getcwd())

                        if result.returncode == 0:
                            return {'success': True, 'message': 'Rollback successful', 'output': result.stdout}
                        else:
                            return {'success': False, 'error': result.stderr}

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                def get_migration_status(self):
                    """获取迁移状态"""
                    import subprocess
                    import sys

                    try:
                        # 获取当前版本
                        cmd_current = [sys.executable, '-m', 'flask', 'db', 'current']
                        result_current = subprocess.run(cmd_current, capture_output=True, text=True, cwd=os.getcwd())

                        # 获取历史记录
                        cmd_history = [sys.executable, '-m', 'flask', 'db', 'history']
                        result_history = subprocess.run(cmd_history, capture_output=True, text=True, cwd=os.getcwd())

                        return {
                            'success': True,
                            'current': result_current.stdout.strip() if result_current.returncode == 0 else 'Unknown',
                            'history': result_history.stdout.strip() if result_history.returncode == 0 else 'Unknown'
                        }

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

            # 创建迁移管理器实例
            migration_manager = DatabaseMigrationManager(app, db)

            # 数据库备份和恢复
            class DatabaseBackupManager:
                """数据库备份管理器"""

                def __init__(self, app=None):
                    self.app = app
                    self.backup_dir = 'backups'
                    os.makedirs(self.backup_dir, exist_ok=True)

                def create_backup(self, backup_name=None):
                    """创建数据库备份"""
                    from datetime import datetime
                    import json
                    import shutil

                    try:
                        if not backup_name:
                            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                            backup_name = f'backup_{timestamp}'

                        backup_path = os.path.join(self.backup_dir, backup_name)
                        os.makedirs(backup_path, exist_ok=True)

                        # 获取数据库配置
                        db_url = app.config['SQLALCHEMY_DATABASE_URI']

                        if db_url.startswith('sqlite'):
                            # SQLite备份
                            db_path = db_url.replace('sqlite:///', '')
                            if os.path.exists(db_path):
                                shutil.copy2(db_path, os.path.join(backup_path, 'database.db'))

                                # 备份元数据
                                metadata = {
                                    'backup_name': backup_name,
                                    'created_at': datetime.now().isoformat(),
                                    'database_type': 'sqlite',
                                    'file_path': db_path,
                                    'file_size': os.path.getsize(db_path)
                                }

                        elif db_url.startswith('postgresql'):
                            # PostgreSQL备份
                            import subprocess

                            # 解析连接信息
                            import re
                            pattern = r'postgresql://([^:]+):([^@]+)@([^:]+):(\d+)/(.+)'
                            match = re.match(pattern, db_url)

                            if match:
                                username, password, host, port, dbname = match.groups()

                                # 创建转储文件
                                dump_file = os.path.join(backup_path, 'database.sql')
                                cmd = [
                                    'pg_dump',
                                    f'--host={host}',
                                    f'--port={port}',
                                    f'--username={username}',
                                    f'--dbname={dbname}',
                                    f'--file={dump_file}',
                                    '--no-password',
                                    '--verbose',
                                    '--clean',
                                    '--if-exists',
                                    '--create'
                                ]

                                # 设置环境变量
                                env = os.environ.copy()
                                env['PGPASSWORD'] = password

                                result = subprocess.run(cmd, env=env, capture_output=True, text=True)

                                if result.returncode != 0:
                                    return {'success': False, 'error': result.stderr}

                                metadata = {
                                    'backup_name': backup_name,
                                    'created_at': datetime.now().isoformat(),
                                    'database_type': 'postgresql',
                                    'host': host,
                                    'port': port,
                                    'dbname': dbname,
                                    'dump_file': dump_file
                                }
                            else:
                                return {'success': False, 'error': 'Invalid PostgreSQL connection string'}

                        else:
                            return {'success': False, 'error': 'Unsupported database type'}

                        # 保存元数据
                        with open(os.path.join(backup_path, 'metadata.json'), 'w') as f:
                            json.dump(metadata, f, indent=2)

                        return {
                            'success': True,
                            'backup_name': backup_name,
                            'backup_path': backup_path,
                            'metadata': metadata
                        }

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                def restore_backup(self, backup_name):
                    """恢复数据库备份"""
                    import json
                    import subprocess

                    try:
                        backup_path = os.path.join(self.backup_dir, backup_name)

                        if not os.path.exists(backup_path):
                            return {'success': False, 'error': 'Backup not found'}

                        # 读取元数据
                        metadata_file = os.path.join(backup_path, 'metadata.json')
                        if not os.path.exists(metadata_file):
                            return {'success': False, 'error': 'Backup metadata not found'}

                        with open(metadata_file, 'r') as f:
                            metadata = json.load(f)

                        db_url = app.config['SQLALCHEMY_DATABASE_URI']

                        if metadata['database_type'] == 'sqlite':
                            # SQLite恢复
                            db_path = db_url.replace('sqlite:///', '')
                            backup_file = os.path.join(backup_path, 'database.db')

                            if os.path.exists(backup_file):
                                import shutil
                                shutil.copy2(backup_file, db_path)

                                return {
                                    'success': True,
                                    'message': 'SQLite database restored successfully'
                                }
                            else:
                                return {'success': False, 'error': 'SQLite backup file not found'}

                        elif metadata['database_type'] == 'postgresql':
                            # PostgreSQL恢复
                            dump_file = os.path.join(backup_path, 'database.sql')

                            if not os.path.exists(dump_file):
                                return {'success': False, 'error': 'PostgreSQL dump file not found'}

                            # 解析当前数据库连接信息
                            import re
                            pattern = r'postgresql://([^:]+):([^@]+)@([^:]+):(\d+)/(.+)'
                            match = re.match(pattern, db_url)

                            if match:
                                username, password, host, port, dbname = match.groups()

                                cmd = [
                                    'psql',
                                    f'--host={host}',
                                    f'--port={port}',
                                    f'--username={username}',
                                    f'--dbname={dbname}',
                                    '--file=' + dump_file,
                                    '--no-password',
                                    '--verbose'
                                ]

                                env = os.environ.copy()
                                env['PGPASSWORD'] = password

                                result = subprocess.run(cmd, env=env, capture_output=True, text=True)

                                if result.returncode == 0:
                                    return {
                                        'success': True,
                                        'message': 'PostgreSQL database restored successfully'
                                    }
                                else:
                                    return {'success': False, 'error': result.stderr}
                            else:
                                return {'success': False, 'error': 'Invalid PostgreSQL connection string'}

                        else:
                            return {'success': False, 'error': 'Unsupported database type'}

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                def list_backups(self):
                    """列出所有备份"""
                    import json

                    try:
                        backups = []

                        if not os.path.exists(self.backup_dir):
                            return {'success': True, 'backups': []}

                        for item in os.listdir(self.backup_dir):
                            backup_path = os.path.join(self.backup_dir, item)

                            if os.path.isdir(backup_path):
                                metadata_file = os.path.join(backup_path, 'metadata.json')

                                if os.path.exists(metadata_file):
                                    try:
                                        with open(metadata_file, 'r') as f:
                                            metadata = json.load(f)

                                        # 添加额外信息
                                        metadata['backup_path'] = backup_path
                                        metadata['size_bytes'] = self._get_directory_size(backup_path)

                                        backups.append(metadata)
                                    except:
                                        continue

                        # 按创建时间排序
                        backups.sort(key=lambda x: x['created_at'], reverse=True)

                        return {'success': True, 'backups': backups}

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                def delete_backup(self, backup_name):
                    """删除备份"""
                    import shutil

                    try:
                        backup_path = os.path.join(self.backup_dir, backup_name)

                        if not os.path.exists(backup_path):
                            return {'success': False, 'error': 'Backup not found'}

                        shutil.rmtree(backup_path)

                        return {'success': True, 'message': 'Backup deleted successfully'}

                    except Exception as e:
                        return {'success': False, 'error': str(e)}

                def _get_directory_size(self, path):
                    """获取目录大小"""
                    total_size = 0
                    try:
                        for dirpath, dirnames, filenames in os.walk(path):
                            for filename in filenames:
                                filepath = os.path.join(dirpath, filename)
                                if os.path.exists(filepath):
                                    total_size += os.path.getsize(filepath)
                    except:
                        pass
                    return total_size

            # 创建备份管理器实例
            backup_manager = DatabaseBackupManager(app)

            # API 路由
            @app.route('/api/db/migrate/create', methods=['POST'])
            def create_migration():
                """创建迁移API"""
                data = request.get_json() or {}
                message = data.get('message')

                result = migration_manager.create_migration(message)

                if result['success']:
                    return jsonify(result), 200
                else:
                    return jsonify(result), 400

            @app.route('/api/db/migrate/apply', methods=['POST'])
            def apply_migrations():
                """应用迁移API"""
                data = request.get_json() or {}
                target = data.get('target', 'head')

                result = migration_manager.apply_migrations(target)

                if result['success']:
                    return jsonify(result), 200
                else:
                    return jsonify(result), 400

            @app.route('/api/db/migrate/rollback', methods=['POST'])
            def rollback_migration():
                """回滚迁移API"""
                data = request.get_json() or {}
                revision = data.get('revision')

                if not revision:
                    return jsonify({'success': False, 'error': 'Revision is required'}), 400

                result = migration_manager.rollback_migration(revision)

                if result['success']:
                    return jsonify(result), 200
                else:
                    return jsonify(result), 400

            @app.route('/api/db/migrate/status')
            def get_migration_status():
                """获取迁移状态API"""
                result = migration_manager.get_migration_status()

                if result['success']:
                    return jsonify(result), 200
                else:
                    return jsonify(result), 500

            @app.route('/api/db/backup', methods=['POST'])
            def create_backup():
                """创建备份API"""
                data = request.get_json() or {}
                backup_name = data.get('backup_name')

                result = backup_manager.create_backup(backup_name)

                if result['success']:
                    return jsonify(result), 201
                else:
                    return jsonify(result), 400

            @app.route('/api/db/backup/<backup_name>/restore', methods=['POST'])
            def restore_backup(backup_name):
                """恢复备份API"""
                result = backup_manager.restore_backup(backup_name)

                if result['success']:
                    return jsonify(result), 200
                else:
                    return jsonify(result), 400

            @app.route('/api/db/backups')
            def list_backups():
                """列出备份API"""
                result = backup_manager.list_backups()

                if result['success']:
                    return jsonify(result), 200
                else:
                    return jsonify(result), 500

            @app.route('/api/db/backup/<backup_name>', methods=['DELETE'])
            def delete_backup(backup_name):
                """删除备份API"""
                result = backup_manager.delete_backup(backup_name)

                if result['success']:
                    return jsonify(result), 200
                else:
                    return jsonify(result), 404

            # 命令行集成
            @manager.command
            def init_db():
                """初始化数据库"""
                from models import init_database
                init_database()
                print("Database initialized successfully!")

            @manager.command
            def create_db():
                """创建数据库表"""
                from models import BaseModel, User, Post, Comment, Category, Tag, Role

                with app.app_context():
                    BaseModel.metadata.create_all(db.engine)
                    print("Database tables created successfully!")

            @manager.command
            def drop_db():
                """删除数据库表"""
                from models import BaseModel

                with app.app_context():
                    BaseModel.metadata.drop_all(db.engine)
                    print("Database tables dropped successfully!")

            @manager.command
            def reset_db():
                """重置数据库"""
                drop_db()
                init_db()
                print("Database reset successfully!")

            @manager.command
            def seed_db():
                """填充测试数据"""
                from models import User, Post, Comment, Category, Tag, Role
                from datetime import datetime

                with app.app_context():
                    # 创建测试用户
                    test_user = User(
                        username='testuser',
                        email='[email protected]',
                        first_name='Test',
                        last_name='User',
                        is_verified=True
                    )
                    test_user.set_password('test123')
                    test_user.save()

                    # 创建测试分类
                    test_category = Category(
                        name='Test Category',
                        slug='test-category',
                        description='A test category'
                    )
                    test_category.save()

                    # 创建测试标签
                    test_tag = Tag(
                        name='Test Tag',
                        slug='test-tag',
                        description='A test tag'
                    )
                    test_tag.save()

                    # 创建测试文章
                    test_post = Post(
                        title='Test Article',
                        slug='test-article',
                        content='This is a test article content.',
                        excerpt='Test article excerpt.',
                        author_id=test_user.id,
                        category_id=test_category.id,
                        is_published=True
                    )
                    test_post.save()
                    test_post.tags.append(test_tag)
                    test_post.save()

                    # 创建测试评论
                    test_comment = Comment(
                        content='This is a test comment.',
                        author_id=test_user.id,
                        post_id=test_post.id,
                        is_approved=True
                    )
                    test_comment.save()

                    print("Test data seeded successfully!")

            if __name__ == '__main__':
                # 初始化数据库(如果没有迁移文件)
                if not os.path.exists('migrations'):
                    with app.app_context():
                        from flask_migrate import init
                        init()
                        print("Initialized migration repository!")

                manager.run()

02.数据库性能优化
    a.查询优化和索引
        a.数据库性能分析工具
            from flask import Flask, jsonify, request
            from flask_sqlalchemy import SQLAlchemy
        from sqlalchemy import text, Index, event
        from sqlalchemy.engine import Engine
        from sqlalchemy.exc import SQLAlchemyError
        import time
        import logging
        from collections import defaultdict

        app = Flask(__name__)
        app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:password@localhost/optimized_blog'
        app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
        db = SQLAlchemy(app)

        # 启用SQL查询日志
        logging.basicConfig()
        logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

        class PerformanceMonitor:
            """性能监控器"""

            def __init__(self):
                self.query_times = []
                self.slow_queries = []
                self.query_frequency = defaultdict(int)

            def record_query(self, query, duration):
                """记录查询信息"""
                self.query_times.append(duration)
                self.query_frequency[query] += 1

                # 记录慢查询(超过1秒)
                if duration > 1.0:
                    self.slow_queries.append({
                        'query': query,
                        'duration': duration,
                        'timestamp': time.time()
                    })

            def get_stats(self):
                """获取性能统计"""
                if not self.query_times:
                    return {}

                return {
                    'total_queries': len(self.query_times),
                    'avg_query_time': sum(self.query_times) / len(self.query_times),
                    'max_query_time': max(self.query_times),
                    'min_query_time': min(self.query_times),
                    'slow_queries_count': len(self.slow_queries),
                    'most_frequent_queries': dict(
                        sorted(self.query_frequency.items(), key=lambda x: x, reverse=True)[:10]
                    )
                }

        # 创建性能监控器实例
        perf_monitor = PerformanceMonitor()

        # SQLAlchemy事件监听器,用于监控查询性能
        @event.listens_for(Engine, "before_cursor_execute")
        def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
            """查询开始时间"""
            context._query_start_time = time.time()

        @event.listens_for(Engine, "after_cursor_execute")
        def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
            """查询结束时间"""
            total = time.time() - context._query_start_time
            perf_monitor.record_query(statement[:100], total)  # 只记录前100个字符

        class DatabaseOptimizer:
            """数据库优化器"""

            @staticmethod
            def create_optimized_indexes():
                """创建优化的索引"""

                # 用户表索引
                Index('idx_users_username_email', User.username, User.email, unique=True)
                Index('idx_users_is_active_verified', User.is_active, User.is_verified)
                Index('idx_users_created_at', User.created_at.desc())
                Index('idx_users_last_login', User.last_login.desc())

                # 文章表索引
                Index('idx_posts_author_category', Post.author_id, Post.category_id)
                Index('idx_posts_published_featured', Post.is_published, Post.is_featured)
                Index('idx_posts_created_at', Post.created_at.desc())
                Index('idx_posts_published_at', Post.published_at.desc())
                Index('idx_posts_title_content', Post.title, Post.content)  # 全文搜索索引

                # 评论表索引
                Index('idx_comments_post_approved', Comment.post_id, Comment.is_approved)
                Index('idx_comments_author_post', Comment.author_id, Comment.post_id)
                Index('idx_comments_created_at', Comment.created_at.desc())

                # 分类表索引
                Index('idx_categories_parent_active', Category.parent_id, Category.is_active)
                Index('idx_categories_slug', Category.slug, unique=True)

                # 标签表索引
                Index('idx_tags_name', Tag.name, unique=True)

                # 关联表索引
                Index('idx_user_roles_user_role', user_roles.c.user_id, user_roles.c.role_id)
                Index('idx_post_tags_post_tag', post_tags.c.post_id, post_tags.c.tag_id)

                print("Optimized indexes created successfully!")

            @staticmethod
            def analyze_query_performance():
                """分析查询性能"""
                slow_queries = perf_monitor.slow_queries

                analysis = {
                    'total_slow_queries': len(slow_queries),
                    'average_slow_query_time': sum(q['duration'] for q in slow_queries) / len(slow_queries) if slow_queries else 0,
                    'recommendations': []
                }

                # 分析慢查询模式
                query_patterns = defaultdict(list)
                for query in slow_queries:
                    # 提取查询类型(SELECT, INSERT, UPDATE, DELETE)
                    query_type = query['query'].strip().split()[0].upper()
                    query_patterns[query_type].append(query['duration'])

                # 生成优化建议
                for query_type, durations in query_patterns.items():
                    avg_duration = sum(durations) / len(durations)
                    if query_type == 'SELECT' and avg_duration > 2.0:
                        analysis['recommendations'].append(
                            f"Consider adding indexes for frequently queried columns in SELECT statements (avg: {avg_duration:.2f}s)"
                        )
                    elif query_type == 'INSERT' and avg_duration > 1.0:
                        analysis['recommendations'].append(
                            f"Consider batch INSERT operations for better performance (avg: {avg_duration:.2f}s)"
                        )
                    elif query_type == 'UPDATE' and avg_duration > 1.5:
                        analysis['recommendations'].append(
                            f"Check UPDATE WHERE conditions for proper indexing (avg: {avg_duration:.2f}s)"
                        )

                return analysis

            @staticmethod
            def get_table_statistics():
                """获取表统计信息"""
                stats = {}

                try:
                    with db.engine.connect() as conn:
                        # 获取所有表
                        result = conn.execute(text("""
                            SELECT table_name,
                                   (SELECT COUNT(*) FROM information_schema.columns WHERE table_name = t.table_name) as column_count
                            FROM information_schema.tables t
                            WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
                        """))

                        tables_info = {row[0]: row for row in result}

                        # 获取每个表的行数和大小
                        for table_name, column_count in tables_info.items():
                            result = conn.execute(text(f"""
                                SELECT
                                    COUNT(*) as row_count,
                                    pg_size_pretty(pg_total_relation_size('{table_name}')) as table_size
                                FROM {table_name}
                            """))

                            row = result.fetchone()
                            stats[table_name] = {
                                'row_count': row[0],
                                'table_size': row,
                                'column_count': column_count
                            }

                except Exception as e:
                    stats['error'] = str(e)

                return stats

            @staticmethod
            def optimize_database():
                """执行数据库优化操作"""
                try:
                    with db.engine.connect() as conn:
                        # 更新表统计信息
                        conn.execute(text("ANALYZE;"))

                        # 清理无用数据
                        conn.execute(text("VACUUM ANALYZE;"))

                        # 重建索引(如果需要)
                        conn.execute(text("REINDEX DATABASE;"))

                        return {'success': True, 'message': 'Database optimization completed'}

                except Exception as e:
                    return {'success': False, 'error': str(e)}

        class QueryOptimizer:
            """查询优化器"""

            @staticmethod
            def optimized_user_search(search_term, filters=None, limit=20, offset=0):
                """优化的用户搜索查询"""
                query = db.session.query(User).filter(User.is_active == True)

                # 使用全文搜索索引
                if search_term:
                    query = query.filter(
                        db.or_(
                            User.username.ilike(f'%{search_term}%'),
                            User.email.ilike(f'%{search_term}%'),
                            User.first_name.ilike(f'%{search_term}%'),
                            User.last_name.ilike(f'%{search_term}%')
                        )
                    )

                # 应用过滤器
                if filters:
                    if filters.get('role'):
                        query = query.join(User.roles).filter(Role.name == filters['role'])

                    if filters.get('is_verified') is not None:
                        query = query.filter(User.is_verified == filters['is_verified'])

                # 使用索引优化的排序
                query = query.order_by(User.created_at.desc())

                # 分页
                total = query.count()
                users = query.offset(offset).limit(limit).all()

                return {
                    'users': users,
                    'total': total,
                    'page': offset // limit + 1
                }

            @staticmethod
            def optimized_post_search(search_term, filters=None, limit=10, offset=0):
                """优化的文章搜索查询"""
                # 使用子查询优化
                base_query = db.session.query(Post).filter(
                    Post.is_active == True,
                    Post.is_published == True
                )

                # 全文搜索(使用PostgreSQL的全文搜索功能)
                if search_term:
                    base_query = base_query.filter(
                        db.or_(
                            Post.title.ilike(f'%{search_term}%'),
                            Post.content.ilike(f'%{search_term}%'),
                            Post.excerpt.ilike(f'%{search_term}%')
                        )
                    )

                # 应用过滤器
                if filters:
                    if filters.get('category_id'):
                        base_query = base_query.filter(Post.category_id == filters['category_id'])

                    if filters.get('author_id'):
                        base_query = base_query.filter(Post.author_id == filters['author_id'])

                    if filters.get('tag_ids'):
                        base_query = base_query.join(Post.tags).filter(Tag.id.in_(filters['tag_ids']))

                # 使用复合索引的排序
                base_query = base_query.order_by(
                    Post.is_featured.desc(),
                    Post.published_at.desc()
                )

                # 分页
                total = base_query.count()
                posts = base_query.offset(offset).limit(limit).all()

                return {
                    'posts': posts,
                    'total': total,
                    'page': offset // limit + 1
                }

            @staticmethod
            def get_popular_posts(days=30, limit=10):
                """获取热门文章(优化版本)"""
                from datetime import datetime, timedelta

                start_date = datetime.utcnow() - timedelta(days=days)

                # 使用窗口函数优化性能
                query = db.session.query(
                    Post,
                    db.func.count(Comment.id).label('comment_count'),
                    db.func.sum(Post.view_count).label('total_views')
                ).outerjoin(Comment).filter(
                    Post.is_active == True,
                    Post.is_published == True,
                    Post.published_at >= start_date
                ).group_by(Post.id).order_by(
                    db.func.count(Comment.id).desc(),
                    db.func.sum(Post.view_count).desc()
                ).limit(limit)

                return query.all()

        class CacheManager:
            """缓存管理器"""

            def __init__(self):
                self.cache = {}
                self.cache_stats = {
                    'hits': 0,
                    'misses': 0,
                    'total_requests': 0
                }

            def get(self, key):
                """获取缓存值"""
                self.cache_stats['total_requests'] += 1

                if key in self.cache:
                    item = self.cache[key]
                    # 检查是否过期
                    if time.time() < item['expires_at']:
                        self.cache_stats['hits'] += 1
                        return item['value']
                    else:
                        del self.cache[key]

                self.cache_stats['misses'] += 1
                return None

            def set(self, key, value, ttl=300):  # 默认5分钟TTL
                """设置缓存值"""
                self.cache[key] = {
                    'value': value,
                    'expires_at': time.time() + ttl
                }

            def invalidate(self, pattern=None):
                """清除缓存"""
                if pattern:
                    keys_to_remove = [key for key in self.cache.keys() if pattern in key]
                    for key in keys_to_remove:
                        del self.cache[key]
                else:
                    self.cache.clear()

            def get_stats(self):
                """获取缓存统计"""
                total = self.cache_stats['total_requests']
                hit_rate = (self.cache_stats['hits'] / total * 100) if total > 0 else 0

                return {
                    'cache_size': len(self.cache),
                    'hits': self.cache_stats['hits'],
                    'misses': self.cache_stats['misses'],
                    'hit_rate': f"{hit_rate:.2f}%"
                }

        # 创建缓存管理器实例
        cache_manager = CacheManager()

        # 优化的API路由
        @app.route('/api/users/search')
        def search_users():
            """优化的用户搜索API"""
            search_term = request.args.get('q', '')
            page = int(request.args.get('page', 1))
            per_page = min(int(request.args.get('per_page', 20)), 50)

            # 检查缓存
            cache_key = f"users_search_{search_term}_{page}_{per_page}"
            cached_result = cache_manager.get(cache_key)

            if cached_result:
                return jsonify({
                    'success': True,
                    'cached': True,
                    **cached_result
                })

            # 构建过滤器
            filters = {}
            if request.args.get('role'):
                filters['role'] = request.args.get('role')

            if request.args.get('is_verified'):
                filters['is_verified'] = request.args.get('is_verified').lower() == 'true'

            # 执行优化查询
            offset = (page - 1) * per_page
            result = QueryOptimizer.optimized_user_search(
                search_term, filters, per_page, offset
            )

            # 格式化结果
            response_data = {
                'users': [user.to_dict() for user in result['users']],
                'total': result['total'],
                'page': result['page'],
                'per_page': per_page
            }

            # 缓存结果
            cache_manager.set(cache_key, response_data, ttl=600)  # 10分钟缓存

            return jsonify({
                'success': True,
                'cached': False,
                **response_data
            })

        @app.route('/api/posts/search')
        def search_posts():
            """优化的文章搜索API"""
            search_term = request.args.get('q', '')
            page = int(request.args.get('page', 1))
            per_page = min(int(request.args.get('per_page', 10)), 50)

            # 检查缓存
            cache_key = f"posts_search_{search_term}_{page}_{per_page}"
            cached_result = cache_manager.get(cache_key)

            if cached_result:
                return jsonify({
                    'success': True,
                    'cached': True,
                    **cached_result
                })

            # 构建过滤器
            filters = {}
            if request.args.get('category_id'):
                filters['category_id'] = int(request.args.get('category_id'))

            if request.args.get('author_id'):
                filters['author_id'] = int(request.args.get('author_id'))

            if request.args.get('tag_ids'):
                tag_ids = request.args.get('tag_ids').split(',')
                filters['tag_ids'] = [int(tag_id) for tag_id in tag_ids if tag_id.isdigit()]

            # 执行优化查询
            offset = (page - 1) * per_page
            result = QueryOptimizer.optimized_post_search(
                search_term, filters, per_page, offset
            )

            # 格式化结果
            response_data = {
                'posts': [post.to_dict() for post in result['posts']],
                'total': result['total'],
                'page': result['page'],
                'per_page': per_page
            }

            # 缓存结果
            cache_manager.set(cache_key, response_data, ttl=300)  # 5分钟缓存

            return jsonify({
                'success': True,
                'cached': False,
                **response_data
            })

        @app.route('/api/posts/popular')
        def get_popular_posts():
            """获取热门文章API"""
            days = int(request.args.get('days', 30))
            limit = min(int(request.args.get('limit', 10)), 20)

            # 检查缓存
            cache_key = f"popular_posts_{days}_{limit}"
            cached_result = cache_manager.get(cache_key)

            if cached_result:
                return jsonify({
                    'success': True,
                    'cached': True,
                    'posts': cached_result
                })

            # 执行优化查询
            popular_posts_data = QueryOptimizer.get_popular_posts(days, limit)

            # 格式化结果
            posts_data = []
            for post, comment_count, total_views in popular_posts_data:
                post_data = post.to_dict()
                post_data['comment_count'] = comment_count or 0
                post_data['total_views'] = total_views or 0
                posts_data.append(post_data)

            # 缓存结果
            cache_manager.set(cache_key, posts_data, ttl=1800)  # 30分钟缓存

            return jsonify({
                'success': True,
                'cached': False,
                'posts': posts_data
            })

        # 性能监控API
        @app.route('/api/db/performance/stats')
        def get_performance_stats():
            """获取数据库性能统计"""
            return jsonify({
                'query_performance': perf_monitor.get_stats(),
                'cache_performance': cache_manager.get_stats(),
                'optimization_analysis': DatabaseOptimizer.analyze_query_performance()
            })

        @app.route('/api/db/performance/tables')
        def get_table_statistics():
            """获取表统计信息"""
            return jsonify({
                'tables': DatabaseOptimizer.get_table_statistics()
            })

        @app.route('/api/db/optimize', methods=['POST'])
        def optimize_database():
            """执行数据库优化"""
            result = DatabaseOptimizer.optimize_database()

            if result['success']:
                # 清除所有缓存
                cache_manager.invalidate()

            return jsonify(result)

        @app.route('/api/cache/clear', methods=['POST'])
        def clear_cache():
            """清除缓存"""
            data = request.get_json() or {}
            pattern = data.get('pattern')

            cache_manager.invalidate(pattern)

            return jsonify({
                'success': True,
                'message': 'Cache cleared successfully',
                'stats': cache_manager.get_stats()
            })

        # 创建优化的索引
        with app.app_context():
            DatabaseOptimizer.create_optimized_indexes()

        if __name__ == '__main__':
            app.run(debug=True)

6 表单处理和验证

6.1 Flask-WTF 基础

01.WTF 表单基础配置
    a.Flask-WTF 初始化设置
        a.基础 Flask-WTF 配置
            from flask import Flask, render_template, redirect, url_for, flash, jsonify, request
            from flask_wtf import FlaskForm
            from wtforms import StringField, TextAreaField, SelectField, BooleanField, IntegerField
            from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional
            from wtforms.widgets import TextArea, TextInput
            from wtforms.fields import SelectMultipleField, PasswordField, DateField, DateTimeField
            from wtforms.validators import ValidationError
            import secrets
            import os

            app = Flask(__name__)

            # Flask-WTF 安全配置
            def configure_wtf_security(app):
                """配置 Flask-WTF 安全设置"""
                # 生成安全的 CSRF 密钥
                app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', secrets.token_urlsafe(32))

                # CSRF 配置
                app.config['WTF_CSRF_ENABLED'] = True
                app.config['WTF_CSRF_TIME_LIMIT'] = 3600  # CSRF token 有效期 1小时
                app.config['WTF_CSRF_SSL_STRICT'] = True  # 仅在 HTTPS 下启用严格模式

                # 表单配置
                app.config['WTF_CSRF_FIELD_NAME'] = 'csrf_token'
                app.config['WTF_CSRF_HEADER_NAME'] = 'X-CSRFToken'

                # 安全头配置
                app.config['SECURE_HEADERS'] = {
                    'X-Frame-Options': 'DENY',
                    'X-Content-Type-Options': 'nosniff',
                    'X-XSS-Protection': '1; mode=block',
                    'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
                }

                # 文件上传配置
                app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB
                app.config['UPLOAD_FOLDER'] = 'uploads'
                app.config['ALLOWED_EXTENSIONS'] = {
                    'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'doc', 'docx'
                }

            configure_wtf_security(app)

            # 确保上传目录存在
            os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

            # 自定义 CSRF 验证
            class CustomCSRFProtect:
                """自定义 CSRF 保护"""

                def __init__(self, app=None):
                    self.app = app
                    if app is not None:
                        self.init_app(app)

                def init_app(self, app):
                    app.before_request(self._before_request)
                    app.after_request(self._after_request)

                def _before_request(self):
                    """请求前 CSRF 验证"""
                    # 跳过不需要 CSRF 验证的请求
                    if request.method in ['GET', 'HEAD', 'OPTIONS', 'TRACE']:
                        return

                    # API 请求可以使用 Header 中的 CSRF token
                    if request.path.startswith('/api/'):
                        csrf_token = request.headers.get('X-CSRFToken')
                        if not csrf_token:
                            csrf_token = request.form.get('csrf_token')
                    else:
                        csrf_token = request.form.get('csrf_token')

                    if not csrf_token:
                        return jsonify({'error': 'CSRF token missing'}), 400

                    # 验证 CSRF token
                    if not self._validate_csrf_token(csrf_token):
                        return jsonify({'error': 'CSRF token invalid'}), 400

                def _after_request(self, response):
                    """响应后添加 CSRF token"""
                    if request.path.startswith('/api/'):
                        # 为 API 响应添加 CSRF token
                        if hasattr(response, 'headers'):
                            response.headers['X-CSRFToken'] = self._generate_csrf_token()
                    return response

                def _validate_csrf_token(self, token):
                    """验证 CSRF token"""
                    # 简化的 token 验证逻辑
                    # 在实际应用中应该使用 session 和时间戳验证
                    return len(token) > 10  # 基本长度检查

                def _generate_csrf_token(self):
                    """生成 CSRF token"""
                    return secrets.token_urlsafe(32)

            # 初始化自定义 CSRF 保护
            csrf_protect = CustomCSRFProtect(app)

            # 基础表单类
            class BaseForm(FlaskForm):
                """基础表单类,包含通用功能和验证"""

                def __init__(self, *args, **kwargs):
                    super().__init__(*args, **kwargs)
                    self.add_error_messages()

                def add_error_messages(self):
                    """添加默认错误消息"""
                    for field_name, field in self._fields.items():
                        if not field.render_kw:
                            field.render_kw = {}

                        # 添加 Bootstrap CSS 类
                        if field_name != 'csrf_token':
                            current_class = field.render_kw.get('class', '')
                            field.render_kw['class'] = f"form-control {current_class}".strip()

                        # 添加占位符
                        if 'placeholder' not in field.render_kw and hasattr(field, 'label'):
                            field.render_kw['placeholder'] = field.label.text

                def get_errors_dict(self):
                    """获取错误字典"""
                    errors = {}
                    for field_name, field_errors in self.errors.items():
                        errors[field_name] = field_errors
                    return errors

                def get_field_error(self, field_name):
                    """获取特定字段的错误"""
                    return self.errors.get(field_name, [])

                def is_valid(self):
                    """检查表单是否有效"""
                    return self.validate_on_submit()

                def get_form_data(self):
                    """获取表单数据"""
                    data = {}
                    for field_name, field in self._fields.items():
                        if field_name != 'csrf_token':
                            data[field_name] = field.data
                    return data

            # 用户注册表单
            class RegistrationForm(BaseForm):
                """用户注册表单"""
                username = StringField(
                    '用户名',
                    validators=[
                        DataRequired(message='用户名不能为空'),
                        Length(min=3, max=20, message='用户名长度必须在3-20个字符之间'),
                        Regexp('^[a-zA-Z0-9_]+$', message='用户名只能包含字母、数字和下划线')
                    ],
                    render_kw={
                        'class': 'form-control',
                        'autocomplete': 'username'
                    }
                )

                email = StringField(
                    '邮箱地址',
                    validators=[
                        DataRequired(message='邮箱地址不能为空'),
                        Email(message='请输入有效的邮箱地址'),
                        Length(max=120, message='邮箱地址不能超过120个字符')
                    ],
                    render_kw={
                        'type': 'email',
                        'autocomplete': 'email'
                    }
                )

                password = PasswordField(
                    '密码',
                    validators=[
                        DataRequired(message='密码不能为空'),
                        Length(min=8, max=128, message='密码长度必须在8-128个字符之间'),
                        Regexp(
                            r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]',
                            message='密码必须包含大小写字母、数字和特殊字符'
                        )
                    ],
                    render_kw={
                        'autocomplete': 'new-password',
                        'minlength': '8'
                    }
                )

                confirm_password = PasswordField(
                    '确认密码',
                    validators=[
                        DataRequired(message='请确认密码'),
                    ]
                )

                first_name = StringField(
                    '名字',
                    validators=[
                        DataRequired(message='名字不能为空'),
                        Length(min=1, max=50, message='名字长度必须在1-50个字符之间')
                    ],
                    render_kw={
                        'autocomplete': 'given-name'
                    }
                )

                last_name = StringField(
                    '姓氏',
                    validators=[
                        DataRequired(message='姓氏不能为空'),
                        Length(min=1, max=50, message='姓氏长度必须在1-50个字符之间')
                    ],
                    render_kw={
                        'autocomplete': 'family-name'
                    }
                )

                bio = TextAreaField(
                    '个人简介',
                    validators=[
                        Optional(),
                        Length(max=500, message='个人简介不能超过500个字符')
                    ],
                    render_kw={
                        'rows': 4,
                        'placeholder': '请简单介绍一下自己...'
                    }
                )

                agree_terms = BooleanField(
                    '我同意服务条款和隐私政策',
                    validators=[
                        DataRequired(message='必须同意服务条款才能注册')
                    ]
                )

                newsletter = BooleanField(
                    '订阅新闻通讯',
                    default=False
                )

                def validate_confirm_password(self, field):
                    """验证确认密码"""
                    if field.data != self.password.data:
                        raise ValidationError('两次输入的密码不一致')

                def validate_username(self, field):
                    """验证用户名唯一性"""
                    from models import User
                    if User.query.filter_by(username=field.data).first():
                        raise ValidationError('用户名已存在')

                def validate_email(self, field):
                    """验证邮箱唯一性"""
                    from models import User
                    if User.query.filter_by(email=field.data).first():
                        raise ValidationError('邮箱地址已被注册')

            # 用户登录表单
            class LoginForm(BaseForm):
                """用户登录表单"""
                username = StringField(
                    '用户名或邮箱',
                    validators=[
                        DataRequired(message='请输入用户名或邮箱'),
                        Length(min=3, max=120, message='用户名或邮箱长度不正确')
                    ],
                    render_kw={
                        'autocomplete': 'username',
                        'autofocus': True
                    }
                )

                password = PasswordField(
                    '密码',
                    validators=[
                        DataRequired(message='请输入密码')
                    ],
                    render_kw={
                        'autocomplete': 'current-password'
                    }
                )

                remember_me = BooleanField(
                    '记住我',
                    default=False
                )

                # 添加自定义验证
                def validate(self, extra_validators=None):
                    """验证用户凭据"""
                    if not super().validate(extra_validators):
                        return False

                    from models import User
                    user = User.query.filter(
                        (User.username == self.username.data) | (User.email == self.username.data)
                    ).first()

                    if not user or not user.check_password(self.password.data):
                        self.username.errors.append('用户名或密码错误')
                        self.password.errors.append('用户名或密码错误')
                        return False

                    if not user.is_active:
                        self.username.errors.append('账户已被禁用')
                        return False

                    if not user.is_verified:
                        self.username.errors.append('账户未验证,请检查邮箱')
                        return False

                    self.user = user  # 保存用户对象供后续使用
                    return True

            # 文章创建表单
            class PostForm(BaseForm):
                """文章创建表单"""
                title = StringField(
                    '文章标题',
                    validators=[
                        DataRequired(message='文章标题不能为空'),
                        Length(min=5, max=200, message='文章标题长度必须在5-200个字符之间')
                    ],
                    render_kw={
                        'placeholder': '请输入文章标题...',
                        'autofocus': True
                    }
                )

                content = TextAreaField(
                    '文章内容',
                    validators=[
                        DataRequired(message='文章内容不能为空'),
                        Length(min=50, message='文章内容至少需要50个字符')
                    ],
                    render_kw={
                        'rows': 15,
                        'placeholder': '请输入文章内容...',
                        'class': 'form-control content-editor'
                    }
                )

                excerpt = TextAreaField(
                    '文章摘要',
                    validators=[
                        Optional(),
                        Length(max=500, message='文章摘要不能超过500个字符')
                    ],
                    render_kw={
                        'rows': 3,
                        'placeholder': '请输入文章摘要(可选)...'
                    }
                )

                category_id = SelectField(
                    '文章分类',
                    coerce=int,
                    validators=[
                        DataRequired(message='请选择文章分类')
                    ]
                )

                tags = SelectMultipleField(
                    '文章标签',
                    coerce=int,
                    validators=[
                        Optional()
                    ]
                )

                is_published = BooleanField(
                    '立即发布',
                    default=False
                )

                is_featured = BooleanField(
                    '设为精选文章',
                    default=False
                )

                meta_title = StringField(
                    'SEO标题',
                    validators=[
                        Optional(),
                        Length(max=60, message='SEO标题不能超过60个字符')
                    ],
                    render_kw={
                        'placeholder': 'SEO优化标题(可选)...'
                    }
                )

                meta_description = StringField(
                    'SEO描述',
                    validators=[
                        Optional(),
                        Length(max=160, message='SEO描述不能超过160个字符')
                    ],
                    render_kw={
                        'placeholder': 'SEO优化描述(可选)...'
                    }
                )

                meta_keywords = StringField(
                    'SEO关键词',
                    validators=[
                        Optional(),
                        Length(max=255, message='SEO关键词不能超过255个字符')
                    ],
                    render_kw={
                        'placeholder': '关键词,用逗号分隔(可选)...'
                    }
                )

                def __init__(self, *args, **kwargs):
                    super().__init__(*args, **kwargs)
                    self._setup_category_choices()
                    self._setup_tag_choices()

                def _setup_category_choices(self):
                    """设置分类选择"""
                    from models import Category
                    categories = Category.query.filter_by(is_active=True).order_by(Category.name).all()
                    self.category_id.choices = [(0, '请选择分类')] + [
                        (cat.id, cat.name) for cat in categories
                    ]

                def _setup_tag_choices(self):
                    """设置标签选择"""
                    from models import Tag
                    tags = Tag.query.filter_by(is_active=True).order_by(Tag.name).all()
                    self.tags.choices = [(tag.id, tag.name) for tag in tags]

                def validate_content(self, field):
                    """验证内容"""
                    if field.data:
                        # 检查内容是否包含恶意脚本
                        import re
                        script_pattern = r'<script[^>]*>.*?</script>'
                        if re.search(script_pattern, field.data, re.IGNORECASE | re.DOTALL):
                            raise ValidationError('内容不能包含脚本代码')

                def validate_title(self, field):
                    """验证标题"""
                    if field.data:
                        # 检查标题是否包含敏感词
                        sensitive_words = ['违法', '暴力', '色情', '赌博']
                        for word in sensitive_words:
                            if word in field.data:
                                raise ValidationError(f'标题不能包含敏感词汇: {word}')

            # 评论表单
            class CommentForm(BaseForm):
                """评论表单"""
                content = TextAreaField(
                    '评论内容',
                    validators=[
                        DataRequired(message='评论内容不能为空'),
                        Length(min=5, max=1000, message='评论内容长度必须在5-1000个字符之间')
                    ],
                    render_kw={
                        'rows': 4,
                        'placeholder': '请输入您的评论...',
                        'class': 'form-control'
                    }
                )

                author_name = StringField(
                    '姓名',
                    validators=[
                        DataRequired(message='姓名不能为空'),
                        Length(min=2, max=50, message='姓名长度必须在2-50个字符之间')
                    ],
                    render_kw={
                        'placeholder': '请输入您的姓名...'
                    }
                )

                author_email = StringField(
                    '邮箱',
                    validators=[
                        DataRequired(message='邮箱不能为空'),
                        Email(message='请输入有效的邮箱地址')
                    ],
                    render_kw={
                        'type': 'email',
                        'placeholder': '请输入您的邮箱地址...'
                    }
                )

                website = StringField(
                    '网站',
                    validators=[
                        Optional(),
                        Length(max=200, message='网站地址不能超过200个字符')
                    ],
                    render_kw={
                        'type': 'url',
                        'placeholder': '您的网站地址(可选)...'
                    }
                )

                parent_id = IntegerField(
                    '父评论ID',
                    validators=[Optional()]
                )

                def validate_website(self, field):
                    """验证网站地址"""
                    if field.data:
                        import re
                        url_pattern = r'^https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:\w*))?)?$'
                        if not re.match(url_pattern, field.data):
                            raise ValidationError('请输入有效的网站地址')

                def validate_content(self, field):
                    """验证评论内容"""
                    if field.data:
                        # 检查是否包含垃圾信息关键词
                        spam_keywords = ['广告', '推广', '赚钱', '免费', '优惠', '折扣']
                        content_lower = field.data.lower()
                        spam_count = sum(1 for keyword in spam_keywords if keyword in content_lower)

                        if spam_count > 2:
                            raise ValidationError('评论内容看起来像垃圾信息')

                        # 检查是否全是英文(可能是垃圾评论)
                        if all(ord(char) < 128 for char in field.data.replace(' ', '')):
                            # 简单的英文垃圾评论检测
                            if len(field.data.split()) < 5:
                                raise ValidationError('评论内容过于简单,请提供更有意义的内容')

            # 搜索表单
            class SearchForm(BaseForm):
                """搜索表单"""
                query = StringField(
                    '搜索关键词',
                    validators=[
                        DataRequired(message='请输入搜索关键词'),
                        Length(min=2, max=100, message='搜索关键词长度必须在2-100个字符之间')
                    ],
                    render_kw={
                        'placeholder': '搜索文章、用户、评论...',
                        'autocomplete': 'off',
                        'class': 'form-control form-control-lg'
                    }
                )

                search_type = SelectField(
                    '搜索类型',
                    choices=[
                        ('all', '全部'),
                        ('posts', '文章'),
                        ('users', '用户'),
                        ('comments', '评论')
                    ],
                    default='all',
                    coerce=str
                )

                category_id = SelectField(
                    '分类筛选',
                    coerce=int,
                    validators=[Optional()]
                )

                date_from = DateField(
                    '开始日期',
                    validators=[Optional()]
                )

                date_to = DateField(
                    '结束日期',
                    validators=[Optional()]
                )

                sort_by = SelectField(
                    '排序方式',
                    choices=[
                        ('relevance', '相关性'),
                        ('date', '发布时间'),
                        'popularity', '热度'
                    ],
                    default='relevance',
                    coerce=str
                )

                def __init__(self, *args, **kwargs):
                    super().__init__(*args, **kwargs)
                    self._setup_category_choices()

                def _setup_category_choices(self):
                    """设置分类选择"""
                    from models import Category
                    categories = Category.query.filter_by(is_active=True).order_by(Category.name).all()
                    self.category_id.choices = [(0, '全部分类')] + [
                        (cat.id, cat.name) for cat in categories
                    ]

                def validate_date_to(self, field):
                    """验证结束日期"""
                    if field.data and self.date_from.data:
                        if field.data < self.date_from.data:
                            raise ValidationError('结束日期不能早于开始日期')

            # 文件上传表单
            class FileUploadForm(BaseForm):
                """文件上传表单"""
                file = TextAreaField(
                    '选择文件',
                    validators=[
                        DataRequired(message='请选择要上传的文件')
                    ]
                )

                description = TextAreaField(
                    '文件描述',
                    validators=[
                        Optional(),
                        Length(max=500, message='文件描述不能超过500个字符')
                    ],
                    render_kw={
                        'rows': 3,
                        'placeholder': '请输入文件描述(可选)...'
                    }
                )

                is_public = BooleanField(
                    '公开文件',
                    default=False
                )

                def validate_file(self, field):
                    """验证上传文件"""
                    if 'file' not in request.files:
                        raise ValidationError('请选择要上传的文件')

                    file = request.files['file']

                    if file.filename == '':
                        raise ValidationError('请选择有效的文件')

                    # 检查文件扩展名
                    allowed_extensions = app.config['ALLOWED_EXTENSIONS']
                    file_ext = '.' + file.filename.rsplit('.', 1).lower() if '.' in file.filename else ''

                    if file_ext not in allowed_extensions:
                        raise ValidationError(f'不支持的文件类型。支持的类型: {", ".join(allowed_extensions)}')

                    # 检查文件大小
                    if hasattr(file, 'content_length'):
                        max_size = app.config['MAX_CONTENT_LENGTH']
                        if file.content_length > max_size:
                            raise ValidationError(f'文件大小不能超过 {max_size // (1024*1024)}MB')

            # 联系表单
            class ContactForm(BaseForm):
                """联系表单"""
                name = StringField(
                    '姓名',
                    validators=[
                        DataRequired(message='姓名不能为空'),
                        Length(min=2, max=50, message='姓名长度必须在2-50个字符之间')
                    ],
                    render_kw={
                        'placeholder': '请输入您的姓名'
                    }
                )

                email = StringField(
                    '邮箱地址',
                    validators=[
                        DataRequired(message='邮箱地址不能为空'),
                        Email(message='请输入有效的邮箱地址')
                    ],
                    render_kw={
                        'type': 'email',
                        'placeholder': '请输入您的邮箱地址'
                    }
                )

                subject = StringField(
                    '主题',
                    validators=[
                        DataRequired(message='主题不能为空'),
                        Length(min=5, max=200, message='主题长度必须在5-200个字符之间')
                    ],
                    render_kw={
                        'placeholder': '请输入消息主题'
                    }
                )

                message = TextAreaField(
                    '消息内容',
                    validators=[
                        DataRequired(message='消息内容不能为空'),
                        Length(min=20, max=2000, message='消息内容长度必须在20-2000个字符之间')
                    ],
                    render_kw={
                        'rows': 6,
                        'placeholder': '请输入您的消息内容...'
                    }
                )

                contact_method = SelectField(
                    '联系方式',
                    choices=[
                        ('email', '邮箱回复'),
                        ('phone', '电话回复'),
                        ('wechat', '微信回复')
                    ],
                    default='email',
                    coerce=str
                )

                phone = StringField(
                    '联系电话',
                    validators=[
                        Optional(),
                        Length(min=10, max=20, message='联系电话格式不正确')
                    ],
                    render_kw={
                        'placeholder': '请输入您的联系电话(可选)'
                    }
                )

                urgent = BooleanField(
                    '紧急消息',
                    default=False
                )

                def validate_phone(self, field):
                    """验证电话号码"""
                    if field.data:
                        import re
                        phone_pattern = r'^[\d\s\-\+\(\)]+$'
                        if not re.match(phone_pattern, field.data):
                            raise ValidationError('请输入有效的电话号码')

                def validate_message(self, field):
                    """验证消息内容"""
                    if field.data:
                        # 检查是否包含敏感信息
                        sensitive_patterns = [
                            r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b',  # 信用卡号
                            r'\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b',          # 社会保险号
                            r'password\s*[:=]\s*\S+',                      # 密码
                        ]

                        for pattern in sensitive_patterns:
                            if re.search(pattern, field.data, re.IGNORECASE):
                                raise ValidationError('消息内容包含敏感信息,请修改后重试')

            # 密码重置表单
            class PasswordResetForm(BaseForm):
                """密码重置表单"""
                email = StringField(
                    '邮箱地址',
                    validators=[
                        DataRequired(message='邮箱地址不能为空'),
                        Email(message='请输入有效的邮箱地址')
                    ],
                    render_kw={
                        'type': 'email',
                        'placeholder': '请输入注册时使用的邮箱地址'
                    }
                )

                def validate_email(self, field):
                    """验证邮箱是否存在"""
                    from models import User
                    user = User.query.filter_by(email=field.data).first()
                    if not user:
                        raise ValidationError('该邮箱地址未注册')

                    self.user = user  # 保存用户对象

            # 新密码设置表单
            class SetPasswordForm(BaseForm):
                """设置新密码表单"""
                password = PasswordField(
                    '新密码',
                    validators=[
                        DataRequired(message='新密码不能为空'),
                        Length(min=8, max=128, message='密码长度必须在8-128个字符之间'),
                        Regexp(
                            r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]',
                            message='密码必须包含大小写字母、数字和特殊字符'
                        )
                    ],
                    render_kw={
                        'placeholder': '请输入新密码'
                    }
                )

                confirm_password = PasswordField(
                    '确认新密码',
                    validators=[
                        DataRequired(message='请确认新密码'),
                    ],
                    render_kw={
                        'placeholder': '请再次输入新密码'
                    }
                )

                def validate_confirm_password(self, field):
                    """验证确认密码"""
                    if field.data != self.password.data:
                        raise ValidationError('两次输入的密码不一致')

            # 用户资料编辑表单
            class ProfileForm(BaseForm):
                """用户资料编辑表单"""
                first_name = StringField(
                    '名字',
                    validators=[
                        DataRequired(message='名字不能为空'),
                        Length(min=1, max=50, message='名字长度必须在1-50个字符之间')
                    ]
                )

                last_name = StringField(
                    '姓氏',
                    validators=[
                        DataRequired(message='姓氏不能为空'),
                        Length(min=1, max=50, message='姓氏长度必须在1-50个字符之间')
                    ]
                )

                bio = TextAreaField(
                    '个人简介',
                    validators=[
                        Optional(),
                        Length(max=500, message='个人简介不能超过500个字符')
                    ],
                    render_kw={
                        'rows': 4,
                        'placeholder': '请介绍一下您自己...'
                    }
                )

                website = StringField(
                    '个人网站',
                    validators=[
                        Optional(),
                        Length(max=200, message='网站地址不能超过200个字符')
                    ],
                    render_kw={
                        'type': 'url',
                        'placeholder': 'https://yourwebsite.com'
                    }
                )

                location = StringField(
                    '所在地',
                    validators=[
                        Optional(),
                        Length(max=100, message='所在地不能超过100个字符')
                    ],
                    render_kw={
                        'placeholder': '城市,国家'
                    }
                )

                avatar = TextAreaField(
                    '头像上传'
                )

                def validate_website(self, field):
                    """验证网站地址"""
                    if field.data:
                        import re
                        url_pattern = r'^https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:\w*))?)?$'
                        if not re.match(url_pattern, field.data):
                            raise ValidationError('请输入有效的网站地址')

            @app.route('/forms/demo')
            def forms_demo():
                """表单演示页面"""
                return render_template('forms/demo.html')

            @app.route('/forms/register', methods=['GET', 'POST'])
            def register():
                """用户注册"""
                form = RegistrationForm()

                if form.validate_on_submit():
                    # 处理注册逻辑
                    flash('注册成功!', 'success')
                    return redirect(url_for('login'))

                return render_template('forms/register.html', form=form)

            @app.route('/forms/login', methods=['GET', 'POST'])
            def login():
                """用户登录"""
                form = LoginForm()

                if form.validate_on_submit():
                    # 处理登录逻辑
                    flash(f'欢迎回来,{form.user.username}!', 'success')
                    return redirect(url_for('dashboard'))

                return render_template('forms/login.html', form=form)

            @app.route('/forms/contact', methods=['GET', 'POST'])
            def contact():
                """联系表单"""
                form = ContactForm()

                if form.validate_on_submit():
                    # 处理联系逻辑
                    flash('消息发送成功!我们会尽快回复您。', 'success')
                    return redirect(url_for('contact'))

                return render_template('forms/contact.html', form=form)

            if __name__ == '__main__':
                app.run(debug=True)

6.2 表单验证和自定义验证器

01.内置验证器使用
    a.常用验证器组合
        a.基础验证器示例
            from flask import Flask, render_template, request, flash, redirect, url_for, jsonify
            from flask_wtf import FlaskForm
            from wtforms import StringField, TextAreaField, SelectField, BooleanField, IntegerField, FloatField, DateField
            from wtforms.validators import (
                DataRequired, Length, Email, NumberRange, Optional, URL, UUID,
                IPAddress, MacAddress, Regexp, EqualTo, InputRequired, AnyOf, NoneOf
            )
            from wtforms.widgets import TextArea, TextInput
            import re
            from datetime import datetime, date

            app = Flask(__name__)
            app.config['SECRET_KEY'] = 'your-secret-key-here'

            # 完整的用户资料表单
            class UserProfileForm(FlaskForm):
                """用户资料表单,展示各种验证器"""

                # 基础输入验证
                username = StringField(
                    '用户名',
                    validators=[
                        InputRequired(message='用户名是必填项'),
                        Length(min=3, max=20, message='用户名长度必须在3-20个字符之间'),
                        Regexp(
                            r'^[a-zA-Z0-9_]+$',
                            message='用户名只能包含字母、数字和下划线'
                        )
                    ]
                )

                # 邮箱验证
                email = StringField(
                    '邮箱地址',
                    validators=[
                        DataRequired(message='邮箱地址不能为空'),
                        Email(message='请输入有效的邮箱地址'),
                        Length(max=120, message='邮箱地址不能超过120个字符')
                    ]
                )

                # 电话号码验证
                phone = StringField(
                    '电话号码',
                    validators=[
                        Optional(),
                        Regexp(
                            r'^\+?1?-?\.?\s?\(?(\d{3})\)?[\s.-]?(\d{3})[\s.-]?(\d{4})$',
                            message='请输入有效的电话号码格式'
                        )
                    ]
                )

                # 年龄验证
                age = IntegerField(
                    '年龄',
                    validators=[
                        NumberRange(min=13, max=120, message='年龄必须在13-120之间')
                    ]
                )

                # 身高验证(浮点数)
                height = FloatField(
                    '身高(cm)',
                    validators=[
                        Optional(),
                        NumberRange(min=50.0, max=250.0, message='身高必须在50-250cm之间')
                    ]
                )

                # 体重验证
                weight = FloatField(
                    '体重(kg)',
                    validators=[
                        Optional(),
                        NumberRange(min=20.0, max=300.0, message='体重必须在20-300kg之间')
                    ]
                )

                # 个人网站验证
                website = StringField(
                    '个人网站',
                    validators=[
                        Optional(),
                        URL(message='请输入有效的URL地址'),
                        Length(max=200, message='网站地址不能超过200个字符')
                    ]
                )

                # IP地址验证
                ip_address = StringField(
                    'IP地址',
                    validators=[
                        Optional(),
                        IPAddress(message='请输入有效的IP地址')
                    ]
                )

                # MAC地址验证
                mac_address = StringField(
                    'MAC地址',
                    validators=[
                        Optional(),
                        MacAddress(message='请输入有效的MAC地址')
                    ]
                )

                # UUID验证
                user_uuid = StringField(
                    '用户UUID',
                    validators=[
                        Optional(),
                        UUID(message='请输入有效的UUID格式')
                    ]
                )

                # 性别选择验证
                gender = SelectField(
                    '性别',
                    choices=[
                        ('', '请选择性别'),
                        ('male', '男'),
                        ('female', '女'),
                        ('other', '其他')
                    ],
                    validators=[
                        AnyOf(['male', 'female', 'other'], message='请选择有效的性别')
                    ],
                    coerce=lambda x: x if x else None
                )

                # 国家验证
                country = SelectField(
                    '国家',
                    choices=[
                        ('', '请选择国家'),
                        ('CN', '中国'),
                        ('US', '美国'),
                        ('UK', '英国'),
                        ('JP', '日本'),
                        ('KR', '韩国')
                    ],
                    validators=[
                        AnyOf(['CN', 'US', 'UK', 'JP', 'KR'], message='请选择有效的国家')
                    ],
                    coerce=lambda x: x if x else None
                )

                # 个人简介验证
                bio = TextAreaField(
                    '个人简介',
                    validators=[
                        Optional(),
                        Length(min=10, max=500, message='个人简介长度必须在10-500个字符之间')
                    ]
                )

                # 技能验证(多选)
                skills = SelectMultipleField(
                    '技能',
                    choices=[
                        ('python', 'Python'),
                        ('javascript', 'JavaScript'),
                        ('java', 'Java'),
                        ('cpp', 'C++'),
                        ('go', 'Go'),
                        ('rust', 'Rust')
                    ],
                    validators=[
                        Optional(),
                        AnyOf(['python', 'javascript', 'java', 'cpp', 'go', 'rust'],
                             message='请选择有效的技能')
                    ],
                    coerce=str
                )

                # 同意条款验证
                agree_terms = BooleanField(
                    '我同意用户协议和隐私政策',
                    validators=[
                        DataRequired(message='必须同意用户协议才能继续')
                    ]
                )

                # 验证年龄与出生日期的一致性
                def validate_age(self, field):
                    """验证年龄的合理性"""
                    if field.data and field.data < 13:
                        raise ValidationError('年龄必须大于13岁')

                # 验证身高体重的比例
                def validate_weight(self, field):
                    """验证身高体重的合理性"""
                    if self.height.data and field.data:
                        bmi = field.data / ((self.height.data / 100) ** 2)
                        if bmi > 50:  # BMI > 50 可能数据有误
                            raise ValidationError('请检查身高体重数据的合理性')

            # 产品订单表单
            class ProductOrderForm(FlaskForm):
                """产品订单表单"""

                # 订单信息验证
                order_id = StringField(
                    '订单编号',
                    validators=[
                        DataRequired(message='订单编号不能为空'),
                        Regexp(
                            r'^[A-Z]{2}\d{8}$',
                            message='订单编号格式:2个大写字母+8个数字'
                        )
                    ]
                )

                product_name = StringField(
                    '产品名称',
                    validators=[
                        DataRequired(message='产品名称不能为空'),
                        Length(min=2, max=100, message='产品名称长度必须在2-100个字符之间'),
                        Regexp(
                            r'^[a-zA-Z0-9\s\u4e00-\u9fa5\-_]+$',
                            message='产品名称只能包含中英文、数字、空格、横线和下划线'
                        )
                    ]
                )

                quantity = IntegerField(
                    '数量',
                    validators=[
                        DataRequired(message='数量不能为空'),
                        NumberRange(min=1, max=1000, message='数量必须在1-1000之间')
                    ]
                )

                unit_price = FloatField(
                    '单价',
                    validators=[
                        DataRequired(message='单价不能为空'),
                        NumberRange(min=0.01, max=100000.00, message='单价必须在0.01-100000之间')
                    ]
                )

                # 订单验证
                def validate_quantity(self, field):
                    """验证订单数量的合理性"""
                    if self.product_name.data and field.data:
                        # 特殊产品数量限制
                        limited_products = ['限量版', '收藏版', '纪念版']
                        for product in limited_products:
                            if product in self.product_name.data and field.data > 1:
                                raise ValidationError(f'{product}产品限购1件')

            # 企业注册表单
            class CompanyRegistrationForm(FlaskForm):
                """企业注册表单"""

                # 企业信息验证
                company_name = StringField(
                    '企业名称',
                    validators=[
                        DataRequired(message='企业名称不能为空'),
                        Length(min=5, max=100, message='企业名称长度必须在5-100个字符之间'),
                        Regexp(
                            r'^[\u4e00-\u9fa5a-zA-Z0-9\s\(\)\)\-_]+$',
                            message='企业名称包含无效字符'
                        )
                    ]
                )

                # 统一社会信用代码验证
                credit_code = StringField(
                    '统一社会信用代码',
                    validators=[
                        DataRequired(message='统一社会信用代码不能为空'),
                        Regexp(
                            r'^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$',
                            message='请输入有效的18位统一社会信用代码'
                        )
                    ]
                )

                # 法定代表人验证
                legal_representative = StringField(
                    '法定代表人',
                    validators=[
                        DataRequired(message='法定代表人不能为空'),
                        Length(min=2, max=20, message='法定代表人姓名长度必须在2-20个字符之间'),
                        Regexp(
                            r'^[\u4e00-\u9fa5a-zA-Z\s]+$',
                            message='法定代表人姓名只能包含中英文字符和空格'
                        )
                    ]
                )

                # 注册资本验证
                registered_capital = FloatField(
                    '注册资本(万元)',
                    validators=[
                        DataRequired(message='注册资本不能为空'),
                        NumberRange(min=1.0, max=1000000.0, message='注册资本必须在1-1000000万元之间')
                    ]
                )

                # 员工人数验证
                employee_count = IntegerField(
                    '员工人数',
                    validators=[
                        DataRequired(message='员工人数不能为空'),
                        NumberRange(min=1, max=100000, message='员工人数必须在1-100000之间')
                    ]
                )

                # 成立日期验证
                establishment_date = DateField(
                    '成立日期',
                    validators=[
                        DataRequired(message='成立日期不能为空')
                    ]
                )

                # 企业类型验证
                company_type = SelectField(
                    '企业类型',
                    choices=[
                        ('', '请选择企业类型'),
                        ('limited', '有限责任公司'),
                        ('stock', '股份有限公司'),
                        ('partnership', '合伙企业'),
                        ('individual', '个人独资企业')
                    ],
                    validators=[
                        AnyOf(['limited', 'stock', 'partnership', 'individual'],
                             message='请选择有效的企业类型')
                    ],
                    coerce=lambda x: x if x else None
                )

                # 行业验证
                industry = SelectField(
                    '所属行业',
                    choices=[
                        ('', '请选择行业'),
                        ('tech', '信息技术'),
                        ('manufacturing', '制造业'),
                        ('finance', '金融业'),
                        ('retail', '零售业'),
                        ('healthcare', '医疗健康'),
                        ('education', '教育培训'),
                        ('real_estate', '房地产')
                    ],
                    validators=[
                        AnyOf(['tech', 'manufacturing', 'finance', 'retail',
                               'healthcare', 'education', 'real_estate'],
                             message='请选择有效的行业')
                    ],
                    coerce=lambda x: x if x else None
                )

                # 营业执照验证
                business_license = StringField(
                    '营业执照注册号',
                    validators=[
                        DataRequired(message='营业执照注册号不能为空'),
                        Regexp(
                            r'^\d{15}$',
                            message='营业执照注册号必须是15位数字'
                        )
                    ]
                )

                def validate_establishment_date(self, field):
                    """验证成立日期的合理性"""
                    if field.data:
                        today = date.today()
                        min_date = date(1950, 1, 1)

                        if field.data > today:
                            raise ValidationError('成立日期不能是未来日期')

                        if field.data < min_date:
                            raise ValidationError('成立日期不能早于1950年')

                def validate_registered_capital(self, field):
                    """验证注册资本与企业类型的关系"""
                    if field.data and self.company_type.data:
                        if self.company_type.data == 'stock' and field.data < 500:
                            raise ValidationError('股份有限公司注册资本不能少于500万元')

            # 考试报名表单
            class ExamRegistrationForm(FlaskForm):
                """考试报名表单"""

                # 考生信息验证
                exam_name = StringField(
                    '考试名称',
                    validators=[
                        DataRequired(message='考试名称不能为空'),
                        Length(min=2, max=50, message='考试名称长度必须在2-50个字符之间')
                    ]
                )

                candidate_name = StringField(
                    '考生姓名',
                    validators=[
                        DataRequired(message='考生姓名不能为空'),
                        Length(min=2, max=20, message='姓名长度必须在2-20个字符之间'),
                        Regexp(
                            r'^[\u4e00-\u9fa5a-zA-Z\s]+$',
                            message='姓名只能包含中英文字符和空格'
                        )
                    ]
                )

                # 身份证号验证
                id_card = StringField(
                    '身份证号码',
                    validators=[
                        DataRequired(message='身份证号码不能为空'),
                        Regexp(
                            r'^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$',
                            message='请输入有效的18位身份证号码'
                        )
                    ]
                )

                # 准考证号验证
                exam_number = StringField(
                    '准考证号',
                    validators=[
                        DataRequired(message='准考证号不能为空'),
                        Regexp(
                            r'^\d{4}\d{2}\d{2}\d{3}\d{2}$',
                            message='准考证号格式不正确'
                        )
                    ]
                )

                # 考试日期验证
                exam_date = DateField(
                    '考试日期',
                    validators=[
                        DataRequired(message='考试日期不能为空')
                    ]
                )

                # 考场验证
                exam_room = StringField(
                    '考场',
                    validators=[
                        DataRequired(message='考场不能为空'),
                        Regexp(
                            r'^[A-Z]\d{3}$',
                            message='考场格式:1个大写字母+3个数字'
                        )
                    ]
                )

                # 座位号验证
                seat_number = IntegerField(
                    '座位号',
                    validators=[
                        DataRequired(message='座位号不能为空'),
                        NumberRange(min=1, max=100, message='座位号必须在1-100之间')
                    ]
                )

                def validate_exam_date(self, field):
                    """验证考试日期"""
                    if field.data:
                        today = date.today()

                        if field.data < today:
                            raise ValidationError('考试日期不能是过去的日期')

                        # 考试日期不能超过一年后
                        max_date = date(today.year + 1, today.month, today.day)
                        if field.data > max_date:
                            raise ValidationError('考试日期不能超过一年后')

            # API 密钥生成表单
            class APIKeyForm(FlaskForm):
                """API密钥管理表单"""

                key_name = StringField(
                    '密钥名称',
                    validators=[
                        DataRequired(message='密钥名称不能为空'),
                        Length(min=3, max=50, message='密钥名称长度必须在3-50个字符之间'),
                        Regexp(
                            r'^[a-zA-Z0-9_-]+$',
                            message='密钥名称只能包含字母、数字、下划线和横线'
                        )
                    ]
                )

                permissions = SelectMultipleField(
                    '权限范围',
                    choices=[
                        ('read', '读取权限'),
                        ('write', '写入权限'),
                        ('delete', '删除权限'),
                        ('admin', '管理权限')
                    ],
                    validators=[
                        AnyOf(['read', 'write', 'delete', 'admin'],
                             message='请选择有效的权限')
                    ],
                    coerce=str
                )

                rate_limit = IntegerField(
                    '请求限制(次/分钟)',
                    validators=[
                        NumberRange(min=1, max=10000, message='请求限制必须在1-10000之间')
                    ]
                )

                expiry_days = IntegerField(
                    '有效期(天)',
                    validators=[
                        Optional(),
                        NumberRange(min=1, max=365, message='有效期必须在1-365天之间')
                    ]
                )

                def validate_permissions(self, field):
                    """验证权限组合的合理性"""
                    if 'admin' in field.data and len(field.data) > 1:
                        raise ValidationError('管理员权限不能与其他权限同时选择')

02.自定义验证器
    a.业务逻辑验证器
        a.复杂业务验证器实现
            from flask import Flask, render_template, request, flash, redirect, url_for, jsonify
            from flask_wtf import FlaskForm
            from wtforms import StringField, TextAreaField, SelectField, IntegerField, FloatField, BooleanField, DateField
            from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional, ValidationError
            from wtforms.widgets import TextArea
            import re
            import hashlib
            from datetime import datetime, date, timedelta

            app = Flask(__name__)
            app.config['SECRET_KEY'] = 'your-secret-key-here'

            # 自定义验证器函数
            class CustomValidators:
                """自定义验证器集合"""

                @staticmethod
                def validate_password_strength(form, field):
                    """密码强度验证器"""
                    password = field.data
                    if not password:
                        return

                    # 检查长度
                    if len(password) < 8:
                        raise ValidationError('密码长度至少8位')

                    # 检查字符类型
                    has_upper = bool(re.search(r'[A-Z]', password))
                    has_lower = bool(re.search(r'[a-z]', password))
                    has_digit = bool(re.search(r'\d', password))
                    has_special = bool(re.search(r'[!@#$%^&*(),.?":{}|<>]', password))

                    if not (has_upper and has_lower and has_digit and has_special):
                        raise ValidationError('密码必须包含大小写字母、数字和特殊字符')

                    # 检查常见弱密码
                    weak_passwords = [
                        'password', '12345678', 'qwerty123', 'admin123',
                        'password123', '123456789', 'abc123456'
                    ]

                    if password.lower() in weak_passwords:
                        raise ValidationError('密码过于简单,请使用更强的密码')

                    # 检查连续字符
                    if any(ord(char) + 1 == ord(next_char)
                           for char, next_char in zip(password, password[1:])
                           for _ in range(3)):
                        raise ValidationError('密码不能包含连续的字符序列')

                @staticmethod
                def validate_business_license(form, field):
                    """营业执照号码验证器"""
                    license_number = field.data
                    if not license_number:
                        return

                    # 18位营业执照号码验证
                    if len(license_number) != 18:
                        raise ValidationError('营业执照号码必须是18位')

                    # 检查字符类型
                    if not re.match(r'^[0-9A-HJ-NPQRTUWXY]+$', license_number):
                        raise ValidationError('营业执照号码格式不正确')

                    # 检查校验码
                    weights = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28]
                    check_codes = '0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,J,K,L,M,N,P,Q,R,T,U,W,X,Y'

                    total = 0
                    for i, char in enumerate(license_number[:17]):
                        if char.isdigit():
                            total += int(char) * weights[i]
                        else:
                            total += (check_codes.index(char) + 1) * weights[i]

                    mod = total % 31
                    expected_check = check_codes[31 - mod] if mod != 0 else '0'

                    if license_number[17] != expected_check:
                        raise ValidationError('营业执照号码校验码错误')

                @staticmethod
                def validate_id_card(form, field):
                    """身份证号码验证器"""
                    id_card = field.data
                    if not id_card:
                        return

                    # 18位身份证验证
                    if len(id_card) != 18:
                        raise ValidationError('身份证号码必须是18位')

                    # 检查字符类型
                    if not re.match(r'^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$', id_card):
                        raise ValidationError('身份证号码格式不正确')

                    # 验证地区代码
                    area_code = id_card[:6]
                    valid_areas = [
                        '110000', '110101', '110102', '110105', '110106', '110107',
                        '110108', '110109', '110111', '112000', '120000', '120101',
                        '120102', '120103', '120104', '120105', '120106', '120110',
                        '120111', '120112', '120113', '120114', '120115', '120116',
                        '120117', '120118', '120119', '130000', '130100', '130102'
                    ]

                    if area_code not in valid_areas:
                        raise ValidationError('身份证号码地区代码无效')

                    # 验证出生日期
                    try:
                        birth_date = datetime.strptime(id_card[6:14], '%Y%m%d').date()
                        today = date.today()

                        if birth_date > today:
                            raise ValidationError('身份证号码出生日期无效')

                        if birth_date < date(1900, 1, 1):
                            raise ValidationError('身份证号码出生日期过于久远')

                        if today.year - birth_date.year > 120:
                            raise ValidationError('身份证号码年龄超出合理范围')

                    except ValueError:
                        raise ValidationError('身份证号码出生日期格式错误')

                    # 验证校验码
                    weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
                    check_codes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']

                    total = sum(int(id_card[i]) * weights[i] for i in range(17))
                    mod = total % 11
                    expected_check = check_codes[mod]

                    if id_card[17].upper() != expected_check:
                        raise ValidationError('身份证号码校验码错误')

                @staticmethod
                def validate_bank_account(form, field):
                    """银行账号验证器"""
                    account_number = field.data
                    if not account_number:
                        return

                    # 移除空格和横线
                    account_number = re.sub(r'[\s-]', '', account_number)

                    # 检查长度(16-19位)
                    if len(account_number) < 16 or len(account_number) > 19:
                        raise ValidationError('银行账号长度必须在16-19位之间')

                    # 检查是否全为数字
                    if not account_number.isdigit():
                        raise ValidationError('银行账号只能包含数字')

                    # Luhn算法验证
                    def luhn_check(card_number):
                        def digits_of(n):
                            return [int(d) for d in str(n)]

                        digits = digits_of(card_number)
                        odd_digits = digits[-1::-2]
                        even_digits = digits[-2::-2]
                        checksum = 0
                        checksum += sum(odd_digits)

                        for d in even_digits:
                            checksum += sum(digits_of(d*2))

                        return checksum % 10 == 0

                    if not luhn_check(account_number):
                        raise ValidationError('银行账号校验失败,请检查账号是否正确')

                @staticmethod
                def validate_credit_card(form, field):
                    """信用卡号验证器"""
                    card_number = field.data
                    if not card_number:
                        return

                    # 移除空格和横线
                    card_number = re.sub(r'[\s-]', '', card_number)

                    # 检查长度(13-19位)
                    if len(card_number) < 13 or len(card_number) > 19:
                        raise ValidationError('信用卡号长度必须在13-19位之间')

                    # 检查是否全为数字
                    if not card_number.isdigit():
                        raise ValidationError('信用卡号只能包含数字')

                    # Luhn算法验证
                    def luhn_check(card_number):
                        def digits_of(n):
                            return [int(d) for d in str(n)]

                        digits = digits_of(card_number)
                        odd_digits = digits[-1::-2]
                        even_digits = digits[-2::-2]
                        checksum = 0
                        checksum += sum(odd_digits)

                        for d in even_digits:
                            checksum += sum(digits_of(d*2))

                        return checksum % 10 == 0

                    if not luhn_check(card_number):
                        raise ValidationError('信用卡号校验失败,请检查卡号是否正确')

                    # 识别卡类型
                    if card_number.startswith('4'):
                        card_type = 'Visa'
                    elif card_number.startswith('5'):
                        card_type = 'MasterCard'
                    elif card_number.startswith('3'):
                        card_type = 'American Express'
                    elif card_number.startswith('6'):
                        card_type = 'Discover'
                    else:
                        card_type = 'Unknown'

                    # 可以将卡类型存储到表单中
                    form.card_type = card_type

                @staticmethod
                def validate_social_media_url(form, field):
                    """社交媒体URL验证器"""
                    url = field.data
                    if not url:
                        return

                    # 支持的社交媒体平台
                    platforms = {
                        'weibo': r'^https?://(weibo\.com/|www\.weibo\.com/)',
                        'wechat': r'^https?://(mp\.weixin\.qq\.com/)',
                        'linkedin': r'^https?://(www\.linkedin\.com/in/|linkedin\.com/in/)',
                        'twitter': r'^https?://(twitter\.com/|www\.twitter\.com/)',
                        'facebook': r'^https?://(facebook\.com/|www\.facebook\.com/)',
                        'instagram': r'^https?://(instagram\.com/|www\.instagram\.com/)'
                    }

                    valid_platform = False
                    for platform, pattern in platforms.items():
                        if re.match(pattern, url):
                            valid_platform = True
                            form.social_platform = platform
                            break

                    if not valid_platform:
                        raise ValidationError('请输入有效的社交媒体URL')

                @staticmethod
                def validate_coordinate(form, field):
                    """地理坐标验证器"""
                    coordinate = field.data
                    if not coordinate:
                        return

                    # 解析坐标格式:经度,纬度
                    try:
                        lon, lat = map(float, coordinate.split(','))
                    except ValueError:
                        raise ValidationError('坐标格式不正确,应为:经度,纬度')

                    # 验证经度范围
                    if not (-180 <= lon <= 180):
                        raise ValidationError('经度必须在-180到180之间')

                    # 验证纬度范围
                    if not (-90 <= lat <= 90):
                        raise ValidationError('纬度必须在-90到90之间')

                    # 检查坐标是否在中国境内(可选)
                    # 中国大致范围:经度73°E-135°E,纬度18°N-54°N
                    if not (73 <= lon <= 135 and 18 <= lat <= 54):
                        raise ValidationError('坐标不在中国境内')

                @staticmethod
                def validate_product_code(form, field):
                    """商品条码验证器"""
                    code = field.data
                    if not code:
                        return

                    # 移除空格和横线
                    code = re.sub(r'[\s-]', '', code)

                    # EAN-13码验证
                    if len(code) == 13:
                        if not code.isdigit():
                            raise ValidationError('EAN-13条码必须为13位数字')

                        # 检查前缀
                        prefixes = ['690', '691', '692', '693', '694', '695']
                        if not any(code.startswith(prefix) for prefix in prefixes):
                            raise ValidationError('EAN-13条码前缀无效')

                        # 验证校验码
                        def calculate_ean13_check_digit(code_12):
                            total = 0
                            for i, digit in enumerate(code_12):
                                weight = 3 if i % 2 == 1 else 1
                                total += int(digit) * weight
                            check_digit = (10 - (total % 10)) % 10
                            return check_digit

                        expected_check = calculate_ean13_check_digit(code[:12])
                        if int(code[12]) != expected_check:
                            raise ValidationError('EAN-13条码校验码错误')

                    # UPC-A码验证
                    elif len(code) == 12:
                        if not code.isdigit():
                            raise ValidationError('UPC-A条码必须为12位数字')

                        # 验证校验码
                        def calculate_upca_check_digit(code_11):
                            total = 0
                            for i, digit in enumerate(code_11):
                                weight = 3 if i % 2 == 0 else 1
                                total += int(digit) * weight
                            check_digit = (10 - (total % 10)) % 10
                            return check_digit

                        expected_check = calculate_upca_check_digit(code[:11])
                        if int(code[11]) != expected_check:
                            raise ValidationError('UPC-A条码校验码错误')

                    else:
                        raise ValidationError('商品条码长度必须为12位(UPC-A)或13位(EAN-13)')

                @staticmethod
                def validate_vehicle_plate(form, field):
                    """车牌号码验证器"""
                    plate = field.data
                    if not plate:
                        return

                    plate = plate.upper()

                    # 新能源车牌验证
                    new_energy_pattern = r'^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-Z0-9]{6}$'

                    # 传统车牌验证
                    traditional_pattern = r'^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-Z0-9]{5}$'

                    if not (re.match(new_energy_pattern, plate) or re.match(traditional_pattern, plate)):
                        raise ValidationError('车牌号码格式不正确')

                @staticmethod
                def validate_color_hex(form, field):
                    """十六进制颜色验证器"""
                    color = field.data
                    if not color:
                        return

                    # 支持 #RGB, #RRGGBB 格式
                    pattern = r'^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$'

                    if not re.match(pattern, color):
                        raise ValidationError('颜色格式不正确,应为 #RGB 或 #RRGGBB 格式')

                    # 转换为RGB值(可选)
                    def hex_to_rgb(hex_color):
                        hex_color = hex_color.lstrip('#')
                        if len(hex_color) == 3:
                            hex_color = ''.join([c*2 for c in hex_color])
                        return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

                    try:
                        rgb_values = hex_to_rgb(color)
                        form.rgb_values = rgb_values
                    except ValueError:
                        raise ValidationError('颜色值转换失败')

                @staticmethod
                def validate_time_range(form, field):
                    """时间范围验证器"""
                    time_range = field.data
                    if not time_range:
                        return

                    # 支持 HH:MM-HH:MM 格式
                    pattern = r'^([01]?[0-9]|2[0-3]):[0-5][0-9]-([01]?[0-9]|2[0-3]):[0-5][0-9]$'

                    if not re.match(pattern, time_range):
                        raise ValidationError('时间范围格式不正确,应为 HH:MM-HH:MM')

                    try:
                        start_time, end_time = time_range.split('-')
                        start_hour, start_minute = map(int, start_time.split(':'))
                        end_hour, end_minute = map(int, end_time.split(':'))

                        start_minutes = start_hour * 60 + start_minute
                        end_minutes = end_hour * 60 + end_minute

                        if start_minutes >= end_minutes:
                            raise ValidationError('开始时间必须早于结束时间')

                        # 存储解析后的时间
                        form.start_time_minutes = start_minutes
                        form.end_time_minutes = end_minutes

                    except ValueError:
                        raise ValidationError('时间范围解析失败')

            # 高级用户注册表单
            class AdvancedRegistrationForm(FlaskForm):
                """高级用户注册表单"""

                username = StringField(
                    '用户名',
                    validators=[
                        DataRequired(message='用户名不能为空'),
                        Length(min=3, max=20, message='用户名长度必须在3-20个字符之间'),
                        Regexp(r'^[a-zA-Z0-9_]+$', message='用户名只能包含字母、数字和下划线')
                    ]
                )

                email = StringField(
                    '邮箱地址',
                    validators=[
                        DataRequired(message='邮箱地址不能为空'),
                        Email(message='请输入有效的邮箱地址')
                    ]
                )

                password = StringField(
                    '密码',
                    validators=[
                        DataRequired(message='密码不能为空'),
                        CustomValidators.validate_password_strength
                    ]
                )

                confirm_password = StringField(
                    '确认密码',
                    validators=[
                        DataRequired(message='请确认密码')
                    ]
                )

                real_name = StringField(
                    '真实姓名',
                    validators=[
                        DataRequired(message='真实姓名不能为空'),
                        Length(min=2, max=20, message='姓名长度必须在2-20个字符之间')
                    ]
                )

                id_card = StringField(
                    '身份证号码',
                    validators=[
                        DataRequired(message='身份证号码不能为空'),
                        CustomValidators.validate_id_card
                    ]
                )

                phone = StringField(
                    '手机号码',
                    validators=[
                        DataRequired(message='手机号码不能为空'),
                        Regexp(r'^1[3-9]\d{9}$', message='请输入有效的手机号码')
                    ]
                )

                # 自定义验证方法
                def validate_confirm_password(self, field):
                    """验证确认密码"""
                    if field.data != self.password.data:
                        raise ValidationError('两次输入的密码不一致')

                def validate_username(self, field):
                    """验证用户名唯一性"""
                    # 模拟数据库检查
                    existing_users = ['admin', 'test', 'user123']
                    if field.data in existing_users:
                        raise ValidationError('用户名已存在')

                def validate_email(self, field):
                    """验证邮箱唯一性"""
                    # 模拟数据库检查
                    existing_emails = ['[email protected]', '[email protected]']
                    if field.data in existing_emails:
                        raise ValidationError('邮箱地址已被注册')

            # 企业信息表单
            class CompanyInfoForm(FlaskForm):
                """企业信息表单"""

                company_name = StringField(
                    '企业名称',
                    validators=[
                        DataRequired(message='企业名称不能为空'),
                        Length(min=5, max=100, message='企业名称长度必须在5-100个字符之间')
                    ]
                )

                business_license = StringField(
                    '营业执照号码',
                    validators=[
                        DataRequired(message='营业执照号码不能为空'),
                        CustomValidators.validate_business_license
                    ]
                )

                tax_id = StringField(
                    '税务登记号',
                    validators=[
                        DataRequired(message='税务登记号不能为空'),
                        Regexp(r'^\d{15}$', message='税务登记号必须为15位数字')
                    ]
                )

                legal_representative = StringField(
                    '法定代表人',
                    validators=[
                        DataRequired(message='法定代表人不能为空'),
                        Length(min=2, max=20, message='法定代表人姓名长度必须在2-20个字符之间')
                    ]
                )

                legal_rep_id_card = StringField(
                    '法人身份证号',
                    validators=[
                        DataRequired(message='法人身份证号不能为空'),
                        CustomValidators.validate_id_card
                    ]
                )

                bank_account = StringField(
                    '银行账号',
                    validators=[
                        DataRequired(message='银行账号不能为空'),
                        CustomValidators.validate_bank_account
                    ]
                )

                credit_card = StringField(
                    '企业信用卡号',
                    validators=[
                        Optional(),
                        CustomValidators.validate_credit_card
                    ]
                )

                company_website = StringField(
                    '企业官网',
                    validators=[
                        Optional(),
                        URL(message='请输入有效的URL地址')
                    ]
                )

                weibo_url = StringField(
                    '微博地址',
                    validators=[
                        Optional(),
                        CustomValidators.validate_social_media_url
                    ]
                )

                coordinate = StringField(
                    '企业坐标',
                    validators=[
                        Optional(),
                        CustomValidators.validate_coordinate
                    ]
                )

                brand_color = StringField(
                    '品牌颜色',
                    validators=[
                        Optional(),
                        CustomValidators.validate_color_hex
                    ]
                )

                business_hours = StringField(
                    '营业时间',
                    validators=[
                        Optional(),
                        CustomValidators.validate_time_range
                    ]
                )

                vehicle_plates = StringField(
                    '车辆牌照',
                    validators=[
                        Optional(),
                        Regexp(r'^[A-Za-z0-9\s,,]+$', message='车牌号格式不正确')
                    ]
                )

                # 验证车牌号
                def validate_vehicle_plates(self, field):
                    """验证车牌号列表"""
                    if field.data:
                        plates = [plate.strip() for plate in re.split(r'[,,]', field.data) if plate.strip()]
                        for plate in plates:
                            if not CustomValidators.validate_vehicle_plate(self, plate):
                                # 这里需要创建一个临时的field对象来调用验证器
                                temp_field = type('TempField', (), {'data': plate})()
                                CustomValidators.validate_vehicle_plate(self, temp_field)

            # 订单表单
            class OrderForm(FlaskForm):
                """订单表单"""

                product_code = StringField(
                    '商品条码',
                    validators=[
                        DataRequired(message='商品条码不能为空'),
                        CustomValidators.validate_product_code
                    ]
                )

                quantity = IntegerField(
                    '数量',
                    validators=[
                        DataRequired(message='数量不能为空'),
                        NumberRange(min=1, max=1000, message='数量必须在1-1000之间')
                    ]
                )

                unit_price = FloatField(
                    '单价',
                    validators=[
                        DataRequired(message='单价不能为空'),
                        NumberRange(min=0.01, max=1000000, message='单价必须在0.01-1000000之间')
                    ]
                )

                discount_code = StringField(
                    '优惠码',
                    validators=[
                        Optional(),
                        Regexp(r'^[A-Z0-9]{6,12}$', message='优惠码格式不正确')
                    ]
                )

                # 自定义验证方法
                def validate_product_code(self, field):
                    """验证商品条码"""
                    # 模拟检查商品是否存在
                    valid_codes = ['6901234567890', '6931234567890']
                    if field.data not in valid_codes:
                        raise ValidationError('商品条码不存在')

                def validate_discount_code(self, field):
                    """验证优惠码"""
                    if field.data:
                        # 模拟检查优惠码有效性
                        valid_codes = ['SAVE20', 'WELCOME10', 'SPECIAL50']
                        if field.data not in valid_codes:
                            raise ValidationError('优惠码无效或已过期')

            # 路由定义
            @app.route('/forms/advanced-register', methods=['GET', 'POST'])
            def advanced_register():
                """高级注册页面"""
                form = AdvancedRegistrationForm()

                if form.validate_on_submit():
                    flash('注册成功!', 'success')
                    return redirect(url_for('success'))

                return render_template('forms/advanced_register.html', form=form)

            @app.route('/forms/company-info', methods=['GET', 'POST'])
            def company_info():
                """企业信息页面"""
                form = CompanyInfoForm()

                if form.validate_on_submit():
                    flash('企业信息提交成功!', 'success')
                    return redirect(url_for('success'))

                return render_template('forms/company_info.html', form=form)

            @app.route('/forms/order', methods=['GET', 'POST'])
            def order():
                """订单页面"""
                form = OrderForm()

                if form.validate_on_submit():
                    flash('订单提交成功!', 'success')
                    return redirect(url_for('success'))

                return render_template('forms/order.html', form=form)

            if __name__ == '__main__':
                app.run(debug=True)

6.3 文件上传处理

01.文件上传基础
    a.基础文件上传配置
        a.安全文件上传实现
            from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_from_directory
            from flask_wtf import FlaskForm
            from flask_wtf.file import FileField, FileRequired, FileAllowed
            from wtforms import StringField, TextAreaField, SelectField, BooleanField, IntegerField
            from wtforms.validators import DataRequired, Length, Optional, NumberRange
            from werkzeug.utils import secure_filename
            from werkzeug.datastructures import FileStorage
            import os
            import uuid
            import hashlib
            from datetime import datetime
            import mimetypes
            import magic

            app = Flask(__name__)

            # 文件上传配置
            def configure_file_upload(app):
                """配置文件上传设置"""
                # 基础配置
                app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB
                app.config['UPLOAD_FOLDER'] = 'uploads'
                app.config['UPLOAD_EXTENSIONS'] = {
                    # 图片文件
                    'images': ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'],
                    # 文档文件
                    'documents': ['.pdf', '.doc', '.docx', '.txt', '.rtf', '.odt'],
                    # 电子表格
                    'spreadsheets': ['.xls', '.xlsx', '.csv', '.ods'],
                    # 演示文稿
                    'presentations': ['.ppt', '.pptx', '.odp'],
                    # 音频文件
                    'audio': ['.mp3', '.wav', '.ogg', '.m4a', '.flac'],
                    # 视频文件
                    'video': ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.webm'],
                    # 压缩文件
                    'archives': ['.zip', '.rar', '.7z', '.tar', '.gz']
                }

                # MIME类型映射
                app.config['UPLOAD_MIME_TYPES'] = {
                    'image/jpeg': '.jpg',
                    'image/png': '.png',
                    'image/gif': '.gif',
                    'image/webp': '.webp',
                    'image/svg+xml': '.svg',
                    'application/pdf': '.pdf',
                    'text/plain': '.txt',
                    'application/msword': '.doc',
                    'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
                    'application/vnd.ms-excel': '.xls',
                    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
                    'application/vnd.ms-powerpoint': '.ppt',
                    'application/vnd.openxmlformats-officedocument.presentationml.presentation': '.pptx'
                }

                # 安全设置
                app.config['UPLOAD_SECURITY'] = {
                    'scan_uploads': True,           # 扫描上传文件
                    'quarantine_infected': True,     # 隔离感染文件
                    'generate_thumbnails': True,     # 生成缩略图
                    'extract_metadata': True,        # 提取文件元数据
                    'allow_duplicates': False       # 允许重复文件
                }

                # 目录结构
                upload_dirs = [
                    'uploads',
                    'uploads/images',
                    'uploads/documents',
                    'uploads/temp',
                    'uploads/quarantine',
                    'uploads/thumbnails'
                ]

                for directory in upload_dirs:
                    os.makedirs(os.path.join(app.root_path, directory), exist_ok=True)

            configure_file_upload(app)

            class FileUploadManager:
                """文件上传管理器"""

                def __init__(self, app=None):
                    self.app = app
                    if app is not None:
                        self.init_app(app)

                def init_app(self, app):
                    """初始化文件上传管理器"""
                    self.app = app

                def allowed_file(self, filename):
                    """检查文件扩展名是否允许"""
                    if not filename:
                        return False

                    # 获取文件扩展名
                    ext = os.path.splitext(filename).lower()

                    # 检查是否在允许的扩展名列表中
                    allowed_extensions = set()
                    for ext_list in self.app.config['UPLOAD_EXTENSIONS'].values():
                        allowed_extensions.update(ext_list)

                    return ext in allowed_extensions

                def get_file_category(self, filename):
                    """获取文件类别"""
                    if not filename:
                        return 'unknown'

                    ext = os.path.splitext(filename).lower()

                    for category, extensions in self.app.config['UPLOAD_EXTENSIONS'].items():
                        if ext in extensions:
                            return category

                    return 'unknown'

                def generate_unique_filename(self, filename):
                    """生成唯一的文件名"""
                    # 获取文件扩展名
                    name, ext = os.path.splitext(filename)
                    ext = ext.lower()

                    # 生成UUID作为文件名
                    unique_id = str(uuid.uuid4())
                    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')

                    # 组合文件名
                    unique_filename = f"{timestamp}_{unique_id[:8]}{ext}"

                    return unique_filename

                def get_file_path(self, filename, category=None):
                    """获取文件存储路径"""
                    if category:
                        upload_dir = os.path.join(self.app.config['UPLOAD_FOLDER'], category)
                    else:
                        upload_dir = self.app.config['UPLOAD_FOLDER']

                    return os.path.join(upload_dir, filename)

                def scan_file_for_malware(self, filepath):
                    """扫描文件恶意软件"""
                    if not self.app.config['UPLOAD_SECURITY']['scan_uploads']:
                        return True, "扫描未启用"

                    try:
                        # 使用python-magic库检测文件类型
                        file_mime = magic.from_file(filepath, mime=True)

                        # 检查可疑的MIME类型
                        suspicious_mimes = [
                            'application/x-executable',
                            'application/x-msdos-program',
                            'application/x-msdownload',
                            'application/x-msi',
                            'application/x-shockwave-flash'
                        ]

                        if file_mime in suspicious_mimes:
                            return False, f"可疑的文件类型: {file_mime}"

                        # 检查文件内容特征
                        with open(filepath, 'rb') as f:
                            header = f.read(1024)

                        # 检查PE文件头(Windows可执行文件)
                        if header.startswith(b'MZ'):
                            return False, "检测到Windows可执行文件"

                        # 检查ELF文件头(Linux可执行文件)
                        if header.startswith(b'\x7fELF'):
                            return False, "检测到Linux可执行文件"

                        # 检查脚本特征
                        script_signatures = [
                            b'<script',
                            b'javascript:',
                            b'vbscript:',
                            b'data:text/html',
                            b'<?php',
                            b'<%',
                            b'#!/bin/sh',
                            b'#!/bin/bash'
                        ]

                        for signature in script_signatures:
                            if signature in header.lower():
                                return False, f"检测到可疑脚本: {signature.decode('utf-8', errors='ignore')}"

                        return True, "文件安全"

                    except Exception as e:
                        # 如果扫描失败,根据配置决定是否接受文件
                        if self.app.config['UPLOAD_SECURITY']['quarantine_infected']:
                            return False, f"扫描失败: {str(e)}"
                        else:
                            return True, f"扫描跳过: {str(e)}"

                def calculate_file_hash(self, filepath):
                    """计算文件哈希值"""
                    hash_md5 = hashlib.md5()
                    hash_sha256 = hashlib.sha256()

                    with open(filepath, 'rb') as f:
                        for chunk in iter(lambda: f.read(4096), b""):
                            hash_md5.update(chunk)
                            hash_sha256.update(chunk)

                    return {
                        'md5': hash_md5.hexdigest(),
                        'sha256': hash_sha256.hexdigest()
                    }

                def extract_file_metadata(self, filepath, filename):
                    """提取文件元数据"""
                    metadata = {
                        'original_name': filename,
                        'file_size': 0,
                        'mime_type': 'application/octet-stream',
                        'upload_time': datetime.utcnow().isoformat(),
                        'file_hash': {}
                    }

                    try:
                        # 获取文件大小
                        metadata['file_size'] = os.path.getsize(filepath)

                        # 获取MIME类型
                        metadata['mime_type'] = magic.from_file(filepath, mime=True)

                        # 计算文件哈希
                        metadata['file_hash'] = self.calculate_file_hash(filepath)

                        # 根据文件类型提取特定元数据
                        if metadata['mime_type'].startswith('image/'):
                            metadata.update(self._extract_image_metadata(filepath))
                        elif metadata['mime_type'] == 'application/pdf':
                            metadata.update(self._extract_pdf_metadata(filepath))
                        elif metadata['mime_type'].startswith('video/'):
                            metadata.update(self._extract_video_metadata(filepath))

                    except Exception as e:
                        metadata['extraction_error'] = str(e)

                    return metadata

                def _extract_image_metadata(self, filepath):
                    """提取图片元数据"""
                    metadata = {}

                    try:
                        from PIL import Image, ExifTags
                        from PIL.ExifTags import TAGS

                        with Image.open(filepath) as img:
                            metadata.update({
                                'image_format': img.format,
                                'image_mode': img.mode,
                                'image_size': img.size,
                                'has_transparency': img.mode in ('RGBA', 'LA') or 'transparency' in img.info
                            })

                            # 提取EXIF数据
                            if hasattr(img, '_getexif') and img._getexif() is not None:
                                exif_data = {}
                                for tag_id, value in img._getexif().items():
                                    tag = TAGS.get(tag_id, tag_id)
                                    exif_data[tag] = value

                                metadata['exif_data'] = exif_data

                    except ImportError:
                        metadata['pil_error'] = 'PIL库未安装'
                    except Exception as e:
                        metadata['extraction_error'] = str(e)

                    return metadata

                def _extract_pdf_metadata(self, filepath):
                    """提取PDF元数据"""
                    metadata = {}

                    try:
                        import PyPDF2

                        with open(filepath, 'rb') as file:
                            pdf_reader = PyPDF2.PdfReader(file)

                            if pdf_reader.metadata:
                                metadata['pdf_info'] = {
                                    'title': pdf_reader.metadata.get('/Title', ''),
                                    'author': pdf_reader.metadata.get('/Author', ''),
                                    'subject': pdf_reader.metadata.get('/Subject', ''),
                                    'creator': pdf_reader.metadata.get('/Creator', ''),
                                    'producer': pdf_reader.metadata.get('/Producer', ''),
                                    'creation_date': pdf_reader.metadata.get('/CreationDate', ''),
                                    'modification_date': pdf_reader.metadata.get('/ModDate', '')
                                }

                            metadata['page_count'] = len(pdf_reader.pages)

                    except ImportError:
                        metadata['pypdf2_error'] = 'PyPDF2库未安装'
                    except Exception as e:
                        metadata['extraction_error'] = str(e)

                    return metadata

                def _extract_video_metadata(self, filepath):
                    """提取视频元数据"""
                    metadata = {}

                    try:
                        import cv2

                        cap = cv2.VideoCapture(filepath)

                        if cap.isOpened():
                            metadata.update({
                                'video_width': int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
                                'video_height': int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)),
                                'video_fps': cap.get(cv2.CAP_PROP_FPS),
                                'video_frame_count': int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
                            })

                            # 计算视频时长
                            frame_count = metadata['video_frame_count']
                            fps = metadata['video_fps']
                            if fps > 0:
                                metadata['video_duration'] = frame_count / fps

                        cap.release()

                    except ImportError:
                        metadata['opencv_error'] = 'OpenCV库未安装'
                    except Exception as e:
                        metadata['extraction_error'] = str(e)

                    return metadata

                def generate_thumbnail(self, filepath, output_path, size=(150, 150)):
                    """生成缩略图"""
                    try:
                        from PIL import Image

                        # 只为图片生成缩略图
                        mime_type = magic.from_file(filepath, mime=True)
                        if not mime_type.startswith('image/'):
                            return False, "非图片文件"

                        with Image.open(filepath) as img:
                            # 转换为RGB模式(处理RGBA等模式)
                            if img.mode in ('RGBA', 'LA', 'P'):
                                background = Image.new('RGB', img.size, (255, 255, 255))
                                if img.mode == 'P':
                                    img = img.convert('RGBA')
                                background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
                                img = background

                            # 生成缩略图
                            img.thumbnail(size, Image.Resampling.LANCZOS)
                            img.save(output_path, 'JPEG', quality=85)

                        return True, "缩略图生成成功"

                    except ImportError:
                        return False, "PIL库未安装"
                    except Exception as e:
                        return False, f"缩略图生成失败: {str(e)}"

            # 创建文件上传管理器实例
            upload_manager = FileUploadManager(app)

            class FileUploadForm(FlaskForm):
                """文件上传表单"""
                file = FileField(
                    '选择文件',
                    validators=[
                        FileRequired(message='请选择要上传的文件'),
                        FileAllowed(
                            ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'doc', 'docx', 'txt'],
                            '不支持的文件类型'
                        )
                    ]
                )

                description = TextAreaField(
                    '文件描述',
                    validators=[
                        Optional(),
                        Length(max=500, message='描述不能超过500个字符')
                    ],
                    render_kw={'rows': 3, 'placeholder': '请输入文件描述...'}
                )

                category = SelectField(
                    '文件类别',
                    choices=[
                        ('', '自动检测'),
                        ('images', '图片'),
                        ('documents', '文档'),
                        ('spreadsheets', '电子表格'),
                        ('presentations', '演示文稿')
                    ],
                    coerce=lambda x: x if x else None
                )

                is_public = BooleanField(
                    '公开文件',
                    default=False
                )

                allow_overwrite = BooleanField(
                    '允许覆盖同名文件',
                    default=False
                )

                def validate_file(self, field):
                    """验证上传文件"""
                    if field.data and isinstance(field.data, FileStorage):
                        # 检查文件名
                        filename = field.data.filename
                        if not filename:
                            raise ValidationError('文件名为空')

                        # 检查文件大小
                        if hasattr(field.data, 'content_length'):
                            max_size = app.config['MAX_CONTENT_LENGTH']
                            if field.data.content_length > max_size:
                                raise ValidationError(f'文件大小不能超过 {max_size // (1024*1024)}MB')

                        # 检查文件类型(基于MIME类型)
                        if field.data.content_type:
                            suspicious_types = [
                                'application/x-executable',
                                'application/x-msdos-program',
                                'application/x-msdownload'
                            ]
                            if field.data.content_type in suspicious_types:
                                raise ValidationError('不允许上传可执行文件')

            # 多文件上传表单
            class MultipleFileUploadForm(FlaskForm):
                """多文件上传表单"""
                files = FileField(
                    '选择文件(可多选)',
                    validators=[
                        FileRequired(message='请选择要上传的文件'),
                    ]
                )

                description = TextAreaField(
                    '批量描述',
                    validators=[
                        Optional(),
                        Length(max=1000, message='描述不能超过1000个字符')
                    ],
                    render_kw={'rows': 3, 'placeholder': '请输入文件描述(将应用于所有文件)...'}
                )

                category = SelectField(
                    '文件类别',
                    choices=[
                        ('', '自动检测'),
                        ('images', '图片'),
                        ('documents', '文档'),
                        ('spreadsheets', '电子表格'),
                        ('presentations', '演示文稿'),
                        ('audio', '音频'),
                        ('video', '视频')
                    ],
                    coerce=lambda x: x if x else None
                )

                is_public = BooleanField(
                    '公开文件',
                    default=False
                )

                def validate_files(self, field):
                    """验证多文件上传"""
                    if field.data and isinstance(field.data, FileStorage):
                        # 单文件情况
                        return super().validate_files(field)
                    elif request.files.getlist(field.name):
                        # 多文件情况
                        files = request.files.getlist(field.name)
                        max_files = 10  # 最多同时上传10个文件

                        if len(files) > max_files:
                            raise ValidationError(f'最多只能同时上传 {max_files} 个文件')

                        total_size = sum(
                            file.content_length for file in files
                            if hasattr(file, 'content_length')
                        )

                        max_total_size = app.config['MAX_CONTENT_LENGTH'] * 2  # 总大小限制
                        if total_size > max_total_size:
                            raise ValidationError(f'总文件大小不能超过 {max_total_size // (1024*1024)}MB')

            # 图片上传表单
            class ImageUploadForm(FlaskForm):
                """图片上传表单"""
                image = FileField(
                    '选择图片',
                    validators=[
                        FileRequired(message='请选择图片文件'),
                        FileAllowed(
                            ['jpg', 'jpeg', 'png', 'gif', 'webp'],
                            '只支持图片文件(JPG, PNG, GIF, WebP)'
                        )
                    ]
                )

                title = StringField(
                    '图片标题',
                    validators=[
                        DataRequired(message='请输入图片标题'),
                        Length(min=1, max=100, message='标题长度必须在1-100个字符之间')
                    ]
                )

                alt_text = StringField(
                    '图片描述(Alt Text)',
                    validators=[
                        Optional(),
                        Length(max=200, message='描述不能超过200个字符')
                    ],
                    render_kw={'placeholder': '用于SEO和可访问性的图片描述...'}
                )

                tags = StringField(
                    '标签',
                    validators=[
                        Optional(),
                        Length(max=200, message='标签不能超过200个字符')
                    ],
                    render_kw={'placeholder': '用逗号分隔多个标签...'}
                )

                watermark = BooleanField(
                    '添加水印',
                    default=False
                )

                auto_optimize = BooleanField(
                    '自动优化',
                    default=True
                )

                def validate_image(self, field):
                    """验证图片文件"""
                    if field.data and isinstance(field.data, FileStorage):
                        # 检查图片尺寸
                        try:
                            from PIL import Image
                            import io

                            # 读取图片数据
                            image_data = field.data.read()
                            field.data.seek(0)  # 重置文件指针

                            # 验证图片
                            img = Image.open(io.BytesIO(image_data))

                            # 检查图片尺寸
                            max_width = 5000
                            max_height = 5000
                            if img.width > max_width or img.height > max_height:
                                raise ValidationError(f'图片尺寸不能超过 {max_width}x{max_height}')

                            # 检查图片格式
                            if img.format not in ['JPEG', 'PNG', 'GIF', 'WEBP']:
                                raise ValidationError(f'不支持的图片格式: {img.format}')

                        except ImportError:
                            raise ValidationError('PIL库未安装,无法验证图片')
                        except Exception as e:
                            if 'cannot identify image file' in str(e):
                                raise ValidationError('无法识别的图片文件')
                            raise ValidationError(f'图片验证失败: {str(e)}')

            @app.route('/upload', methods=['GET', 'POST'])
            def upload_file():
                """单文件上传"""
                form = FileUploadForm()

                if form.validate_on_submit():
                    file = form.file.data
                    original_filename = secure_filename(file.filename)

                    # 生成唯一文件名
                    unique_filename = upload_manager.generate_unique_filename(original_filename)

                    # 确定文件类别
                    category = form.category.data or upload_manager.get_file_category(original_filename)

                    # 获取存储路径
                    file_path = upload_manager.get_file_path(unique_filename, category)

                    try:
                        # 保存文件
                        file.save(file_path)

                        # 扫描文件
                        is_safe, scan_result = upload_manager.scan_file_for_malware(file_path)
                        if not is_safe:
                            # 移动到隔离区
                            quarantine_path = upload_manager.get_file_path(
                                unique_filename, 'quarantine'
                            )
                            os.rename(file_path, quarantine_path)

                            flash(f'文件上传失败:{scan_result}', 'error')
                            return redirect(url_for('upload_file'))

                        # 提取元数据
                        metadata = upload_manager.extract_file_metadata(file_path, original_filename)

                        # 生成缩略图(如果是图片)
                        thumbnail_path = None
                        if (category == 'images' and
                            app.config['UPLOAD_SECURITY']['generate_thumbnails']):
                            thumbnail_filename = f"thumb_{unique_filename}"
                            thumbnail_dir = os.path.join(app.config['UPLOAD_FOLDER'], 'thumbnails')
                            thumbnail_path = os.path.join(thumbnail_dir, thumbnail_filename)

                            success, thumb_result = upload_manager.generate_thumbnail(
                                file_path, thumbnail_path
                            )
                            if not success:
                                thumbnail_path = None

                        # 保存文件信息到数据库(这里简化处理)
                        file_info = {
                            'original_name': original_filename,
                            'stored_name': unique_filename,
                            'file_path': file_path,
                            'file_size': metadata['file_size'],
                            'mime_type': metadata['mime_type'],
                            'category': category,
                            'description': form.description.data,
                            'is_public': form.is_public.data,
                            'upload_time': metadata['upload_time'],
                            'file_hash': metadata['file_hash'],
                            'thumbnail_path': thumbnail_path,
                            'metadata': metadata
                        }

                        flash(f'文件 "{original_filename}" 上传成功!', 'success')
                        return render_template('upload/success.html', file_info=file_info)

                    except Exception as e:
                        # 清理上传的文件
                        if os.path.exists(file_path):
                            os.remove(file_path)
                        flash(f'文件上传失败:{str(e)}', 'error')
                        return redirect(url_for('upload_file'))

                return render_template('upload/upload.html', form=form)

            @app.route('/upload/multiple', methods=['GET', 'POST'])
            def upload_multiple_files():
                """多文件上传"""
                form = MultipleFileUploadForm()

                if form.validate_on_submit():
                    files = request.files.getlist('files')
                    uploaded_files = []
                    failed_files = []

                    for i, file in enumerate(files):
                        if file and file.filename:
                            original_filename = secure_filename(file.filename)
                            unique_filename = upload_manager.generate_unique_filename(original_filename)
                            category = form.category.data or upload_manager.get_file_category(original_filename)
                            file_path = upload_manager.get_file_path(unique_filename, category)

                            try:
                                # 保存文件
                                file.save(file_path)

                                # 扫描文件
                                is_safe, scan_result = upload_manager.scan_file_for_malware(file_path)
                                if not is_safe:
                                    # 移动到隔离区
                                    quarantine_path = upload_manager.get_file_path(
                                        unique_filename, 'quarantine'
                                    )
                                    os.rename(file_path, quarantine_path)
                                    failed_files.append({
                                        'filename': original_filename,
                                        'error': scan_result
                                    })
                                    continue

                                # 提取元数据
                                metadata = upload_manager.extract_file_metadata(file_path, original_filename)

                                # 保存文件信息
                                file_info = {
                                    'original_name': original_filename,
                                    'stored_name': unique_filename,
                                    'file_path': file_path,
                                    'file_size': metadata['file_size'],
                                    'mime_type': metadata['mime_type'],
                                    'category': category,
                                    'upload_time': metadata['upload_time']
                                }

                                uploaded_files.append(file_info)

                            except Exception as e:
                                failed_files.append({
                                    'filename': original_filename,
                                    'error': str(e)
                                })

                    # 返回结果
                    return render_template('upload/multiple_result.html',
                                         uploaded_files=uploaded_files,
                                         failed_files=failed_files)

                return render_template('upload/multiple.html', form=form)

            @app.route('/upload/image', methods=['GET', 'POST'])
            def upload_image():
                """图片上传"""
                form = ImageUploadForm()

                if form.validate_on_submit():
                    file = form.image.data
                    original_filename = secure_filename(file.filename)
                    unique_filename = upload_manager.generate_unique_filename(original_filename)
                    file_path = upload_manager.get_file_path(unique_filename, 'images')

                    try:
                        # 图片处理
                        from PIL import Image, ImageDraw, ImageFont
                        import io

                        # 读取图片
                        image_data = file.read()
                        file.seek(0)
                        img = Image.open(io.BytesIO(image_data))

                        # 自动优化
                        if form.auto_optimize.data:
                            # 压缩图片
                            if img.mode in ('RGBA', 'LA', 'P'):
                                img = img.convert('RGB')

                            # 限制最大尺寸
                            max_size = (1920, 1080)
                            img.thumbnail(max_size, Image.Resampling.LANCZOS)

                        # 添加水印
                        if form.watermark.data:
                            draw = ImageDraw.Draw(img)
                            watermark_text = "© My Website"

                            # 使用默认字体
                            try:
                                font = ImageFont.truetype("arial.ttf", 20)
                            except:
                                font = ImageFont.load_default()

                            # 计算水印位置
                            bbox = draw.textbbox((0, 0), watermark_text, font=font)
                            text_width = bbox - bbox[0]
                            text_height = bbox[3] - bbox

                            # 在右下角添加水印
                            margin = 10
                            x = img.width - text_width - margin
                            y = img.height - text_height - margin

                            # 添加半透明背景
                            padding = 5
                            draw.rectangle(
                                [x - padding, y - padding, x + text_width + padding, y + text_height + padding],
                                fill=(255, 255, 255, 128)
                            )

                            draw.text((x, y), watermark_text, font=font, fill=(0, 0, 0, 200))

                        # 保存处理后的图片
                        img.save(file_path, 'JPEG', quality=85, optimize=True)

                        # 生成缩略图
                        thumbnail_filename = f"thumb_{unique_filename}"
                        thumbnail_path = os.path.join(
                            app.config['UPLOAD_FOLDER'], 'thumbnails', thumbnail_filename
                        )
                        upload_manager.generate_thumbnail(file_path, thumbnail_path, (300, 300))

                        # 保存图片信息
                        image_info = {
                            'original_name': original_filename,
                            'stored_name': unique_filename,
                            'file_path': file_path,
                            'title': form.title.data,
                            'alt_text': form.alt_text.data,
                            'tags': [tag.strip() for tag in form.tags.data.split(',') if tag.strip()],
                            'image_size': img.size,
                            'file_size': os.path.getsize(file_path),
                            'thumbnail_path': thumbnail_path,
                            'upload_time': datetime.utcnow().isoformat()
                        }

                        flash(f'图片 "{original_filename}" 上传成功!', 'success')
                        return render_template('upload/image_success.html', image_info=image_info)

                    except Exception as e:
                        if os.path.exists(file_path):
                            os.remove(file_path)
                        flash(f'图片上传失败:{str(e)}', 'error')
                        return redirect(url_for('upload_image'))

                return render_template('upload/image.html', form=form)

            @app.route('/uploads/<path:filename>')
            def serve_uploaded_file(filename):
                """服务上传的文件"""
                upload_dir = app.config['UPLOAD_FOLDER']
                file_path = os.path.join(upload_dir, filename)

                if not os.path.exists(file_path):
                    return jsonify({'error': 'File not found'}), 404

                return send_from_directory(upload_dir, filename)

            @app.route('/uploads/thumbnails/<path:filename>')
            def serve_thumbnail(filename):
                """服务缩略图"""
                thumbnail_dir = os.path.join(app.config['UPLOAD_FOLDER'], 'thumbnails')
                return send_from_directory(thumbnail_dir, filename)

            @app.route('/api/upload', methods=['POST'])
            def api_upload():
                """API文件上传"""
                if 'file' not in request.files:
                    return jsonify({'error': 'No file provided'}), 400

                file = request.files['file']
                if file.filename == '':
                    return jsonify({'error': 'No file selected'}), 400

                # 验证文件
                if not upload_manager.allowed_file(file.filename):
                    return jsonify({'error': 'File type not allowed'}), 400

                try:
                    # 保存文件
                    original_filename = secure_filename(file.filename)
                    unique_filename = upload_manager.generate_unique_filename(original_filename)
                    category = upload_manager.get_file_category(original_filename)
                    file_path = upload_manager.get_file_path(unique_filename, category)

                    file.save(file_path)

                    # 扫描文件
                    is_safe, scan_result = upload_manager.scan_file_for_malware(file_path)
                    if not is_safe:
                        os.remove(file_path)
                        return jsonify({'error': f'File scan failed: {scan_result}'}), 400

                    # 提取元数据
                    metadata = upload_manager.extract_file_metadata(file_path, original_filename)

                    # 生成缩略图
                    thumbnail_path = None
                    if category == 'images':
                        thumbnail_filename = f"thumb_{unique_filename}"
                        thumbnail_dir = os.path.join(app.config['UPLOAD_FOLDER'], 'thumbnails')
                        thumbnail_path = os.path.join(thumbnail_dir, thumbnail_filename)
                        success, _ = upload_manager.generate_thumbnail(file_path, thumbnail_path)
                        if not success:
                            thumbnail_path = None

                    return jsonify({
                        'success': True,
                        'file_info': {
                            'original_name': original_filename,
                            'stored_name': unique_filename,
                            'file_url': url_for('serve_uploaded_file', filename=os.path.join(category, unique_filename)),
                            'thumbnail_url': url_for('serve_thumbnail', filename=thumbnail_filename) if thumbnail_path else None,
                            'file_size': metadata['file_size'],
                            'mime_type': metadata['mime_type'],
                            'category': category,
                            'upload_time': metadata['upload_time']
                        }
                    })

                except Exception as e:
                    return jsonify({'error': str(e)}), 500

            if __name__ == '__main__':
                app.run(debug=True)

6.4 表单安全和CSRF保护

01.CSRF保护机制
    a.CSRF攻击防护
        a.高级CSRF保护实现
            from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session
            from flask_wtf import FlaskForm, CSRFProtect
            from flask_wtf.csrf import CSRFError
            from wtforms import StringField, TextAreaField, BooleanField, SelectField, IntegerField
            from wtforms.validators import DataRequired, Length, Optional, NumberRange
            from werkzeug.security import generate_password_hash, check_password_hash
            import secrets
            import time
            import hashlib
            from datetime import datetime, timedelta
            functools

            app = Flask(__name__)

            # 高级CSRF配置
            def configure_advanced_csrf(app):
                """配置高级CSRF保护"""
                app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', secrets.token_urlsafe(32))

                # CSRF令牌配置
                app.config['WTF_CSRF_ENABLED'] = True
                app.config['WTF_CSRF_TIME_LIMIT'] = 3600  # 1小时
                app.config['WTF_CSRF_SSL_STRICT'] = True  # 仅HTTPS下严格模式
                app.config['WTF_CSRF_SECRET_KEY'] = secrets.token_urlsafe(32)

                # 自定义CSRF令牌配置
                app.config['CSRF_TOKEN_LENGTH'] = 32
                app.config['CSRF_TOKEN_TIMEOUT'] = 3600
                app.config['CSRF_TOKEN_REFRESH'] = True  # 每次请求刷新令牌
                app.config['CSRF_COOKIE_SECURE'] = True  # 仅HTTPS
                app.config['CSRF_COOKIE_HTTPONLY'] = True  # 防止XSS访问
                app.config['CSRF_COOKIE_SAMESITE'] = 'Lax'  # Cookie同源策略

                # 双重提交Cookie模式
                app.config['CSRF_DOUBLE_SUBMIT_COOKIE'] = True
                app.config['CSRF_DOUBLE_SUBMIT_COOKIE_NAME'] = 'csrf_token'

                # Referer检查
                app.config['CSRF_CHECK_REFERER'] = True
                app.config['CSRF_ALLOWED_ORIGINS'] = [
                    'https://yourdomain.com',
                    'https://www.yourdomain.com',
                    'https://api.yourdomain.com'
                ]

            configure_advanced_csrf(app)

            class AdvancedCSRFProtect:
                """高级CSRF保护"""

                def __init__(self, app=None):
                    self.app = app
                    self.token_store = {}
                    if app is not None:
                        self.init_app(app)

                def init_app(self, app):
                    """初始化高级CSRF保护"""
                    self.app = app
                    app.before_request(self._before_request)
                    app.after_request(self._after_request)
                    app.errorhandler(CSRFError)(self._handle_csrf_error)

                def _before_request(self):
                    """请求前CSRF检查"""
                    # 跳过不需要CSRF保护的请求
                    if self._should_skip_csrf():
                        return

                    # 检查CSRF令牌
                    token = self._get_csrf_token()
                    if not token:
                        return self._csrf_error('CSRF token missing')

                    # 验证CSRF令牌
                    if not self._validate_csrf_token(token):
                        return self._csrf_error('CSRF token invalid')

                    # 检查Referer头
                    if self.app.config.get('CSRF_CHECK_REFERER', True):
                        if not self._validate_referer():
                            return self._csrf_error('Invalid Referer')

                def _after_request(self, response):
                    """响应后设置CSRF令牌"""
                    if request.method == 'GET':
                        # 为GET响应设置CSRF令牌
                        token = self._generate_csrf_token()
                        self._set_csrf_token_cookie(response, token)
                        response.headers['X-CSRF-Token'] = token

                    return response

                def _should_skip_csrf(self):
                    """判断是否跳过CSRF检查"""
                    # 跳过安全方法
                    if request.method in ['GET', 'HEAD', 'OPTIONS', 'TRACE']:
                        return True

                    # 跳过特定路径
                    skip_paths = [
                        '/api/webhook/',
                        '/api/payment/callback/',
                        '/health',
                        '/metrics'
                    ]

                    for path in skip_paths:
                        if request.path.startswith(path):
                            return True

                    # 跳过API请求(如果有专门的API令牌验证)
                    if request.path.startswith('/api/'):
                        auth_header = request.headers.get('Authorization')
                        if auth_header and auth_header.startswith('Bearer '):
                            return True

                    return False

                def _get_csrf_token(self):
                    """获取CSRF令牌"""
                    # 优先从Header获取
                    token = request.headers.get('X-CSRF-Token')
                    if token:
                        return token

                    # 从表单数据获取
                    token = request.form.get('csrf_token')
                    if token:
                        return token

                    # 从JSON数据获取
                    if request.is_json:
                        data = request.get_json(silent=True) or {}
                        token = data.get('csrf_token')
                        if token:
                            return token

                    return None

                def _generate_csrf_token(self):
                    """生成CSRF令牌"""
                    timestamp = int(time.time())
                    random_data = secrets.token_bytes(16)
                    user_agent = request.headers.get('User-Agent', '')[:50]

                    # 组合数据
                    token_data = f"{timestamp}:{random_data.hex()}:{user_agent}"

                    # 生成签名
                    secret = self.app.config.get('CSRF_SECRET_KEY', self.app.config['SECRET_KEY'])
                    signature = hashlib.sha256(f"{token_data}:{secret}".encode()).hexdigest()

                    # 最终令牌
                    token = f"{timestamp}.{signature}"

                    # 存储令牌(用于验证)
                    self.token_store[token] = {
                        'timestamp': timestamp,
                        'user_agent': user_agent,
                        'ip_address': request.remote_addr
                    }

                    # 清理过期令牌
                    self._cleanup_expired_tokens()

                    return token

                def _validate_csrf_token(self, token):
                    """验证CSRF令牌"""
                    try:
                        # 解析令牌
                        timestamp_str, signature = token.split('.', 1)
                        timestamp = int(timestamp_str)

                        # 检查时间戳
                        timeout = self.app.config.get('CSRF_TOKEN_TIMEOUT', 3600)
                        if time.time() - timestamp > timeout:
                            return False

                        # 检查令牌是否存在
                        if token not in self.token_store:
                            return False

                        # 获取存储的令牌信息
                        stored_info = self.token_store[token]
                        user_agent = request.headers.get('User-Agent', '')[:50]
                        ip_address = request.remote_addr

                        # 验证用户代理
                        if stored_info['user_agent'] != user_agent:
                            return False

                        # 可选:验证IP地址(可能会因为代理而失败)
                        # if stored_info['ip_address'] != ip_address:
                        #     return False

                        return True

                    except (ValueError, KeyError):
                        return False

                def _validate_referer(self):
                    """验证Referer头"""
                    referer = request.headers.get('Referer')
                    if not referer:
                        return False  # 没有Referer头,拒绝请求

                    # 检查Referer是否在允许的域名列表中
                    allowed_origins = self.app.config.get('CSRF_ALLOWED_ORIGINS', [])
                    for origin in allowed_origins:
                        if referer.startswith(origin):
                            return True

                    return False

                def _set_csrf_token_cookie(self, response, token):
                    """设置CSRF令牌Cookie"""
                    response.set_cookie(
                        self.app.config.get('CSRF_DOUBLE_SUBMIT_COOKIE_NAME', 'csrf_token'),
                        token,
                        max_age=self.app.config.get('CSRF_TOKEN_TIMEOUT', 3600),
                        secure=self.app.config.get('CSRF_COOKIE_SECURE', True),
                        httponly=self.app.config.get('CSRF_COOKIE_HTTPONLY', True),
                        samesite=self.app.config.get('CSRF_COOKIE_SAMESITE', 'Lax')
                    )

                def _cleanup_expired_tokens(self):
                    """清理过期的CSRF令牌"""
                    current_time = time.time()
                    timeout = self.app.config.get('CSRF_TOKEN_TIMEOUT', 3600)

                    expired_tokens = [
                        token for token, info in self.token_store.items()
                        if current_time - info['timestamp'] > timeout
                    ]

                    for token in expired_tokens:
                        del self.token_store[token]

                def _csrf_error(self, message):
                    """CSRF错误处理"""
                    if request.path.startswith('/api/'):
                        return jsonify({'error': message}), 400
                    else:
                        flash(f'安全错误: {message}', 'error')
                        return redirect(url_for('index'))

                def _handle_csrf_error(self, error):
                    """CSRF异常处理"""
                    return self._csrf_error(str(error))

            # 创建高级CSRF保护实例
            advanced_csrf = AdvancedCSRFProtect(app)

            class FormSecurityManager:
                """表单安全管理器"""

                def __init__(self, app=None):
                    self.app = app
                    if app is not None:
                        self.init_app(app)

                def init_app(self, app):
                    """初始化表单安全管理器"""
                    self.app = app

                def generate_form_id(self, form_name):
                    """生成表单ID"""
                    timestamp = int(time.time())
                    random_data = secrets.token_hex(8)
                    form_id = f"{form_name}_{timestamp}_{random_data}"

                    # 存储表单ID到session
                    if 'form_ids' not in session:
                        session['form_ids'] = {}

                    session['form_ids'][form_id] = {
                        'form_name': form_name,
                        'timestamp': timestamp,
                        'used': False
                    }

                    return form_id

                def validate_form_id(self, form_id, form_name):
                    """验证表单ID"""
                    if 'form_ids' not in session:
                        return False

                    form_info = session['form_ids'].get(form_id)
                    if not form_info:
                        return False

                    if form_info['form_name'] != form_name:
                        return False

                    if form_info['used']:
                        return False

                    # 检查时间戳(防止表单重放)
                    timeout = 3600  # 1小时
                    if time.time() - form_info['timestamp'] > timeout:
                        return False

                    # 标记为已使用
                    form_info['used'] = True

                    return True

                def add_honeypot_field(self, form_class):
                    """为表单添加蜜罐字段"""
                    class HoneyPotForm(form_class):
                        # 蜜罐字段,对人类用户不可见
                        confirm_email = StringField(
                            '确认邮箱',
                            validators=[Optional()],
                            render_kw={
                                'style': 'display:none;',
                                'tabindex': '-1',
                                'autocomplete': 'off'
                            }
                        )

                        website = StringField(
                            '网站',
                            validators=[Optional()],
                            render_kw={
                                'style': 'display:none;',
                                'tabindex': '-1',
                                'autocomplete': 'off'
                            }
                        )

                        def validate_confirm_email(self, field):
                            """蜜罐字段验证"""
                            if field.data:
                                raise ValidationError('检测到机器人行为')

                        def validate_website(self, field):
                            """蜜罐字段验证"""
                            if field.data:
                                raise ValidationError('检测到机器人行为')

                    return HoneyPotForm

                def add_rate_limiting(self, form_name, max_attempts=5, window_seconds=300):
                    """添加表单提交频率限制"""
                    def decorator(f):
                        @functools.wraps(f)
                        def decorated_function(*args, **kwargs):
                            # 获取客户端标识
                            client_id = request.remote_addr
                            if request.user.is_authenticated:
                                client_id = f"user_{request.user.id}"

                            # 检查频率限制
                            key = f"rate_limit_{form_name}_{client_id}"

                            if key not in session:
                                session[key] = []

                            attempts = session[key]
                            current_time = time.time()

                            # 清理过期记录
                            attempts[:] = [attempt for attempt in attempts
                                           if current_time - attempt < window_seconds]

                            # 检查是否超过限制
                            if len(attempts) >= max_attempts:
                                return jsonify({
                                    'error': 'Too many requests. Please try again later.',
                                    'retry_after': window_seconds - (current_time - attempts[0])
                                }), 429

                            # 记录当前请求
                            attempts.append(current_time)
                            session[key] = attempts

                            return f(*args, **kwargs)
                        return decorated_function
                    return decorator

            # 创建表单安全管理器实例
            form_security = FormSecurityManager(app)

            class SecureForm(FlaskForm):
                """安全表单基类"""

                def __init__(self, *args, **kwargs):
                    super().__init__(*args, **kwargs)
                    self._form_id = None
                    self._add_security_fields()

                def _add_security_fields(self):
                    """添加安全字段"""
                    # 添加表单ID字段
                    if hasattr(self, 'form_name'):
                        self._form_id = form_security.generate_form_id(self.form_name)

                def validate(self, extra_validators=None):
                    """验证表单"""
                    # 基础验证
                    if not super().validate(extra_validators):
                        return False

                    # 验证表单ID
                    if hasattr(self, 'form_name') and self._form_id:
                        if not form_security.validate_form_id(self._form_id, self.form_name):
                            self.form_errors.append('表单验证失败')
                            return False

                    return True

            # 用户注册表单(包含安全保护)
            class SecureRegistrationForm(SecureForm):
                """安全注册表单"""
                form_name = 'registration'

                username = StringField(
                    '用户名',
                    validators=[
                        DataRequired(message='用户名不能为空'),
                        Length(min=3, max=20, message='用户名长度必须在3-20个字符之间')
                    ]
                )

                email = StringField(
                    '邮箱',
                    validators=[
                        DataRequired(message='邮箱不能为空'),
                        Length(max=120, message='邮箱长度不能超过120个字符')
                    ]
                )

                password = StringField(
                    '密码',
                    validators=[
                        DataRequired(message='密码不能为空'),
                        Length(min=8, max=128, message='密码长度必须在8-128个字符之间')
                    ]
                )

                confirm_password = StringField(
                    '确认密码',
                    validators=[
                        DataRequired(message='请确认密码')
                    ]
                )

                # 添加蜜罐字段
                confirm_email = StringField(
                    '确认邮箱',
                    validators=[Optional()],
                    render_kw={
                        'style': 'display:none;',
                        'tabindex': '-1',
                        'autocomplete': 'off'
                    }
                )

                def validate_confirm_password(self, field):
                    """验证确认密码"""
                    if field.data != self.password.data:
                        raise ValidationError('两次输入的密码不一致')

                def validate_confirm_email(self, field):
                    """蜜罐字段验证"""
                    if field.data:
                        raise ValidationError('检测到机器人行为')

            # 添加频率限制装饰器
            @form_security.add_rate_limiting('registration', max_attempts=3, window_seconds=900)
            @app.route('/secure/register', methods=['GET', 'POST'])
            def secure_register():
                """安全注册"""
                form = SecureRegistrationForm()

                if form.validate_on_submit():
                    # 处理注册逻辑
                    flash('注册成功!', 'success')
                    return redirect(url_for('login'))

                return render_template('secure/register.html', form=form)

            # API表单提交(支持JSON)
            @app.route('/api/secure/register', methods=['POST'])
            def api_secure_register():
                """API安全注册"""
                # 检查CSRF令牌
                if not advanced_csrf._validate_csrf_token(request.json.get('csrf_token')):
                    return jsonify({'error': 'Invalid CSRF token'}), 400

                # 验证表单数据
                form = SecureRegistrationForm(data=request.json)
                form.csrf_token.data = request.json.get('csrf_token')  # 设置CSRF令牌

                if form.validate():
                    # 处理注册逻辑
                    return jsonify({
                        'success': True,
                        'message': '注册成功!'
                    })
                else:
                    return jsonify({
                        'success': False,
                        'errors': form.errors
                    }), 400

            # AJAX表单提交示例
            class AjaxForm(SecureForm):
                """AJAX表单示例"""
                form_name = 'ajax_form'

                message = StringField(
                    '消息',
                    validators=[
                        DataRequired(message='消息不能为空'),
                        Length(max=500, message='消息不能超过500个字符')
                    ]
                )

                # 添加蜜罐字段
                bot_field = StringField(
                    '机器人字段',
                    validators=[Optional()],
                    render_kw={
                        'style': 'display:none;',
                        'tabindex': '-1'
                    }
                )

                def validate_bot_field(self, field):
                    """蜜罐字段验证"""
                    if field.data:
                        raise ValidationError('机器人检测失败')

            @app.route('/ajax/submit', methods=['POST'])
            def ajax_submit():
                """AJAX表单提交"""
                form = AjaxForm()

                if form.validate_on_submit():
                    # 处理表单提交
                    return jsonify({
                        'success': True,
                        'message': '提交成功!',
                        'data': {
                            'message': form.message.data,
                            'timestamp': datetime.utcnow().isoformat()
                        }
                    })
                else:
                    return jsonify({
                        'success': False,
                        'errors': form.errors
                    }), 400

            # 文件上传安全保护
            class SecureUploadForm(SecureForm):
                """安全上传表单"""
                form_name = 'upload'

                # 添加蜜罐字段
                upload_type = StringField(
                    '上传类型',
                    validators=[Optional()],
                    render_kw={
                        'style': 'display:none;',
                        'tabindex': '-1'
                    }
                )

                def validate_upload_type(self, field):
                    """蜜罐字段验证"""
                    if field.data:
                        raise ValidationError('机器人上传被阻止')

            @app.route('/secure/upload', methods=['GET', 'POST'])
            def secure_upload():
                """安全上传"""
                form = SecureUploadForm()

                if form.validate_on_submit():
                    # 处理文件上传
                    return jsonify({'success': True, 'message': '上传成功'})
                else:
                    return jsonify({'success': False, 'errors': form.errors}), 400

            # 多步表单示例
            class MultiStepForm(SecureForm):
                """多步表单基类"""
                step = IntegerField('步骤')

                def __init__(self, step=1, *args, **kwargs):
                    super().__init__(*args, **kwargs)
                    self.step = step

            class Step1Form(MultiStepForm):
                """第一步表单"""
                form_name = 'multistep_step1'

                name = StringField('姓名', validators=[DataRequired()])
                email = StringField('邮箱', validators=[DataRequired()])

            class Step2Form(MultiStepForm):
                """第二步表单"""
                form_name = 'multistep_step2'

                phone = StringField('电话', validators=[DataRequired()])
                address = StringField('地址', validators=[DataRequired()])

            @app.route('/multistep/<int:step>', methods=['GET', 'POST'])
            def multistep_form(step):
                """多步表单处理"""
                if step == 1:
                    form = Step1Form(step=step)
                    template = 'multistep/step1.html'
                elif step == 2:
                    form = Step2Form(step=step)
                    template = 'multistep/step2.html'
                else:
                    return redirect(url_for('index'))

                if form.validate_on_submit():
                    # 保存步骤数据到session
                    if 'multistep_data' not in session:
                        session['multistep_data'] = {}

                    session['multistep_data'][f'step{step}'] = form.data

                    if step == 1:
                        return redirect(url_for('multistep_form', step=2))
                    else:
                        # 完成多步表单
                        return redirect(url_for('form_success'))

                return render_template(template, form=form)

            # 安全检查端点
            @app.route('/security/check')
            def security_check():
                """安全检查端点"""
                return jsonify({
                    'csrf_enabled': app.config.get('WTF_CSRF_ENABLED', False),
                    'session_csrf_token': session.get('csrf_token'),
                    'cookie_csrf_token': request.cookies.get('csrf_token'),
                    'form_ids_count': len(session.get('form_ids', {})),
                    'request_method': request.method,
                    'headers_protection': {
                        'x_frame_options': response.headers.get('X-Frame-Options'),
                        'x_content_type_options': response.headers.get('X-Content-Type-Options'),
                        'x_xss_protection': response.headers.get('X-XSS-Protection')
                    }
                })

            # 清理过期数据的定时任务
            @app.before_request
            def cleanup_expired_data():
                """清理过期的表单ID和CSRF令牌"""
                current_time = time.time()

                # 清理过期的表单ID
                if 'form_ids' in session:
                    expired_form_ids = []
                    for form_id, form_info in session['form_ids'].items():
                        if current_time - form_info['timestamp'] > 3600:  # 1小时
                            expired_form_ids.append(form_id)

                    for form_id in expired_form_ids:
                        del session['form_ids'][form_id]

                # 清理过期的频率限制记录
                for key in list(session.keys()):
                    if key.startswith('rate_limit_'):
                        attempts = session[key]
                        attempts[:] = [attempt for attempt in attempts
                                       if current_time - attempt < 300]  # 5分钟
                        if not attempts:
                            del session[key]

            if __name__ == '__main__':
                app.run(debug=True)

7 功能扩展

7.1 Flask-SQLAlchemy

01.模型,有2种定义方式
    a.方式1
        import datetime
        from applications.extensions import db


        class Role(db.Model):
            __tablename__ = 'admin_role'
            id = db.Column(db.Integer, primary_key=True, comment='角色ID')
            name = db.Column(db.String(255), comment='角色名称')
            code = db.Column(db.String(255), comment='角色标识')
            enable = db.Column(db.Integer, comment='是否启用')
            remark = db.Column(db.String(255), comment='备注')
            details = db.Column(db.String(255), comment='详情')
            sort = db.Column(db.Integer, comment='排序')
            create_time = db.Column(db.DateTime, default=datetime.datetime.now, comment='创建时间')
            update_time = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment='更新时间')
            power = db.relationship('Power', secondary="admin_role_power", backref=db.backref('role'))
    b.方式2
        from applications.extensions import db

        # 创建中间表
        role_power = db.Table(
            "admin_role_power",  # 中间表名称
            db.Column("id", db.Integer, primary_key=True, autoincrement=True, comment='标识'),  # 主键
            db.Column("power_id", db.Integer, db.ForeignKey("admin_power.id"), comment='用户编号'),  # 属性 外键
            db.Column("role_id", db.Integer, db.ForeignKey("admin_role.id"), comment='角色编号'),  # 属性 外键
        )

02.Schema,有2种定义方式
    a.方式1
        # 定义User模型,表示用户表
        class User(db.Model):
          __tablename__ = 'users'  # 指定表名
          id = db.Column(db.Integer, primary_key=True)  # 主键
          username = db.Column(db.String(80), unique=True,
                               nullable=False)  # 用户名字段,唯一且不为空
          email = db.Column(db.String(120), unique=True, nullable=False)  # 邮箱字段,唯一且不为空
          password = db.Column(db.String(128), nullable=False)  # 密码字段,不为空
          created_at = db.Column(db.DateTime,
                                 default=db.func.current_timestamp())  # 创建时间,默认当前时间
          updated_at = db.Column(db.DateTime, default=db.func.current_timestamp(),
                                 onupdate=db.func.current_timestamp())  # 更新时间,默认当前时间,在更新时自动更新

          # 定义一对多关系,用户可以有多篇文章
          posts = db.relationship('Post', backref='author', lazy=True)

          # 定义对象的官方字符串表示,便于调试和日志记录
          def __repr__(self):
            return f'<User {self.username}>'
    b.方式2
        # 定义Post模型,表示帖子表
        class Post(db.Model):
          __tablename__ = 'posts'  # 指定表名
          id = db.Column(db.Integer, primary_key=True)  # 主键
          title = db.Column(db.String(200), nullable=False)  # 标题字段,不为空
          content = db.Column(db.Text, nullable=False)  # 内容字段,不为空
          author_id = db.Column(db.Integer, db.ForeignKey('users.id'),
                                nullable=False)  # 定义外键,引用users表的id字段
          created_at = db.Column(db.DateTime,
                                 default=db.func.current_timestamp())  # 创建时间,默认当前时间
          updated_at = db.Column(db.DateTime, default=db.func.current_timestamp(),
                                 onupdate=db.func.current_timestamp())  # 更新时间,默认当前时间,在更新时自动更新

          # 定义对象的官方字符串表示,便于调试和日志记录
          def __repr__(self):
            return f'<Post {self.title}>'

7.2 Flask-Migrate

01.常用信息1
    a.介绍
        Flask-Migrate 中的这些步骤确实主要用于管理数据库的结构变更,包括创建新表、修改表结构等操作。具体来说:
        init():初始化迁移环境,会在项目中创建一个migrations目录,用于存储迁移脚本和其他相关信息。
        migrate():根据模型类的变化生成迁移脚本,这些脚本描述了如何从当前数据库状态迁移到下一个状态。
        upgrade():执行数据库迁移,将生成的迁移脚本应用到数据库中,包括创建新表、修改表结构等操作。
        这些步骤确实是针对数据库结构的变更,如果数据库中已经存在这些表,则会按照脚本中定义的操作进行更新或者修改。但是,这些步骤本身并不会添加任何数据到数据库中。
    b.注意事项
        如果需要在迁移过程中添加初始数据,像之前提到的那样,你需要在迁移脚本中自行编写代码来添加数据,使用 db.session.add() 和 db.session.commit() 方法将数据写入数据库。
        因此,Flask-Migrate 主要负责管理数据库结构的变更,而对于数据的添加和初始化,需要额外编写代码来完成。

7.3 Flask-WTF

01.常用信息1
    a.介绍
        Flask-WTF 是一个 Flask 扩展,用于处理 Web 表单。
        它基于 WTForms 库,简化了在 Flask 应用程序中创建和处理表单的过程。
    b.主要特点和功能包括
        表单类的定义: 可以通过继承 FlaskForm 类来定义表单,使用 WTForms 提供的各种字段和验证器,例如文本字段 (StringField)、整数字段 (IntegerField)、密码字段 (PasswordField) 等,以及各种验证器来验证用户输入。
        CSRF 保护: 默认情况下,Flask-WTF 自动为表单添加 CSRF 保护,以防止跨站请求伪造攻击。
        表单渲染: 可以很方便地在 Flask 应用中渲染和处理表单,通过模板引擎(如 Jinja2)将表单对象传递到模板中,以便在 HTML 页面中显示和处理用户输入。
        表单验证: 提供了强大的数据验证功能,可以在表单提交前对用户输入进行验证,确保输入的数据符合预期的格式和规则。
        文件上传: 支持处理文件上传,通过 FileField 字段可以处理文件的上传和保存。
        集成性: 可以与 Flask 的其他扩展和功能无缝集成,例如 Flask-SQLAlchemy、Flask-Migrate 等,使得在复杂的应用中管理和处理表单变得更加简单和高效。
        总体来说,Flask-WTF 提供了一个快速、安全且易于使用的方法来处理 Web 应用程序中的表单,是开发 Flask 网站时的重要工具之一。

02.常用信息2
    a.示例
        # 导入必要的模块
        from flask import Flask, render_template, redirect, url_for
        from flask_wtf import FlaskForm
        from wtforms import StringField, SubmitField
        from wtforms.validators import DataRequired

        # 创建Flask应用程序
        app = Flask(__name__)
        app.config['SECRET_KEY'] = 'your_secret_key_here'

        # 创建一个简单的表单类
        class MyForm(FlaskForm):
            name = StringField('Name', validators=[DataRequired()])
            submit = SubmitField('Submit')

        # 定义路由和视图函数
        @app.route('/', methods=['GET', 'POST'])
        def index():
            form = MyForm()
            if form.validate_on_submit():
                name = form.name.data
                return f'Hello, {name}! Form submitted successfully.'
            return render_template('index.html', form=form)

        # 主函数
        if __name__ == '__main__':
            app.run(debug=True)
    b.说明
        FlaskForm 是从Flask-WTF中导入的基类,用于创建表单。
        StringField 和 SubmitField 是从 wtforms 中导入的字段类,用于分别创建文本输入字段和提交按钮。
        DataRequired 是从 wtforms.validators 中导入的验证器,用于确保字段中有数据。
        MyForm 类定义了一个表单,其中 name 是一个必填的文本输入字段,submit 是一个提交按钮。
        index 视图函数处理根路由 / 的 GET 和 POST 请求。当表单通过验证提交时,会显示提交成功的消息。
        app.run(debug=True) 启动应用程序,debug=True 启用调试模式。

7.4 Flask-Login

01.常用信息1
    @login_required 装饰器用于限制只有登录用户才能访问的视图。
    使用:定义一个用户模型,通常需要继承 UserMixin,并实现 get_id() 方法和其他必要的方法
    登录:login_user(user, remember=remember)
    登出:logout_user()

7.5 Flask-Mail

01.常用信息1
    用于在 Flask 应用程序中发送电子邮件。它简化了通过 SMTP 协议发送邮件的过程,
    支持常见的邮件功能和配置选项,如安全连接、认证、附件等。

7.6 Flask-RESTful

01.常用信息1
    a.简化的路由定义
        Flask-RESTful 提供了 Resource 类,使得定义 API 资源变得简单明了。
        你可以将不同的资源(如用户、书籍、订单等)定义为继承自 Resource 的类,
        并为其定义 HTTP 方法(如 GET、POST、PUT、DELETE 等)来处理请求。
    b.自动化的输入和输出序列化
        Flask-RESTful 提供了内置的序列化和反序列化功能,
        可以方便地将请求数据转换为 Python 对象,并将 Python 对象转换为 JSON 格式的响应数据。
        这简化了数据的处理过程,减少了重复的代码。
    c.请求参数解析
        Flask-RESTful 内置了请求参数解析器,可以轻松地从请求中获取参数,并进行验证和转换。
        这使得处理各种类型的请求参数变得简单和可靠。
    d.错误处理
        Flask-RESTful 提供了统一的错误处理机制,可以自定义错误响应和处理逻辑。
        这使得在 API 开发过程中能够更好地控制和处理各种异常情况。
    e.插件扩展支持
        Flask-RESTful 可以与 Flask 的众多扩展库无缝集成,
        如 Flask-SQLAlchemy(用于数据库操作)、Flask-JWT(用于身份验证和授权)、Flask-Cache 等,
        使得开发和扩展 API 功能更加灵活和强大。
    f.RESTful 规范支持
        Flask-RESTful 遵循 RESTful API 设计的最佳实践,
        包括使用 HTTP 方法来表示资源的操作(GET 用于获取资源,POST 用于创建资源,
        PUT 用于更新资源,DELETE 用于删除资源等),使得 API 设计更加符合标准和规范。

02.常用信息2
    a.代码
        from flask import Flask, jsonify
        from flask_restful import Api, Resource

        # 创建 Flask 应用程序
        app = Flask(__name__)
        api = Api(app)

        # 创建一个简单的数据集
        books = [
            {'id': 1, 'title': 'Python Programming', 'author': 'Guido van Rossum'},
            {'id': 2, 'title': 'Flask Essentials', 'author': 'Randall Degges'}
        ]

        # 创建一个继承自 Resource 的类来定义资源
        class BookList(Resource):
            def get(self):
                # 返回所有书籍
                return jsonify(books)

        class Book(Resource):
            def get(self, book_id):
                # 返回特定 ID 的书籍
                book = next(filter(lambda x: x['id'] == book_id, books), None)
                return jsonify(book)

        # 添加资源到 API 中,并定义访问路径
        api.add_resource(BookList, '/books')
        api.add_resource(Book, '/book/<int:book_id>')

        # 定义主函数用于运行应用程序
        def main():
            app.run(debug=True)

        # 程序入口点,运行主函数
        if __name__ == '__main__':
            main()

7.7 Flask-JWT

01.常用信息1
    a.简化的身份验证机制
        Flask-JWT 提供了简单的接口,帮助开发者实现基于 JWT 的身份验证机制。
        开发者可以通过少量的代码,轻松地将 JWT 整合到他们的 Flask 应用中。
    b.无状态的认证
        JWT 能够在请求和响应之间传递用户信息,因此不需要在服务器端存储用户的认证状态。
        这样的无状态特性使得 JWT 非常适合用于分布式和跨域环境。
    c.安全性
        JWT 使用了基于加密的签名机制来保证数据的完整性和安全性。
        通过设置密钥(Secret Key),可以有效地防止 JWT 被篡改或伪造。
    d.灵活的自定义配置
        开发者可以根据自己的需求,自定义 JWT 的过期时间、加密算法以及其他参数。
        这种灵活性使得 JWT 可以适应各种不同的安全需求。
    e.与 Flask 框架的无缝集成
        Flask-JWT 与 Flask 框架天然集成,可以与 Flask 的路由和中间件无缝配合,
        方便地实现对 API 端点的身份验证和访问控制。

02.常用信息2
    a.示例
        from flask import Flask, jsonify
        from flask_jwt import JWT, jwt_required, current_identity


        # 用户数据模型
        class User(object):
          def __init__(self, id, username, password):
            self.id = id
            self.username = username
            self.password = password


        # 用户列表
        users = [
          User(1, 'user1', 'password1'),
          User(2, 'user2', 'password2')
        ]


        # 根据用户名查找用户
        def authenticate(username, password):
          user = next(
            filter(lambda u: u.username == username and u.password == password, users),
            None)
          if user:
            return user


        # 根据用户 ID 查找用户
        def identity(payload):
          user_id = payload['identity']
          return next(filter(lambda u: u.id == user_id, users), None)


        # 创建 Flask 应用程序
        app = Flask(__name__)
        app.config['SECRET_KEY'] = 'super-secret'

        # 创建 JWT 对象
        jwt = JWT(app, authenticate, identity)


        # 受保护的资源,需要验证才能访问
        @app.route('/protected', methods=['GET'])
        @jwt_required()
        def protected():
          return jsonify(id=current_identity.id,
                         username=current_identity.username), 200


        # 定义主函数用于运行应用程序
        if __name__ == '__main__':
          app.run(debug=True)
    b.说明
        导入必要的模块 Flask、Flask-JWT 和 werkzeug.security。
        创建一个简单的 User 类来模拟用户数据,包含 id、username 和 password 属性。
        创建一个用户列表 users,存储两个示例用户。
        定义 authenticate 函数,用于验证用户名和密码是否匹配,返回用户对象。
        定义 identity 函数,根据 JWT 负载中的用户 ID 查找并返回对应的用户对象。
        创建 Flask 应用 app,设置 SECRET_KEY 以用于加密 JWT。
        创建 JWT 对象 jwt,传入 app、authenticate 和 identity 函数,用于处理身份验证和授权。
        定义受保护的路由 /protected,使用 @jwt_required() 装饰器标记,表示该资源需要进行 JWT 验证才能访问。
        在 /protected 路由中,通过 current_identity 获取当前用户的信息,并返回用户的 id 和 username。
        在程序入口处,通过 if __name__ == '__main__': 判断,如果直接运行此文件,则调用 app.run() 启动应用。

7.8 Flask-JWT-Extended

01.常用信息1
    a.确保安装了 Flask 和 Flask-JWT-Extended 库。可以使用 pip 安装:
        pip install Flask Flask-JWT-Extended
    b.配置应用
        在 Flask 应用中配置 JWT 扩展。例如:

        from flask import Flask
        from flask_jwt_extended import JWTManager

        app = Flask(__name__)

        # 设置秘钥,用于签名 JWT
        app.config['JWT_SECRET_KEY'] = 'your-secret-key'
        jwt = JWTManager(app)
    c.定义认证逻辑
        创建认证逻辑,例如登录视图函数,用于生成 JWT。

        from flask import jsonify, request
        from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity

        @app.route('/login', methods=['POST'])
        def login():
            username = request.json.get('username', None)
            password = request.json.get('password', None)
            if username != 'user' or password != 'password':
                return jsonify({"msg": "Bad username or password"}), 401

            access_token = create_access_token(identity=username)
            return jsonify(access_token=access_token), 200
    d.保护资源
        使用 jwt_required 装饰器来保护需要认证的资源。

        @app.route('/protected', methods=['GET'])
        @jwt_required()
        def protected():
            current_user = get_jwt_identity()
            return jsonify(logged_in_as=current_user), 200
    e.处理权限
        如果需要更精细的权限控制,可以使用 @jwt_required 装饰器结合 get_jwt_claims 来检查用户的权限声明。
        这个例子中,要求 JWT 的声明中包含一个 role 字段,并且其值为 admin 才能访问。

        @app.route('/admin', methods=['GET'])
        @jwt_required()
        def admin():
            current_user = get_jwt_identity()
            claims = get_jwt_claims()
            if claims['role'] != 'admin':
                return jsonify(msg='Admins only!'), 403
            return jsonify(admin_resource='secret'), 200

02.常用信息2
    a.create_access_token(identity)
        create_access_token 函数用于生成 JWT 访问令牌,基于提供的身份信息。
        参数:identity:表示用户的标识信息,通常是用户名或用户 ID。
        返回值:返回一个字符串,即生成的 JWT 访问令牌。
        -----------------------------------------------------------------------------------------------------
        from flask_jwt_extended import create_access_token
        # 假设用户登录成功后,生成 JWT 访问令牌
        username = 'user'
        access_token = create_access_token(identity=username)
    b.jwt_required()
        jwt_required 是一个装饰器,用于保护需要 JWT 认证的路由,确保请求中包含有效的 JWT 访问令牌。
        -----------------------------------------------------------------------------------------------------
        from flask import jsonify
        from flask_jwt_extended import jwt_required, get_jwt_identity

        # 受保护的路由,需要 JWT 认证
        @app.route('/protected', methods=['GET'])
        @jwt_required()
        def protected():
            # 获取 JWT 中包含的身份信息
            current_user = get_jwt_identity()
            return jsonify(logged_in_as=current_user), 200
    c.get_jwt_identity()
        get_jwt_identity 函数用于从当前请求的 JWT 访问令牌中获取身份信息。
        返回值:返回一个字符串,即 JWT 中包含的身份信息(例如用户名或用户 ID)。
        -----------------------------------------------------------------------------------------------------
        from flask import jsonify
        from flask_jwt_extended import jwt_required, get_jwt_identity

        # 受保护的路由,需要 JWT 认证
        @app.route('/protected', methods=['GET'])
        @jwt_required()
        def protected():
            # 获取 JWT 中包含的身份信息
            current_user = get_jwt_identity()
            return jsonify(logged_in_as=current_user), 200
    d.总结
        除了上述介绍的核心功能外,Flask-JWT-Extended 还提供了其他有用的功能和装饰器,例如:
            get_raw_jwt():获取原始的 JWT 数据。
            create_refresh_token(identity):生成 JWT 刷新令牌。
            jwt_refresh_token_required():保护需要刷新令牌的路由。
            get_jwt_claims():获取 JWT 中的声明信息。
            jwt_optional():标记路由为可选 JWT 认证,即可以提供或不提供有效 JWT 访问令牌。

7.9 Flask-Security

01.常用信息1
    a.代码
        from flask import Flask
        from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, \
          RoleMixin, login_required
        from flask_sqlalchemy import SQLAlchemy

        # 创建 Flask 应用
        app = Flask(__name__)
        app.config['SECRET_KEY'] = 'super-secret'  # 应用的密钥
        app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///demo08.db'  # SQLite 数据库 URI
        app.config['SECURITY_PASSWORD_SALT'] = 'salt'  # 密码盐值

        # 创建 SQLAlchemy 实例
        db = SQLAlchemy(app)

        # 用户-角色关联表
        roles_users = db.Table('roles_users',
                               db.Column('user_id', db.Integer(),
                                         db.ForeignKey('user.id')),
                               db.Column('role_id', db.Integer(),
                                         db.ForeignKey('role.id'))
                               )


        # 角色模型
        class Role(db.Model, RoleMixin):
          id = db.Column(db.Integer(), primary_key=True)
          name = db.Column(db.String(80), unique=True)


        # 用户模型
        class User(db.Model, UserMixin):
          id = db.Column(db.Integer, primary_key=True)
          username = db.Column(db.String(255), unique=True)
          password = db.Column(db.String(255))
          active = db.Column(db.Boolean())
          roles = db.relationship('Role', secondary=roles_users,
                                  backref=db.backref('users', lazy='dynamic'))


        # Flask-Security 需要的用户数据存储对象
        user_datastore = SQLAlchemyUserDatastore(db, User, Role)

        # 创建 Security 实例
        security = Security(app, user_datastore)


        # 创建数据库表结构
        def create_db():
          db.create_all()


        # 保护的路由,需要登录才能访问
        @app.route('/protected')
        @login_required
        def protected_route():
          return 'This is a protected route!'


        # 主函数,用于运行 Flask 应用
        def main():
          create_db()  # 创建数据库表结构
          app.run(debug=True)


        if __name__ == '__main__':
          main()
    b.说明
        导入了 Flask 相关模块和 Flask-Security 扩展。
        创建了 Flask 应用 app,并配置了应用的 SECRET_KEY、数据库 URI 和密码盐值。
        使用 SQLAlchemy 创建了数据库实例 db。
        定义了角色模型 Role 和用户模型 User,并创建了用户-角色关联表 roles_users。
        创建了 SQLAlchemyUserDatastore 对象 user_datastore,用于处理用户和角色的数据存储。
        初始化了 Security 实例,并将 app 和 user_datastore 传递进去。
        定义了保护的路由 /protected,使用 @login_required 装饰器确保只有登录用户才能访问。
        编写了 create_db() 函数,用于创建数据库表结构。
        在 main() 函数中调用 create_db() 函数创建数据库表,并通过 app.run() 启动 Flask 应用。

7.10 Flask-Uploads

01.常用信息1
    a.代码
        from flask import Flask, request, render_template
        from flask_uploads import UploadSet, configure_uploads, IMAGES

        app = Flask(__name__)
        app.config['SECRET_KEY'] = 'super-secret'  # 应用的密钥

        # 配置文件上传目录和允许的文件类型
        photos = UploadSet('photos', IMAGES)
        app.config['UPLOADED_PHOTOS_DEST'] = 'uploads'  # 文件上传目录
        configure_uploads(app, photos)  # 配置上传集合


        # 文件上传路由和视图函数
        @app.route('/upload', methods=['GET', 'POST'])
        def upload_file():
          if request.method == 'POST' and 'photo' in request.files:
            filename = photos.save(request.files['photo'])
            return f'File {filename} uploaded successfully!'
          return render_template('upload.html')


        # 主函数,用于运行 Flask 应用
        def main():
          app.run(debug=True)


        if __name__ == '__main__':
          main()
    b.说明
        导入 Flask 相关模块和 Flask-Uploads 扩展。
        创建 Flask 应用 app,设置应用的密钥和文件上传配置。配置了一个名为 photos 的上传集合,限制为图片类型 (IMAGES)。
        定义了 /upload 路由,用于处理文件上传。在 POST 方法中,通过 photos.save() 方法保存上传的文件,并返回成功上传的消息。
        创建了一个简单的 HTML 模板 upload.html,包含一个文件上传表单。
        定义了 main() 函数用于运行 Flask 应用,通过 app.run() 启动应用。

7.11 Flask-Session

01.常用信息1
    a.代码
        from flask import Flask, session
        from flask_session import Session

        app = Flask(__name__)
        app.config['SECRET_KEY'] = 'super-secret-key'  # 设置应用的密钥

        # 配置 Flask-Session
        app.config['SESSION_TYPE'] = 'filesystem'  # 选择会话存储方式为文件系统
        Session(app)


        # 定义一个简单的路由,用于设置和获取会话数据
        @app.route('/set_session')
        def set_session():
          """
          设置会话数据。
          """
          session['username'] = '[email protected]'
          return 'Session is set!'


        @app.route('/get_session')
        def get_session():
          """
          获取会话数据。
          """
          if 'username' in session:
            return f'Logged in as {session["username"]}'
          return 'Session not set'


        # 主函数,用于运行 Flask 应用
        def main():
          """
          主函数,用于运行 Flask 应用。
          """
          app.run(debug=True)


        if __name__ == '__main__':
          main()
    b.说明
        Flask:用于创建 Flask 应用。
        session:用于访问会话对象,存储和获取会话数据。
        redirect, url_for:用于重定向和 URL 构建。
        Session:Flask-Session 扩展,用于管理 Flask 中的会话。

        Flask(__name__):创建 Flask 应用对象。
        app.config['SECRET_KEY']:设置应用的密钥,用于会话加密和安全性。

        app.config['SESSION_TYPE']:设置会话存储方式为文件系统,可选的还有 'redis', 'mongodb', 'sqlalchemy' 等。
        Session(app):初始化 Flask-Session 扩展,将会话配置应用到 Flask 中。

        /set_session 路由:设置会话数据 session['username']。
        /get_session 路由:获取并显示会话数据 session['username']。

7.12 Flask-CORS

01.常用信息1
    a.代码
        from flask import Flask, jsonify
        from flask_cors import CORS

        app = Flask(__name__)
        app.config['SECRET_KEY'] = 'super-secret'  # 设置应用的密钥

        # 启用 CORS 支持,允许所有来源访问
        CORS(app)

        # 定义一个简单的路由,返回JSON数据
        @app.route('/api/data', methods=['GET'])
        def get_data():
            data = {'message': 'Hello, CORS!'}
            return jsonify(data)

        # 主函数,用于运行 Flask 应用
        def main():
            """
            主函数,用于运行 Flask 应用。
            """
            app.run(debug=True)

        if __name__ == '__main__':
            main()
    b.说明
        使用 CORS(app) 启用 CORS 支持,并允许所有来源访问。
        可以根据需求进行更精细的配置,如设置具体的来源或允许的 HTTP 方法等。

8 相关API

8.1 处理参数

01.请求参数
    a.路径参数 (Path Parameters)
        a.定义
            路径参数是 URL 路径的一部分。例如,/items/{item_id} 中的 item_id 就是一个路径参数。
        b.使用方法
            使用 Path 类来定义路径参数并可以添加描述。
            from fastapi import Path

            @app.get("/items/{item_id}")
            async def read_item(item_id: int = Path(..., description="The unique ID of the item")):
                return {"item_id": item_id}
        c.注意事项
            item_id: int 是强制类型申明,会强制类型转化,如果转化错误,会校验错误。
    b.查询参数 (Query Parameters)
        a.定义
            查询参数出现在 URL 的 ? 后面,通常用于过滤、排序等。可以是可选的。
        b.使用方法
            from fastapi import Query
            @app.get("/items/")
            async def read_items(q: str = Query(None, description="Search query")):
                return {"q": q}
    c.请求体 (Request Body)
        a.定义
            请求体用于 POST、PUT 和 PATCH 请求,通常用来发送数据(如 JSON、XML 等)。
        b.使用方法
            使用 Pydantic 模型 来定义请求体的格式,并可以为字段添加描述。
            from pydantic import BaseModel
            from fastapi import Body
            from typing import Union, Optional

            class Item(BaseModel):
                name: str
                description: str
                birth: Union[date, None] = None
                desc: Optional[str] = None

            @app.post("/items/")
            async def create_item(item: Item = Body(..., description="The item to create")):
                return {"name": item.name, "description": item.description}
        c.注意事项
            请求体常用于提交较大或复杂的数据。
    d.表单参数 (Form Parameters)
        a.定义
            表单参数通常用于 POST 请求中的表单数据提交。
        b.使用方法
            from fastapi import Form
            @app.post("/submit/")
            async def submit_form(name: str = Form(...), age: int = Form(...)):
                return {"name": name, "age": age}
    e.文件上传 (File Uploads)
        a.定义
            FastAPI 支持文件上传,允许通过表单字段上传文件。
        b.使用方法
            from fastapi import File, UploadFile

            @app.post("/upload/")
            async def upload_file(file: UploadFile = File(...)):
                return {"filename": file.filename}
    f.Cookie 参数 (Cookie Parameters)
        a.定义
            如果你需要从请求的 cookies 中获取数据,可以使用 Cookie 类。
        b.使用方法
            from fastapi import Cookie
            @app.get("/items/")
            async def get_items(cookie_id: str = Cookie(None)):
                return {"cookie_id": cookie_id}
    g.头部参数 (Header Parameters)
        a.定义
            如果你需要从请求的头部获取数据,可以使用 Header 类。
        b.使用方法
            from fastapi import Header
            @app.get("/items/")
            async def get_items(user_agent: str = Header(None)):
                return {"user_agent": user_agent}
    h.Request参数 (Request参数 Parameters)
        a.定义
            如果你需要从Request参数获取数据,可以使用 Request 类。
        b.使用方法
            from fastapi import Request
            @app.get("/items/")
            async def get_items(request: Request):
                return {"url": request.url}

8.2 示例工程

01.工程目录
    ├── api_router_demo    项目名称
    ├── handlers           路由处理模块
    │   ├── __init__.py
    │   ├── book.py
    │   ├── movie.py
    │   └── user.py
    ├── routers            路由模块
    │   ├── __init__.py
    │   ├── book.py
    │   ├── movie.py
    │   └── user.py
    └── server.py          应用服务模块

02.代码示例
    a.应用主入口
        #!/usr/bin/python3
        # -*- coding: utf-8 -*-
        # @Author: Hui
        # @File: server.py
        # @Desc: { 应用服务模块 }
        # @Date: 2024/06/28 11:35
        import uvicorn
        from fastapi import FastAPI
        from contextlib import asynccontextmanager
        from routers import api_router


        @asynccontextmanager
        async def lifespan(app: FastAPI):
            await startup()
            yield
            await shutdown()


        app = FastAPI(lifespan=lifespan, description="APIRouter模块路由Demo")
        # app = FastAPI(lifespan=lifespan, routes=api_router.routes, description="APIRouter模块路由Demo")


        async def startup():
            # 初始化路由
            app.include_router(api_router)

            # 初始化资源...


        async def shutdown():
            print("释放资源")
            print("Shutting down...")


        def main():
            uvicorn.run(app)


        if __name__ == '__main__':
            main()
    b.路由模块
        a.用户模块路由
            #!/usr/bin/python3
            # -*- coding: utf-8 -*-
            # @Author: Hui
            # @File: user.py
            # @Desc: { 用户模块路由 }
            # @Date: 2024/06/28 11:47
            from fastapi import APIRouter
            from handlers import UserHandler

            user_router = APIRouter()

            user_router.add_api_route(
                "/api/v1/users/register", UserHandler.register, methods=["POST"], summary="用户注册"
            )
            user_router.add_api_route(
                "/api/v1/users/login", UserHandler.login, methods=["POST"], summary="用户登陆"
            )
            user_router.add_api_route(
                "/api/v1/users", UserHandler.get_users, methods=["GET"], summary="获取用户列表"
            )
            user_router.add_api_route(
                "/api/v1/users/{user_id}", UserHandler.get_user_detail, methods=["GET"], summary="用户详情"
            )
        b.图书模块路由
            #!/usr/bin/python3
            # -*- coding: utf-8 -*-
            # @Author: Hui
            # @File: book.py
            # @Desc: { 书籍模块路由 }
            # @Date: 2024/06/28 11:48
            from fastapi import APIRouter
            from handlers import BookHandler

            book_router = APIRouter()

            book_router.add_api_route(
                "/api/v1/books", BookHandler.get_books, methods=["GET"], summary="获取图书列表"
            )
            book_router.add_api_route(
                "/api/v1/books/{book_id}", BookHandler.get_book_detail, methods=["GET"], summary="图书详情"
            )
        c.电影模块路由
            #!/usr/bin/python3
            # -*- coding: utf-8 -*-
            # @Author: Hui
            # @File: movie.py
            # @Desc: { 电影模块路由 }
            # @Date: 2024/06/28 11:48
            from fastapi import APIRouter
            from handlers import MovieHandler

            movie_router = APIRouter()

            movie_router.add_api_route(
                "/api/v1/movies", MovieHandler.get_movies, methods=["GET"], summary="获取电影列表"
            )
            movie_router.add_api_route(
                "/api/v1/movies/{movie_id}", MovieHandler.get_movie_detail, methods=["GET"], summary="电影详情"
            )
        d.__init__模块
            不同模块各自组织路由,就是通过 APIRouter 的 add_api_route 方法来添加路由信息,常用参数如下
            path 路由路径
            endpoint 对应路由处理函数
            methods 路由请求方法列表
            response_model 响应数据模型(pydantic的model)
            summary 路由备注
            -------------------------------------------------------------------------------------------------
            #!/usr/bin/python3
            # -*- coding: utf-8 -*-
            # @Author: Hui
            # @File: __init__.py.py
            # @Desc: { 项目路由初始化模块 }
            # @Date: 2024/06/28 11:47
            from fastapi import APIRouter
            from .book import book_router
            from .user import user_router
            from .movie import movie_router

            api_router = APIRouter()

            api_router.include_router(user_router, tags=["用户模块"])
            api_router.include_router(book_router, tags=["图书模块"])
            api_router.include_router(movie_router, tags=["电影模块"])
    c.路由处理模块
        a.图书模块handler
            #!/usr/bin/python3
            # -*- coding: utf-8 -*-
            # @Author: Hui
            # @File: book.py
            # @Desc: { 图书模块handler }
            # @Date: 2024/06/28 11:58

            class BookHandler:
                @classmethod
                async def get_books(cls, book_name: str):
                    """获取图书列表"""
                    # 参数校验
                    # 调用业务层处理
                    # 响应出参
                    return "获取图书列表"

                @classmethod
                async def get_book_detail(cls, book_id: int):
                    return "获取图书详情"
        b.电影模块handler
            #!/usr/bin/python3
            # -*- coding: utf-8 -*-
            # @Author: Hui
            # @File: movie.py
            # @Desc: { 电影模块handler }
            # @Date: 2024/06/28 11:58


            class MovieHandler:
                @classmethod
                async def get_movies(cls, movie_name: str, movie_type: str):
                    """获取电影列表"""
                    # 参数校验
                    # 调用业务层处理
                    # 响应出参
                    return "获取电影列表"

                @classmethod
                async def get_movie_detail(cls, movie_id: int):
                    return "获取电影详情"
        c.用户模块handler
            #!/usr/bin/python3
            # -*- coding: utf-8 -*-
            # @Author: Hui
            # @File: user.py
            # @Desc: { 用户模块handler }
            # @Date: 2024/06/28 11:58


            class UserHandler:

                @classmethod
                async def register(cls, username: str, password: str):
                    return "用户注册"

                @classmethod
                async def login(cls, username: str, password: str):
                    return "用户登录"

                @classmethod
                async def get_user_detail(cls, user_id: int):
                    return "获取用户详情"

                @classmethod
                async def get_users(cls, username: str):
                    """获取用户列表"""
                    # 参数校验
                    # 调用业务层处理
                    # 响应出参
                    return "获取用户列表"
    d.路由API展示
        直接访问 http://127.0.0.1:8000/docs#/ 就可以查看路由接口文档