静态文件处理与模板渲染深度指南

目录

『宝藏代码胶囊开张啦!』------ 我的 CodeCapsule 来咯!✨写代码不再头疼!我的新站点 CodeCapsule 主打一个 "白菜价"+"量身定制 "!无论是卡脖子的毕设/课设/文献复现 ,需要灵光一现的算法改进 ,还是想给项目加个"外挂",这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网

静态文件处理与模板渲染深度指南

1. 引言:Web应用的核心架构 {#引言}

在现代Web应用开发中,静态文件处理与模板渲染是构建用户界面的两大支柱。根据HTTP Archive 2024年报告,网页平均大小已达2.3MB,其中静态资源占比超过75%。高效的静态文件处理和智能的模板渲染直接影响着用户体验和系统性能。

1.1 静态文件处理的演进历程

timeline title 静态文件处理技术演进 section 1990s 静态文件服务器 : 直接文件系统访问 无缓存策略 : 每次请求都重新加载 section 2000s CDN兴起 : Akamai等CDN服务商 浏览器缓存 : Cache-Control头部 section 2010s 版本化哈希 : Webpack等打包工具 HTTP/2推送 : 服务器主动推送资源 section 2020s 边缘计算 : Cloudflare Workers等 AI智能优化 : 基于使用模式的预加载

1.2 模板渲染的重要性

模板渲染不仅仅是字符串替换,它涉及到:

  • 安全性:防止XSS攻击
  • 性能:编译缓存、预编译
  • 可维护性:模板继承、组件化
  • 国际化:多语言支持

2. 静态文件处理原理与技术 {#静态文件原理}

2.1 静态文件服务的基本原理

静态文件服务的核心是将文件系统中的资源通过HTTP协议提供给客户端。其数学表示如下:

设 F F F 为文件系统, R R R 为HTTP请求, S S S 为静态文件服务函数:

S ( R ) = { FileContent ( F , R . path ) if R . method = GET and Exists ( F , R . path ) Error ( 404 ) otherwise S(R) = \begin{cases} \text{FileContent}(F, R.\text{path}) & \text{if } R.\text{method} = \text{GET} \text{ and } \text{Exists}(F, R.\text{path}) \\ \text{Error}(404) & \text{otherwise} \end{cases} S(R)={FileContent(F,R.path)Error(404)if R.method=GET and Exists(F,R.path)otherwise

2.2 静态文件服务器实现

python 复制代码
"""
高级静态文件服务器实现
支持:范围请求、缓存控制、压缩、安全头
"""
import os
import mimetypes
import hashlib
import time
import gzip
import brotli
from pathlib import Path
from typing import Optional, Dict, Any, Tuple, BinaryIO
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from enum import Enum
import logging
from urllib.parse import unquote


class CacheControl(Enum):
    """缓存控制策略"""
    NO_CACHE = "no-cache"
    NO_STORE = "no-store"
    PUBLIC = "public"
    PRIVATE = "private"
    MAX_AGE = "max-age"
    IMMUTABLE = "immutable"
    STALE_WHILE_REVALIDATE = "stale-while-revalidate"


@dataclass
class StaticFile:
    """静态文件元数据"""
    path: Path
    size: int
    mtime: float
    content_type: str
    etag: str
    compressed_size: Dict[str, int] = field(default_factory=dict)  # gzip, br
    
    @classmethod
    def from_path(cls, path: Path) -> Optional['StaticFile']:
        """从路径创建StaticFile"""
        if not path.exists() or not path.is_file():
            return None
        
        stat = path.stat()
        
        # 计算ETag
        with open(path, 'rb') as f:
            content = f.read()
            etag = hashlib.md5(content).hexdigest()
        
        # 猜测MIME类型
        content_type, encoding = mimetypes.guess_type(str(path))
        if not content_type:
            content_type = 'application/octet-stream'
        
        # 计算压缩大小
        compressed_size = {}
        
        # gzip压缩
        gzipped = gzip.compress(content, compresslevel=6)
        compressed_size['gzip'] = len(gzipped)
        
        # brotli压缩
        try:
            brotlied = brotli.compress(content)
            compressed_size['br'] = len(brotlied)
        except:
            pass
        
        return cls(
            path=path,
            size=stat.st_size,
            mtime=stat.st_mtime,
            content_type=content_type,
            etag=etag,
            compressed_size=compressed_size
        )
    
    def get_compression_ratio(self, algorithm: str) -> float:
        """获取压缩率"""
        if algorithm not in self.compressed_size:
            return 1.0
        return self.compressed_size[algorithm] / self.size if self.size > 0 else 0
    
    def should_compress(self, algorithm: str, threshold: float = 0.9) -> bool:
        """判断是否应该压缩"""
        if algorithm not in self.compressed_size:
            return False
        return self.get_compression_ratio(algorithm) < threshold


@dataclass
class FileCacheEntry:
    """文件缓存条目"""
    content: bytes
    headers: Dict[str, str]
    timestamp: float
    expires: float
    hits: int = 0
    last_accessed: float = field(default_factory=time.time)
    
    def is_expired(self) -> bool:
        """检查是否过期"""
        return time.time() > self.expires
    
    def should_refresh(self, max_age: float) -> bool:
        """检查是否需要刷新"""
        age = time.time() - self.timestamp
        return age > max_age
    
    def touch(self):
        """更新访问时间"""
        self.last_accessed = time.time()
        self.hits += 1


class StaticFileServer:
    """高性能静态文件服务器"""
    
    # MIME类型映射
    MIME_TYPES = {
        '.html': 'text/html',
        '.css': 'text/css',
        '.js': 'application/javascript',
        '.json': 'application/json',
        '.png': 'image/png',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.gif': 'image/gif',
        '.svg': 'image/svg+xml',
        '.ico': 'image/x-icon',
        '.txt': 'text/plain',
        '.pdf': 'application/pdf',
        '.zip': 'application/zip',
        '.gz': 'application/gzip',
        '.woff': 'font/woff',
        '.woff2': 'font/woff2',
        '.ttf': 'font/ttf',
        '.eot': 'application/vnd.ms-fontobject',
    }
    
    # 压缩级别
    COMPRESSION_LEVELS = {
        'gzip': 6,
        'br': 4,
    }
    
    def __init__(
        self,
        root_dir: str,
        cache_size: int = 100,
        max_age: int = 3600,
        enable_gzip: bool = True,
        enable_brotli: bool = True,
        enable_range: bool = True,
        security_headers: bool = True
    ):
        self.root_path = Path(root_dir).resolve()
        self.cache_size = cache_size
        self.max_age = max_age
        self.enable_gzip = enable_gzip
        self.enable_brotli = enable_brotli
        self.enable_range = enable_range
        self.security_headers = security_headers
        
        # 文件缓存
        self.file_cache: Dict[str, FileCacheEntry] = {}
        self.lru_queue: List[str] = []
        
        # 文件元数据缓存
        self.metadata_cache: Dict[str, StaticFile] = {}
        
        # 统计信息
        self.stats = {
            'requests': 0,
            'cache_hits': 0,
            'cache_misses': 0,
            'bytes_served': 0,
            'compression_savings': 0,
        }
        
        # 初始化MIME类型
        mimetypes.init()
        
        self.logger = logging.getLogger('static_server')
        
        # 验证根目录
        if not self.root_path.exists():
            raise ValueError(f"Root directory does not exist: {root_dir}")
        
        # 预热缓存线程
        self._start_cache_manager()
    
    def _start_cache_manager(self):
        """启动缓存管理线程"""
        import threading
        
        def cleanup_cache():
            while True:
                time.sleep(60)  # 每分钟清理一次
                self._cleanup_cache()
        
        thread = threading.Thread(target=cleanup_cache, daemon=True)
        thread.start()
    
    def _cleanup_cache(self):
        """清理缓存"""
        # 清理过期缓存
        expired_keys = [
            key for key, entry in self.file_cache.items()
            if entry.is_expired()
        ]
        
        for key in expired_keys:
            del self.file_cache[key]
            if key in self.lru_queue:
                self.lru_queue.remove(key)
        
        # 如果缓存仍然过大,按LRU清理
        if len(self.file_cache) > self.cache_size:
            to_remove = len(self.file_cache) - self.cache_size
            for _ in range(to_remove):
                if self.lru_queue:
                    oldest = self.lru_queue.pop(0)
                    if oldest in self.file_cache:
                        del self.file_cache[oldest]
    
    def _update_lru(self, key: str):
        """更新LRU队列"""
        if key in self.lru_queue:
            self.lru_queue.remove(key)
        self.lru_queue.append(key)
        
        # 限制队列长度
        if len(self.lru_queue) > self.cache_size * 2:
            self.lru_queue = self.lru_queue[-self.cache_size:]
    
    def _get_static_file(self, path: str) -> Optional[StaticFile]:
        """获取静态文件元数据"""
        # 解码URL路径
        decoded_path = unquote(path)
        
        # 构建完整路径
        full_path = self.root_path / decoded_path.lstrip('/')
        
        # 安全防护:确保路径在根目录内
        try:
            full_path.resolve().relative_to(self.root_path)
        except ValueError:
            self.logger.warning(f"Path traversal attempt: {decoded_path}")
            return None
        
        # 检查缓存
        cache_key = str(full_path)
        if cache_key in self.metadata_cache:
            return self.metadata_cache[cache_key]
        
        # 创建StaticFile
        static_file = StaticFile.from_path(full_path)
        if static_file:
            self.metadata_cache[cache_key] = static_file
        
        return static_file
    
    def _generate_headers(
        self,
        static_file: StaticFile,
        accept_encoding: str = '',
        range_header: str = None
    ) -> Dict[str, str]:
        """生成HTTP响应头"""
        headers = {
            'Content-Type': static_file.content_type,
            'ETag': f'"{static_file.etag}"',
            'Last-Modified': time.strftime(
                '%a, %d %b %Y %H:%M:%S GMT',
                time.gmtime(static_file.mtime)
            ),
            'Cache-Control': f'public, max-age={self.max_age}',
            'Accept-Ranges': 'bytes' if self.enable_range else 'none',
        }
        
        # 安全头
        if self.security_headers:
            headers.update({
                'X-Content-Type-Options': 'nosniff',
                'X-Frame-Options': 'DENY',
                'X-XSS-Protection': '1; mode=block',
                'Referrer-Policy': 'strict-origin-when-cross-origin',
            })
        
        # 压缩头
        if self.enable_gzip and 'gzip' in accept_encoding:
            if static_file.should_compress('gzip'):
                headers['Content-Encoding'] = 'gzip'
        elif self.enable_brotli and 'br' in accept_encoding:
            if static_file.should_compress('br'):
                headers['Content-Encoding'] = 'br'
        
        return headers
    
    def _compress_content(
        self,
        content: bytes,
        algorithm: str
    ) -> bytes:
        """压缩内容"""
        if algorithm == 'gzip':
            return gzip.compress(content, compresslevel=self.COMPRESSION_LEVELS['gzip'])
        elif algorithm == 'br':
            return brotli.compress(content, quality=self.COMPRESSION_LEVELS['br'])
        return content
    
    def _handle_range_request(
        self,
        content: bytes,
        range_header: str
    ) -> Tuple[bytes, int, Dict[str, str]]:
        """处理范围请求"""
        if not self.enable_range:
            return content, 200, {}
        
        size = len(content)
        
        # 解析Range头
        # 格式: bytes=0-499, 500-999, -500
        range_header = range_header.strip()
        if not range_header.startswith('bytes='):
            return content, 200, {}
        
        ranges = range_header[6:].split(',')
        
        # 暂时只处理单个范围
        if len(ranges) != 1:
            return content, 200, {}
        
        range_spec = ranges[0].strip()
        
        if '-' not in range_spec:
            return content, 200, {}
        
        start_str, end_str = range_spec.split('-')
        
        try:
            if start_str and end_str:
                # bytes=0-499
                start = int(start_str)
                end = int(end_str)
                if end >= size:
                    end = size - 1
            elif start_str:
                # bytes=500-
                start = int(start_str)
                end = size - 1
            elif end_str:
                # bytes=-500
                start = size - int(end_str)
                end = size - 1
            else:
                return content, 200, {}
            
            # 验证范围
            if start < 0 or end >= size or start > end:
                return content, 416, {'Content-Range': f'bytes */{size}'}
            
            # 提取范围内容
            ranged_content = content[start:end+1]
            
            # 生成响应头
            headers = {
                'Content-Range': f'bytes {start}-{end}/{size}',
                'Content-Length': str(len(ranged_content)),
            }
            
            return ranged_content, 206, headers
            
        except ValueError:
            return content, 200, {}
    
    def serve_file(
        self,
        path: str,
        method: str = 'GET',
        headers: Dict[str, str] = None
    ) -> Tuple[int, Dict[str, str], bytes]:
        """
        提供静态文件服务
        
        Args:
            path: 请求路径
            method: HTTP方法
            headers: HTTP请求头
            
        Returns:
            Tuple[int, Dict[str, str], bytes]: 状态码、响应头、内容
        """
        self.stats['requests'] += 1
        
        # 只支持GET和HEAD
        if method not in ('GET', 'HEAD'):
            return 405, {}, b'Method Not Allowed'
        
        headers = headers or {}
        accept_encoding = headers.get('Accept-Encoding', '')
        range_header = headers.get('Range')
        
        # 获取文件元数据
        static_file = self._get_static_file(path)
        if not static_file:
            return 404, {}, b'Not Found'
        
        # 检查缓存条件
        if_none_match = headers.get('If-None-Match')
        if if_none_match and if_none_match.strip('"') == static_file.etag:
            return 304, {}, b''
        
        if_modified_since = headers.get('If-Modified-Since')
        if if_modified_since:
            try:
                if_modified_time = time.mktime(
                    time.strptime(if_modified_since, '%a, %d %b %Y %H:%M:%S GMT')
                )
                if if_modified_time >= static_file.mtime:
                    return 304, {}, b''
            except ValueError:
                pass
        
        # 检查缓存
        cache_key = f"{path}:{accept_encoding}"
        if cache_key in self.file_cache:
            entry = self.file_cache[cache_key]
            if not entry.is_expired():
                self.stats['cache_hits'] += 1
                entry.touch()
                self._update_lru(cache_key)
                
                # 处理范围请求
                if range_header and self.enable_range:
                    ranged_content, status, range_headers = self._handle_range_request(
                        entry.content, range_header
                    )
                    response_headers = {**entry.headers, **range_headers}
                    return status, response_headers, ranged_content
                
                self.stats['bytes_served'] += len(entry.content)
                return 200, entry.headers, entry.content
        
        self.stats['cache_misses'] += 1
        
        # 读取文件内容
        try:
            with open(static_file.path, 'rb') as f:
                content = f.read()
        except Exception as e:
            self.logger.error(f"Error reading file {path}: {e}")
            return 500, {}, b'Internal Server Error'
        
        # 生成响应头
        response_headers = self._generate_headers(
            static_file, accept_encoding, range_header
        )
        
        # 处理压缩
        final_content = content
        content_encoding = response_headers.get('Content-Encoding')
        
        if content_encoding == 'gzip':
            final_content = self._compress_content(content, 'gzip')
        elif content_encoding == 'br':
            final_content = self._compress_content(content, 'br')
        
        # 处理范围请求
        if range_header and self.enable_range:
            ranged_content, status, range_headers = self._handle_range_request(
                final_content, range_header
            )
            response_headers.update(range_headers)
            final_content = ranged_content
        else:
            status = 200
        
        # 更新缓存
        if status == 200:
            cache_entry = FileCacheEntry(
                content=final_content,
                headers=response_headers,
                timestamp=time.time(),
                expires=time.time() + self.max_age
            )
            self.file_cache[cache_key] = cache_entry
            self._update_lru(cache_key)
        
        # 更新统计
        compression_saving = len(content) - len(final_content)
        if compression_saving > 0:
            self.stats['compression_savings'] += compression_saving
        
        self.stats['bytes_served'] += len(final_content)
        
        # 对于HEAD请求,只返回头部
        if method == 'HEAD':
            return status, response_headers, b''
        
        return status, response_headers, final_content
    
    def preload_files(self, patterns: List[str]):
        """预加载文件到缓存"""
        import fnmatch
        
        for pattern in patterns:
            for file_path in self.root_path.rglob('*'):
                if file_path.is_file() and fnmatch.fnmatch(str(file_path), pattern):
                    relative_path = str(file_path.relative_to(self.root_path))
                    self.serve_file(relative_path)
    
    def get_stats(self) -> Dict[str, Any]:
        """获取服务器统计信息"""
        cache_hit_rate = (
            self.stats['cache_hits'] / self.stats['requests']
            if self.stats['requests'] > 0 else 0
        )
        
        avg_file_size = (
            self.stats['bytes_served'] / self.stats['requests']
            if self.stats['requests'] > 0 else 0
        )
        
        compression_ratio = (
            self.stats['compression_savings'] / self.stats['bytes_served']
            if self.stats['bytes_served'] > 0 else 0
        )
        
        return {
            **self.stats,
            'cache_hit_rate': f"{cache_hit_rate:.2%}",
            'avg_file_size': f"{avg_file_size:.2f} bytes",
            'compression_ratio': f"{compression_ratio:.2%}",
            'cache_size': len(self.file_cache),
            'metadata_cache_size': len(self.metadata_cache),
            'lru_queue_size': len(self.lru_queue),
        }


# 使用示例
if __name__ == "__main__":
    # 配置日志
    logging.basicConfig(level=logging.INFO)
    
    # 创建静态文件服务器
    server = StaticFileServer(
        root_dir="./public",
        cache_size=100,
        max_age=3600,
        enable_gzip=True,
        enable_brotli=True,
        enable_range=True,
        security_headers=True
    )
    
    # 预加载文件
    server.preload_files(["*.html", "*.css", "*.js"])
    
    # 模拟HTTP请求
    def simulate_request(path: str, accept_encoding: str = ""):
        print(f"\nRequesting: {path}")
        
        headers = {'Accept-Encoding': accept_encoding}
        status, headers, content = server.serve_file(path, 'GET', headers)
        
        print(f"Status: {status}")
        print(f"Headers: {headers.get('Content-Type')}, {headers.get('Content-Encoding', 'none')}")
        print(f"Content Length: {len(content)} bytes")
        
        if status == 200 and path.endswith('.txt'):
            print(f"Content (first 100 chars): {content[:100].decode('utf-8', errors='ignore')}")
    
    # 测试不同文件
    simulate_request("index.html", "gzip, br")
    simulate_request("style.css", "gzip")
    simulate_request("script.js", "br")
    simulate_request("image.png", "")
    
    # 测试范围请求
    headers = {'Range': 'bytes=0-499'}
    status, headers, content = server.serve_file("large_file.txt", 'GET', headers)
    print(f"\nRange Request Status: {status}")
    print(f"Range: {headers.get('Content-Range')}")
    
    # 显示统计信息
    print("\nServer Statistics:")
    for key, value in server.get_stats().items():
        print(f"  {key}: {value}")

2.3 HTTP缓存策略

HTTP缓存策略可以用有限状态机表示:
缓存决策逻辑 命中 未命中 新鲜 陈旧 未修改 已修改 计算新鲜度:
max-age / Expires 验证头:
ETag / Last-Modified 请求到达 缓存查找 新鲜度检查 源服务器 返回缓存 验证请求 返回响应
更新缓存 响应客户端

缓存新鲜度计算:

Freshness = { true if age < max-age false otherwise \text{Freshness} = \begin{cases} \text{true} & \text{if } \text{age} < \text{max-age} \\ \text{false} & \text{otherwise} \end{cases} Freshness={truefalseif age<max-ageotherwise

其中 age = current time − response time \text{age} = \text{current time} - \text{response time} age=current time−response time

3. 现代静态文件服务架构 {#现代架构}

3.1 基于CDN的全球分发网络

python 复制代码
"""
CDN集成静态文件服务
支持:边缘缓存、智能路由、实时刷新
"""
import hashlib
import time
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass, field
from enum import Enum
import boto3  # AWS S3示例
from google.cloud import storage  # GCS示例
import requests


class CDNProvider(Enum):
    """CDN提供商"""
    CLOUDFLARE = "cloudflare"
    AWS_CLOUDFRONT = "aws_cloudfront"
    GOOGLE_CLOUD_CDN = "google_cloud_cdn"
    AKAMAI = "akamai"


@dataclass
class CDNFile:
    """CDN文件信息"""
    key: str
    url: str
    size: int
    content_type: str
    etag: str
    last_modified: float
    metadata: Dict[str, str] = field(default_factory=dict)
    cdn_urls: Dict[str, str] = field(default_factory=dict)  # provider -> url
    
    @property
    def cache_key(self) -> str:
        """缓存键"""
        return f"{self.key}:{self.etag}"
    
    def get_cdn_url(self, provider: CDNProvider) -> Optional[str]:
        """获取CDN URL"""
        return self.cdn_urls.get(provider.value)


class CDNStorageBackend:
    """CDN存储后端抽象"""
    
    def __init__(self, provider: CDNProvider, config: Dict[str, Any]):
        self.provider = provider
        self.config = config
        self.client = self._create_client()
        
    def _create_client(self):
        """创建客户端"""
        if self.provider == CDNProvider.AWS_CLOUDFRONT:
            return boto3.client(
                's3',
                aws_access_key_id=self.config.get('aws_access_key_id'),
                aws_secret_access_key=self.config.get('aws_secret_access_key'),
                region_name=self.config.get('region', 'us-east-1')
            )
        elif self.provider == CDNProvider.GOOGLE_CLOUD_CDN:
            return storage.Client.from_service_account_json(
                self.config.get('service_account_key')
            )
        elif self.provider == CDNProvider.CLOUDFLARE:
            # Cloudflare R2 (S3兼容)
            return boto3.client(
                's3',
                endpoint_url=self.config.get('endpoint_url'),
                aws_access_key_id=self.config.get('access_key_id'),
                aws_secret_access_key=self.config.get('secret_access_key'),
                region_name='auto'
            )
        else:
            raise ValueError(f"Unsupported provider: {self.provider}")
    
    async def upload_file(
        self,
        local_path: str,
        remote_key: str,
        content_type: Optional[str] = None,
        metadata: Optional[Dict[str, str]] = None,
        cache_control: str = "public, max-age=31536000"
    ) -> CDNFile:
        """上传文件到CDN"""
        raise NotImplementedError
    
    async def get_file(self, key: str) -> Optional[CDNFile]:
        """从CDN获取文件信息"""
        raise NotImplementedError
    
    async def delete_file(self, key: str) -> bool:
        """从CDN删除文件"""
        raise NotImplementedError
    
    async def list_files(self, prefix: str = "") -> List[CDNFile]:
        """列出CDN文件"""
        raise NotImplementedError
    
    async def purge_cache(self, urls: List[str]) -> bool:
        """清除CDN缓存"""
        raise NotImplementedError


class S3CDNBackend(CDNStorageBackend):
    """S3兼容CDN后端"""
    
    def __init__(self, config: Dict[str, Any]):
        super().__init__(CDNProvider.AWS_CLOUDFRONT, config)
        self.bucket = config['bucket']
    
    async def upload_file(
        self,
        local_path: str,
        remote_key: str,
        content_type: Optional[str] = None,
        metadata: Optional[Dict[str, str]] = None,
        cache_control: str = "public, max-age=31536000"
    ) -> CDNFile:
        """上传文件到S3"""
        import aiofiles
        import aioboto3
        
        # 读取文件
        async with aiofiles.open(local_path, 'rb') as f:
            content = await f.read()
        
        # 计算ETag
        etag = hashlib.md5(content).hexdigest()
        
        # 猜测内容类型
        if not content_type:
            import mimetypes
            content_type, _ = mimetypes.guess_type(local_path)
            if not content_type:
                content_type = 'application/octet-stream'
        
        # 上传到S3
        async with aioboto3.Session().client(
            's3',
            aws_access_key_id=self.config.get('aws_access_key_id'),
            aws_secret_access_key=self.config.get('aws_secret_access_key'),
            region_name=self.config.get('region', 'us-east-1')
        ) as s3:
            await s3.put_object(
                Bucket=self.bucket,
                Key=remote_key,
                Body=content,
                ContentType=content_type,
                CacheControl=cache_control,
                Metadata=metadata or {},
                ACL='public-read'
            )
        
        # 生成CDN URL
        cdn_url = f"https://{self.bucket}.s3.amazonaws.com/{remote_key}"
        
        # 如果有CloudFront分发,使用CloudFront URL
        cloudfront_distribution = self.config.get('cloudfront_distribution')
        if cloudfront_distribution:
            cdn_url = f"https://{cloudfront_distribution}/{remote_key}"
        
        return CDNFile(
            key=remote_key,
            url=cdn_url,
            size=len(content),
            content_type=content_type,
            etag=etag,
            last_modified=time.time(),
            metadata=metadata or {},
            cdn_urls={self.provider.value: cdn_url}
        )
    
    async def get_file(self, key: str) -> Optional[CDNFile]:
        """从S3获取文件信息"""
        import aioboto3
        
        try:
            async with aioboto3.Session().client(
                's3',
                aws_access_key_id=self.config.get('aws_access_key_id'),
                aws_secret_access_key=self.config.get('aws_secret_access_key'),
                region_name=self.config.get('region', 'us-east-1')
            ) as s3:
                response = await s3.head_object(Bucket=self.bucket, Key=key)
                
                # 生成CDN URL
                cdn_url = f"https://{self.bucket}.s3.amazonaws.com/{key}"
                cloudfront_distribution = self.config.get('cloudfront_distribution')
                if cloudfront_distribution:
                    cdn_url = f"https://{cloudfront_distribution}/{key}"
                
                return CDNFile(
                    key=key,
                    url=cdn_url,
                    size=response['ContentLength'],
                    content_type=response['ContentType'],
                    etag=response['ETag'].strip('"'),
                    last_modified=response['LastModified'].timestamp(),
                    metadata=response.get('Metadata', {}),
                    cdn_urls={self.provider.value: cdn_url}
                )
        except Exception as e:
            return None
    
    async def purge_cache(self, urls: List[str]) -> bool:
        """清除CloudFront缓存"""
        cloudfront_distribution = self.config.get('cloudfront_distribution')
        if not cloudfront_distribution:
            return False
        
        import aioboto3
        
        async with aioboto3.Session().client(
            'cloudfront',
            aws_access_key_id=self.config.get('aws_access_key_id'),
            aws_secret_access_key=self.config.get('aws_secret_access_key'),
            region_name=self.config.get('region', 'us-east-1')
        ) as cloudfront:
            # 创建失效请求
            items = []
            for url in urls:
                if url.startswith(f"https://{cloudfront_distribution}/"):
                    path = url.replace(f"https://{cloudfront_distribution}/", "/")
                    items.append(path)
            
            if items:
                await cloudfront.create_invalidation(
                    DistributionId=cloudfront_distribution,
                    InvalidationBatch={
                        'Paths': {
                            'Quantity': len(items),
                            'Items': items
                        },
                        'CallerReference': str(time.time())
                    }
                )
        
        return True


class CDNOrchestrator:
    """CDN编排器 - 多CDN管理"""
    
    def __init__(self):
        self.backends: Dict[CDNProvider, CDNStorageBackend] = {}
        self.file_registry: Dict[str, CDNFile] = {}  # key -> CDNFile
        self.cache = {}
        
    def register_backend(self, provider: CDNProvider, backend: CDNStorageBackend):
        """注册CDN后端"""
        self.backends[provider] = backend
    
    async def upload_to_all(
        self,
        local_path: str,
        remote_key: str,
        **kwargs
    ) -> Dict[CDNProvider, CDNFile]:
        """上传文件到所有CDN"""
        results = {}
        
        for provider, backend in self.backends.items():
            try:
                cdn_file = await backend.upload_file(local_path, remote_key, **kwargs)
                results[provider] = cdn_file
                
                # 更新注册表
                if remote_key not in self.file_registry:
                    self.file_registry[remote_key] = cdn_file
                else:
                    # 合并CDN URL
                    self.file_registry[remote_key].cdn_urls.update(cdn_file.cdn_urls)
                    
            except Exception as e:
                print(f"Failed to upload to {provider}: {e}")
        
        return results
    
    async def get_best_cdn_url(self, key: str) -> Optional[str]:
        """获取最佳CDN URL(基于地理位置和性能)"""
        if key not in self.file_registry:
            return None
        
        cdn_file = self.file_registry[key]
        
        # 简单的选择逻辑:优先选择Cloudflare
        for provider in [CDNProvider.CLOUDFLARE, CDNProvider.AWS_CLOUDFRONT, CDNProvider.GOOGLE_CLOUD_CDN]:
            if provider in self.backends:
                url = cdn_file.get_cdn_url(provider)
                if url:
                    return url
        
        # 返回第一个可用的URL
        if cdn_file.cdn_urls:
            return next(iter(cdn_file.cdn_urls.values()))
        
        return None
    
    async def purge_all_caches(self, urls: List[str]) -> Dict[CDNProvider, bool]:
        """清除所有CDN缓存"""
        results = {}
        
        for provider, backend in self.backends.items():
            try:
                success = await backend.purge_cache(urls)
                results[provider] = success
            except Exception as e:
                results[provider] = False
                print(f"Failed to purge cache for {provider}: {e}")
        
        return results
    
    def get_file_stats(self, key: str) -> Optional[Dict[str, Any]]:
        """获取文件统计信息"""
        if key not in self.file_registry:
            return None
        
        cdn_file = self.file_registry[key]
        
        return {
            'key': cdn_file.key,
            'size': cdn_file.size,
            'content_type': cdn_file.content_type,
            'cdn_providers': list(cdn_file.cdn_urls.keys()),
            'urls': cdn_file.cdn_urls,
            'metadata': cdn_file.metadata,
        }


class IntelligentCDNRouter:
    """智能CDN路由 - 基于性能选择最佳CDN"""
    
    def __init__(self, orchestrator: CDNOrchestrator):
        self.orchestrator = orchestrator
        self.performance_metrics = defaultdict(list)
        self.geo_ip_db = None
        
        # 加载GeoIP数据库
        self._load_geoip_db()
        
        # 启动性能监控
        self._start_performance_monitoring()
    
    def _load_geoip_db(self):
        """加载GeoIP数据库"""
        try:
            import geoip2.database
            self.geo_ip_db = geoip2.database.Reader('GeoLite2-City.mmdb')
        except:
            self.geo_ip_db = None
    
    def _start_performance_monitoring(self):
        """启动性能监控"""
        import threading
        
        def monitor_performance():
            while True:
                time.sleep(300)  # 每5分钟监控一次
                self._update_performance_metrics()
        
        thread = threading.Thread(target=monitor_performance, daemon=True)
        thread.start()
    
    async def _update_performance_metrics(self):
        """更新性能指标"""
        # 测试每个CDN的性能
        test_file = "test-1k.bin"  # 1KB测试文件
        
        for provider, backend in self.orchestrator.backends.items():
            try:
                # 获取测试文件URL
                cdn_file = await backend.get_file(test_file)
                if not cdn_file:
                    continue
                
                url = cdn_file.get_cdn_url(provider)
                if not url:
                    continue
                
                # 测量延迟和下载速度
                latency, speed = await self._measure_cdn_performance(url)
                
                # 存储指标
                self.performance_metrics[provider].append({
                    'timestamp': time.time(),
                    'latency': latency,
                    'speed': speed,
                })
                
                # 保留最近100个样本
                if len(self.performance_metrics[provider]) > 100:
                    self.performance_metrics[provider].pop(0)
                    
            except Exception as e:
                print(f"Failed to measure performance for {provider}: {e}")
    
    async def _measure_cdn_performance(self, url: str) -> Tuple[float, float]:
        """测量CDN性能"""
        import aiohttp
        import asyncio
        
        start_time = time.time()
        
        try:
            async with aiohttp.ClientSession() as session:
                # 测量延迟(HEAD请求)
                head_start = time.time()
                async with session.head(url) as response:
                    head_latency = time.time() - head_start
                
                # 测量下载速度(GET前1MB)
                get_start = time.time()
                async with session.get(url, headers={'Range': 'bytes=0-1048575'}) as response:
                    content = await response.read()
                    download_time = time.time() - get_start
                
                speed = len(content) / download_time if download_time > 0 else 0
                
                # 综合延迟(加权平均)
                latency = head_latency * 0.3 + (download_time / 2) * 0.7
                
                return latency, speed
                
        except Exception as e:
            return float('inf'), 0
    
    def get_client_location(self, client_ip: str) -> Optional[Dict[str, str]]:
        """获取客户端位置"""
        if not self.geo_ip_db or not client_ip:
            return None
        
        try:
            response = self.geo_ip_db.city(client_ip)
            return {
                'country': response.country.iso_code,
                'city': response.city.name,
                'latitude': response.location.latitude,
                'longitude': response.location.longitude,
            }
        except:
            return None
    
    async def route_request(
        self,
        file_key: str,
        client_ip: str = None,
        user_agent: str = None
    ) -> Optional[str]:
        """路由请求到最佳CDN"""
        # 获取文件信息
        cdn_file = self.orchestrator.file_registry.get(file_key)
        if not cdn_file:
            return None
        
        # 获取客户端位置
        location = self.get_client_location(client_ip) if client_ip else None
        
        # 选择最佳CDN
        best_provider = await self._select_best_cdn(location)
        
        if best_provider and best_provider in cdn_file.cdn_urls:
            return cdn_file.cdn_urls[best_provider]
        
        # 回退到第一个可用URL
        if cdn_file.cdn_urls:
            return next(iter(cdn_file.cdn_urls.values()))
        
        return None
    
    async def _select_best_cdn(self, location: Optional[Dict[str, str]]) -> Optional[CDNProvider]:
        """选择最佳CDN提供商"""
        if not self.orchestrator.backends:
            return None
        
        # 基于位置的路由规则
        if location:
            country = location.get('country')
            
            # 简单的地理路由规则
            if country in ['CN', 'JP', 'KR']:
                # 亚洲用户优先使用本地CDN
                for provider in self.orchestrator.backends.keys():
                    if provider.value in ['akamai', 'cloudflare']:
                        return provider
            
            elif country in ['US', 'CA', 'MX']:
                # 北美用户
                for provider in self.orchestrator.backends.keys():
                    if provider == CDNProvider.AWS_CLOUDFRONT:
                        return provider
            
            elif country in ['GB', 'DE', 'FR', 'IT', 'ES']:
                # 欧洲用户
                for provider in self.orchestrator.backends.keys():
                    if provider == CDNProvider.CLOUDFLARE:
                        return provider
        
        # 基于性能的路由
        best_provider = None
        best_score = float('inf')
        
        for provider, metrics in self.performance_metrics.items():
            if not metrics:
                continue
            
            # 计算最近性能得分
            recent_metrics = metrics[-10:]  # 最近10个样本
            avg_latency = sum(m['latency'] for m in recent_metrics) / len(recent_metrics)
            avg_speed = sum(m['speed'] for m in recent_metrics) / len(recent_metrics)
            
            # 性能得分 = 延迟 * 0.7 + (1 / 速度) * 0.3
            # 速度转换为MB/s,避免除以0
            speed_mbps = avg_speed / 1024 / 1024 if avg_speed > 0 else 0.1
            score = avg_latency * 0.7 + (1 / speed_mbps) * 0.3
            
            if score < best_score:
                best_score = score
                best_provider = provider
        
        return best_provider or next(iter(self.orchestrator.backends.keys()))


# 使用示例
async def cdn_example():
    """CDN集成示例"""
    
    # 创建CDN编排器
    orchestrator = CDNOrchestrator()
    
    # 配置AWS CloudFront后端
    aws_config = {
        'aws_access_key_id': 'YOUR_KEY',
        'aws_secret_access_key': 'YOUR_SECRET',
        'bucket': 'my-static-assets',
        'region': 'us-east-1',
        'cloudfront_distribution': 'd123.cloudfront.net'
    }
    aws_backend = S3CDNBackend(aws_config)
    orchestrator.register_backend(CDNProvider.AWS_CLOUDFRONT, aws_backend)
    
    # 配置Cloudflare R2后端
    cf_config = {
        'endpoint_url': 'https://r2.cloudflarestorage.com',
        'access_key_id': 'YOUR_CF_KEY',
        'secret_access_key': 'YOUR_CF_SECRET',
        'bucket': 'my-assets'
    }
    cf_backend = S3CDNBackend(cf_config)
    orchestrator.register_backend(CDNProvider.CLOUDFLARE, cf_backend)
    
    # 创建智能路由器
    router = IntelligentCDNRouter(orchestrator)
    
    # 上传文件到所有CDN
    results = await orchestrator.upload_to_all(
        local_path='./dist/main.js',
        remote_key='js/main.js',
        content_type='application/javascript',
        cache_control='public, max-age=31536000, immutable',
        metadata={'version': '1.0.0'}
    )
    
    print(f"Uploaded to {len(results)} CDNs")
    
    # 获取最佳CDN URL
    best_url = await router.route_request(
        file_key='js/main.js',
        client_ip='8.8.8.8'  # 示例IP
    )
    
    print(f"Best CDN URL: {best_url}")
    
    # 获取文件统计
    stats = orchestrator.get_file_stats('js/main.js')
    if stats:
        print(f"File stats: {stats}")
    
    # 清除缓存
    purge_results = await orchestrator.purge_all_caches([best_url])
    print(f"Cache purge results: {purge_results}")


# 运行示例
if __name__ == "__main__":
    import asyncio
    asyncio.run(cdn_example())

3.2 静态资源优化算法

资源优化可以通过以下数学模型表示:

文件合并优化问题

min ⁡ P ∑ b ∈ B ( c request + ∑ f ∈ b s f b w ) \min_{P} \sum_{b \in B} \left( c_{\text{request}} + \frac{\sum_{f \in b} s_f}{bw} \right) Pminb∈B∑(crequest+bw∑f∈bsf)

其中:

  • P P P 是文件分块方案
  • B B B 是分块集合
  • c request c_{\text{request}} crequest 是HTTP请求开销
  • s f s_f sf 是文件大小
  • b w bw bw 是带宽

解决方案使用贪心算法

python 复制代码
class ResourceOptimizer:
    """静态资源优化器"""
    
    @staticmethod
    def optimize_bundling(
        files: List[Dict[str, Any]],
        max_bundle_size: int = 244 * 1024,  # 244KB
        max_bundles: int = 10
    ) -> List[List[str]]:
        """
        优化文件打包
        
        Args:
            files: 文件列表,每个文件包含 'path' 和 'size'
            max_bundle_size: 最大包大小
            max_bundles: 最大包数量
            
        Returns:
            打包方案
        """
        # 按类型分组
        type_groups = defaultdict(list)
        for file in files:
            ext = Path(file['path']).suffix
            type_groups[ext].append(file)
        
        bundles = []
        
        # 处理每种类型的文件
        for ext, group in type_groups.items():
            # 按大小排序
            group.sort(key=lambda x: x['size'], reverse=True)
            
            current_bundle = []
            current_size = 0
            
            for file in group:
                if (file['size'] > max_bundle_size or
                    current_size + file['size'] > max_bundle_size or
                    len(current_bundle) >= max_bundles):
                    
                    if current_bundle:
                        bundles.append([f['path'] for f in current_bundle])
                        current_bundle = []
                        current_size = 0
                
                if file['size'] <= max_bundle_size:
                    current_bundle.append(file)
                    current_size += file['size']
            
            if current_bundle:
                bundles.append([f['path'] for f in current_bundle])
        
        return bundles
    
    @staticmethod
    def calculate_optimization_gain(
        original_requests: int,
        optimized_requests: int,
        avg_latency: float = 100,  # ms
        bandwidth: float = 5  # MB/s
    ) -> Dict[str, float]:
        """
        计算优化收益
        
        Returns:
            优化指标
        """
        # 请求时间节省
        request_saving = (original_requests - optimized_requests) * avg_latency / 1000
        
        # 假设每个请求平均10KB头部
        header_saving = (original_requests - optimized_requests) * 10 * 1024 / bandwidth
        
        total_saving = request_saving + header_saving
        
        return {
            'request_saving_seconds': request_saving,
            'header_saving_seconds': header_saving,
            'total_saving_seconds': total_saving,
            'improvement_percentage': (1 - optimized_requests / original_requests) * 100
        }

4. 模板引擎设计与实现 {#模板引擎设计}

4.1 模板引擎核心架构

python 复制代码
"""
高级模板引擎实现
支持:继承、包含、宏、过滤器、控制结构、沙箱安全
"""
import re
import ast
import types
import builtins
from typing import Any, Dict, List, Optional, Callable, Union, Tuple
from dataclasses import dataclass, field
from enum import Enum
import inspect
import hashlib
import time
import threading
from contextlib import contextmanager
from collections import defaultdict


class TokenType(Enum):
    """模板令牌类型"""
    TEXT = "text"
    VARIABLE = "variable"
    BLOCK_START = "block_start"
    BLOCK_END = "block_end"
    COMMENT = "comment"
    FILTER = "filter"
    MACRO = "macro"
    INCLUDE = "include"
    EXTENDS = "extends"


@dataclass
class TemplateToken:
    """模板令牌"""
    type: TokenType
    value: str
    line: int
    column: int
    meta: Dict[str, Any] = field(default_factory=dict)


@dataclass
class ASTNode:
    """抽象语法树节点"""
    type: str
    children: List['ASTNode'] = field(default_factory=list)
    value: Any = None
    line: int = 0
    column: int = 0
    
    def __str__(self, level=0):
        result = "  " * level + f"{self.type}"
        if self.value is not None:
            result += f": {self.value}"
        result += "\n"
        for child in self.children:
            result += child.__str__(level + 1)
        return result


class TemplateSecurityError(Exception):
    """模板安全错误"""
    pass


class SandboxSecurity:
    """沙箱安全机制"""
    
    SAFE_BUILTINS = {
        'bool', 'int', 'float', 'str', 'list', 'tuple', 'dict', 'set',
        'len', 'range', 'enumerate', 'zip', 'min', 'max', 'sum', 'abs',
        'round', 'sorted', 'reversed', 'any', 'all',
    }
    
    UNSAFE_ATTRIBUTES = {
        '__class__', '__dict__', '__globals__', '__code__', '__func__',
        '__self__', '__bases__', '__subclasses__', '__import__', 'eval',
        'exec', 'compile', 'open', 'input', 'exit', 'quit',
    }
    
    def __init__(self, safe_mode: bool = True):
        self.safe_mode = safe_mode
        self.allowed_filters = set()
        self.allowed_functions = set()
        self.max_depth = 10
        self.max_iterations = 1000
        
    def is_safe_identifier(self, name: str) -> bool:
        """检查标识符是否安全"""
        if name in self.UNSAFE_ATTRIBUTES:
            return False
        
        # 检查危险模式
        dangerous_patterns = [
            r'^_{2}.*_{2}$',  # 双下划线
            r'^[A-Z_][A-Z0-9_]*$',  # 常量(可能用于访问配置)
        ]
        
        for pattern in dangerous_patterns:
            if re.match(pattern, name):
                return False
        
        return True
    
    def is_safe_call(self, func_name: str, args: List[Any]) -> bool:
        """检查函数调用是否安全"""
        if not self.is_safe_identifier(func_name):
            return False
        
        # 检查内置函数
        if func_name in builtins.__dict__:
            return func_name in self.SAFE_BUILTINS
        
        return True
    
    def sanitize_value(self, value: Any) -> Any:
        """净化值"""
        if isinstance(value, str):
            # 转义HTML特殊字符
            return (
                value.replace('&', '&amp;')
                     .replace('<', '&lt;')
                     .replace('>', '&gt;')
                     .replace('"', '&quot;')
                     .replace("'", '&#x27;')
            )
        return value


class TemplateLexer:
    """模板词法分析器"""
    
    # 正则表达式模式
    PATTERNS = {
        TokenType.VARIABLE: r'\{\{\s*(.*?)\s*\}\}',
        TokenType.BLOCK_START: r'\{%\s*(.*?)\s*%\}',
        TokenType.BLOCK_END: r'\{%\s*end(.*?)\s*%\}',
        TokenType.COMMENT: r'\{#\s*(.*?)\s*#\}',
        TokenType.MACRO: r'\{%\s*macro\s+(.*?)\s*%\}',
        TokenType.INCLUDE: r'\{%\s*include\s+["\'](.*?)["\']\s*%\}',
        TokenType.EXTENDS: r'\{%\s*extends\s+["\'](.*?)["\']\s*%\}',
    }
    
    def __init__(self, security: SandboxSecurity = None):
        self.security = security or SandboxSecurity()
        self._compile_patterns()
    
    def _compile_patterns(self):
        """编译正则表达式模式"""
        self.compiled_patterns = {}
        for token_type, pattern in self.PATTERNS.items():
            self.compiled_patterns[token_type] = re.compile(pattern)
    
    def tokenize(self, source: str) -> List[TemplateToken]:
        """将模板源代码转换为令牌序列"""
        tokens = []
        pos = 0
        line = 1
        column = 1
        
        while pos < len(source):
            # 寻找下一个模板标记
            next_token = None
            next_pos = len(source)
            
            for token_type, pattern in self.compiled_patterns.items():
                match = pattern.search(source, pos)
                if match and match.start() < next_pos:
                    next_token = (token_type, match)
                    next_pos = match.start()
            
            # 添加文本令牌
            if next_pos > pos:
                text = source[pos:next_pos]
                tokens.append(TemplateToken(
                    type=TokenType.TEXT,
                    value=text,
                    line=line,
                    column=column
                ))
                
                # 更新行列计数
                line_breaks = text.count('\n')
                if line_breaks > 0:
                    line += line_breaks
                    column = len(text) - text.rfind('\n')
                else:
                    column += len(text)
                
                pos = next_pos
            
            # 添加模板令牌
            if next_token:
                token_type, match = next_token
                token_value = match.group(1).strip()
                
                tokens.append(TemplateToken(
                    type=token_type,
                    value=token_value,
                    line=line,
                    column=column,
                    meta={'match': match}
                ))
                
                # 更新位置
                token_text = match.group(0)
                line_breaks = token_text.count('\n')
                if line_breaks > 0:
                    line += line_breaks
                    column = len(token_text) - token_text.rfind('\n')
                else:
                    column += len(token_text)
                
                pos = match.end()
        
        return tokens


class TemplateParser:
    """模板语法分析器"""
    
    def __init__(self, security: SandboxSecurity = None):
        self.security = security or SandboxSecurity()
        self.lexer = TemplateLexer(security)
        
    def parse(self, source: str) -> ASTNode:
        """解析模板为AST"""
        tokens = self.lexer.tokenize(source)
        return self._parse_tokens(tokens)
    
    def _parse_tokens(self, tokens: List[TemplateToken]) -> ASTNode:
        """解析令牌序列为AST"""
        root = ASTNode(type='root')
        stack = [root]
        i = 0
        
        while i < len(tokens):
            token = tokens[i]
            
            if token.type == TokenType.TEXT:
                node = ASTNode(
                    type='text',
                    value=token.value,
                    line=token.line,
                    column=token.column
                )
                stack[-1].children.append(node)
                
            elif token.type == TokenType.VARIABLE:
                # 解析变量表达式
                expr_node = self._parse_expression(token.value)
                node = ASTNode(
                    type='variable',
                    children=[expr_node],
                    line=token.line,
                    column=token.column
                )
                stack[-1].children.append(node)
                
            elif token.type == TokenType.BLOCK_START:
                block_type, *args = token.value.split(maxsplit=1)
                block_args = args[0] if args else ""
                
                if block_type in ('if', 'for', 'macro', 'block'):
                    # 开始块
                    node = ASTNode(
                        type=block_type,
                        value=block_args,
                        line=token.line,
                        column=token.column
                    )
                    stack[-1].children.append(node)
                    stack.append(node)
                    
                elif block_type in ('elif', 'else'):
                    # 中间块
                    if stack[-1].type not in ('if', 'for'):
                        raise SyntaxError(f"Unexpected {block_type}")
                    
                    # 结束当前块
                    current_block = stack.pop()
                    if not stack:
                        raise SyntaxError(f"Unmatched {block_type}")
                    
                    # 创建新分支
                    node = ASTNode(
                        type=block_type,
                        value=block_args,
                        line=token.line,
                        column=token.column
                    )
                    stack[-1].children.append(node)
                    stack.append(node)
                    
                elif block_type == 'end':
                    # 结束块
                    if len(stack) <= 1:
                        raise SyntaxError("Unmatched end tag")
                    stack.pop()
                    
                elif block_type == 'include':
                    # 包含指令
                    node = ASTNode(
                        type='include',
                        value=block_args.strip('"\''),
                        line=token.line,
                        column=token.column
                    )
                    stack[-1].children.append(node)
                    
                elif block_type == 'extends':
                    # 扩展指令
                    node = ASTNode(
                        type='extends',
                        value=block_args.strip('"\''),
                        line=token.line,
                        column=token.column
                    )
                    root.children.insert(0, node)  # extends必须在最前面
                    
                else:
                    raise SyntaxError(f"Unknown block type: {block_type}")
                
            elif token.type == TokenType.COMMENT:
                # 忽略注释
                pass
                
            elif token.type == TokenType.FILTER:
                # 过滤器
                filter_name, *filter_args = token.value.split('|')
                node = ASTNode(
                    type='filter',
                    value=filter_name.strip(),
                    line=token.line,
                    column=token.column
                )
                stack[-1].children.append(node)
                
            i += 1
        
        if len(stack) != 1:
            raise SyntaxError("Unclosed blocks")
        
        return root
    
    def _parse_expression(self, expr: str) -> ASTNode:
        """解析表达式"""
        # 简化版的表达式解析
        # 在实际应用中,应该使用完整的表达式解析器
        
        expr = expr.strip()
        
        # 检查过滤器
        if '|' in expr:
            parts = [p.strip() for p in expr.split('|')]
            variable = parts[0]
            filters = parts[1:]
            
            node = ASTNode(type='variable', value=variable)
            
            for filter_expr in filters:
                filter_name, *filter_args = filter_expr.split(':')
                filter_node = ASTNode(
                    type='filter',
                    value=filter_name.strip(),
                    line=0,
                    column=0
                )
                
                if filter_args:
                    args_node = ASTNode(
                        type='arguments',
                        value=', '.join(filter_args),
                        line=0,
                        column=0
                    )
                    filter_node.children.append(args_node)
                
                node = ASTNode(
                    type='filter_chain',
                    children=[node, filter_node],
                    line=0,
                    column=0
                )
            
            return node
        
        # 简单变量
        return ASTNode(type='variable', value=expr)


class TemplateCompiler:
    """模板编译器"""
    
    def __init__(self, security: SandboxSecurity = None):
        self.security = security or SandboxSecurity()
        self.parser = TemplateParser(security)
        
        # 编译缓存
        self.compiled_cache = {}
        self.cache_lock = threading.RLock()
        
    def compile(self, source: str, template_name: str = None) -> Callable:
        """编译模板为可执行函数"""
        cache_key = hashlib.md5(source.encode()).hexdigest()
        
        with self.cache_lock:
            if cache_key in self.compiled_cache:
                return self.compiled_cache[cache_key]
        
        # 解析为AST
        ast_root = self.parser.parse(source)
        
        # 生成Python代码
        python_code = self._generate_code(ast_root, template_name)
        
        # 编译代码
        try:
            code_obj = compile(python_code, f'<template:{template_name}>', 'exec')
            
            # 创建命名空间
            namespace = {
                '__builtins__': self._create_safe_builtins(),
                '_escape': self.security.sanitize_value,
                '_format': self._format_value,
            }
            
            # 执行代码创建渲染函数
            exec(code_obj, namespace)
            render_func = namespace['render']
            
            # 缓存结果
            with self.cache_lock:
                self.compiled_cache[cache_key] = render_func
            
            return render_func
            
        except Exception as e:
            raise TemplateSecurityError(f"Compilation error: {e}")
    
    def _create_safe_builtins(self) -> dict:
        """创建安全的builtins字典"""
        safe_builtins = {}
        
        for name in self.security.SAFE_BUILTINS:
            if hasattr(builtins, name):
                safe_builtins[name] = getattr(builtins, name)
        
        return types.ModuleType('safe_builtins', safe_builtins)
    
    def _generate_code(self, node: ASTNode, template_name: str) -> str:
        """从AST生成Python代码"""
        lines = []
        
        # 函数定义
        lines.append('def render(context):')
        lines.append('    _result = []')
        lines.append('    _buf = _result.append')
        
        # 生成主体代码
        self._generate_node_code(node, lines, '    ')
        
        lines.append('    return "".join(_result)')
        
        return '\n'.join(lines)
    
    def _generate_node_code(self, node: ASTNode, lines: List[str], indent: str):
        """生成节点代码"""
        if node.type == 'text':
            lines.append(f'{indent}_buf({repr(node.value)})')
            
        elif node.type == 'variable':
            var_code = self._generate_variable_code(node.value)
            lines.append(f'{indent}_buf(_escape({var_code}))')
            
        elif node.type == 'filter_chain':
            # 处理过滤器链
            value_code = self._generate_node_value(node.children[0])
            
            for filter_node in node.children[1:]:
                filter_name = filter_node.value
                filter_args = []
                
                if filter_node.children:
                    args_node = filter_node.children[0]
                    if args_node.type == 'arguments':
                        filter_args = [a.strip() for a in args_node.value.split(',')]
                
                # 应用过滤器
                if filter_name == 'safe':
                    value_code = value_code  # 不转义
                elif filter_name == 'lower':
                    value_code = f'{value_code}.lower()'
                elif filter_name == 'upper':
                    value_code = f'{value_code}.upper()'
                elif filter_name == 'title':
                    value_code = f'{value_code}.title()'
                elif filter_name == 'capitalize':
                    value_code = f'{value_code}.capitalize()'
                elif filter_name == 'trim':
                    value_code = f'{value_code}.strip()'
                elif filter_name == 'length':
                    value_code = f'len({value_code})'
                elif filter_name == 'default':
                    default = filter_args[0] if filter_args else "''"
                    value_code = f'({value_code} if {value_code} is not None else {default})'
                else:
                    # 自定义过滤器
                    value_code = f'_filters.get("{filter_name}", lambda x: x)({value_code})'
            
            lines.append(f'{indent}_buf({value_code})')
            
        elif node.type == 'if':
            condition = node.value
            lines.append(f'{indent}if {condition}:')
            
            for child in node.children:
                if child.type in ('elif', 'else'):
                    lines.append(f'{indent}else:')
                    self._generate_node_code(child, lines, indent + '    ')
                else:
                    self._generate_node_code(child, lines, indent + '    ')
                    
        elif node.type == 'for':
            # 解析for循环: item in items
            match = re.match(r'(\w+)\s+in\s+(.+)$', node.value)
            if not match:
                raise SyntaxError(f"Invalid for loop: {node.value}")
            
            item_var, items_expr = match.groups()
            lines.append(f'{indent}for {item_var} in {items_expr}:')
            
            for child in node.children:
                self._generate_node_code(child, lines, indent + '    ')
                
        elif node.type == 'include':
            # 包含指令
            template_path = node.value
            lines.append(f'{indent}# Include: {template_path}')
            lines.append(f'{indent}_buf(_include("{template_path}", context))')
            
        else:
            # 忽略其他节点
            for child in node.children:
                self._generate_node_code(child, lines, indent)
    
    def _generate_variable_code(self, var_expr: str) -> str:
        """生成变量访问代码"""
        parts = var_expr.split('.')
        code = f"context.get('{parts[0]}', '')"
        
        for part in parts[1:]:
            if part.isdigit():
                code = f"{code}[{part}]"
            else:
                code = f"getattr({code}, '{part}', '')"
        
        return code
    
    def _generate_node_value(self, node: ASTNode) -> str:
        """生成节点值代码"""
        if node.type == 'variable':
            return self._generate_variable_code(node.value)
        elif node.type == 'text':
            return repr(node.value)
        else:
            return '""'
    
    def _format_value(self, value: Any, format_spec: str = '') -> str:
        """格式化值"""
        if hasattr(value, '__format__'):
            return format(value, format_spec)
        return str(value)


class TemplateEngine:
    """模板引擎主类"""
    
    def __init__(
        self,
        template_dirs: List[str] = None,
        auto_reload: bool = False,
        safe_mode: bool = True,
        cache_size: int = 100
    ):
        self.template_dirs = template_dirs or ['.']
        self.auto_reload = auto_reload
        self.cache_size = cache_size
        
        # 安全设置
        self.security = SandboxSecurity(safe_mode)
        
        # 编译器
        self.compiler = TemplateCompiler(self.security)
        
        # 模板缓存
        self.template_cache = {}
        self.file_mtimes = {}
        
        # 过滤器注册表
        self.filters = {}
        self._register_default_filters()
        
        # 全局上下文
        self.global_context = {}
        
        # 统计信息
        self.stats = {
            'templates_compiled': 0,
            'cache_hits': 0,
            'cache_misses': 0,
            'render_time': 0,
        }
    
    def _register_default_filters(self):
        """注册默认过滤器"""
        self.filters.update({
            'upper': str.upper,
            'lower': str.lower,
            'title': str.title,
            'capitalize': str.capitalize,
            'trim': str.strip,
            'length': len,
            'default': lambda x, default='': x if x is not None else default,
            'join': lambda x, sep='': sep.join(str(i) for i in x),
            'slice': lambda x, start=0, end=None: x[start:end],
            'replace': lambda x, old, new, count=-1: x.replace(old, new, count),
        })
    
    def add_filter(self, name: str, func: Callable):
        """添加自定义过滤器"""
        if not self.security.is_safe_identifier(name):
            raise TemplateSecurityError(f"Unsafe filter name: {name}")
        
        # 检查函数安全性
        if not self._is_safe_function(func):
            raise TemplateSecurityError(f"Unsafe filter function: {name}")
        
        self.filters[name] = func
    
    def _is_safe_function(self, func: Callable) -> bool:
        """检查函数是否安全"""
        # 简化检查:在实际应用中应该更严格
        try:
            source = inspect.getsource(func)
            
            # 检查危险操作
            dangerous_patterns = [
                r'__import__\s*\(',
                r'eval\s*\(',
                r'exec\s*\(',
                r'compile\s*\(',
                r'open\s*\(',
                r'\.__',
            ]
            
            for pattern in dangerous_patterns:
                if re.search(pattern, source):
                    return False
                    
        except:
            # 无法获取源代码,可能是内置函数或C扩展
            pass
        
        return True
    
    def _find_template(self, name: str) -> Optional[Path]:
        """查找模板文件"""
        for template_dir in self.template_dirs:
            path = Path(template_dir) / name
            if path.exists():
                return path
        
        # 检查是否有扩展名
        if '.' not in name:
            for ext in ['.html', '.htm', '.jinja', '.jinja2', '.tmpl']:
                for template_dir in self.template_dirs:
                    path = Path(template_dir) / (name + ext)
                    if path.exists():
                        return path
        
        return None
    
    def _load_template_source(self, name: str) -> Optional[Tuple[str, float]]:
        """加载模板源代码"""
        path = self._find_template(name)
        if not path:
            return None
        
        try:
            with open(path, 'r', encoding='utf-8') as f:
                source = f.read()
            
            mtime = path.stat().st_mtime
            return source, mtime
            
        except Exception as e:
            raise IOError(f"Cannot load template {name}: {e}")
    
    def _compile_template(self, name: str) -> Optional[Callable]:
        """编译模板"""
        result = self._load_template_source(name)
        if not result:
            return None
        
        source, mtime = result
        
        # 检查是否需要重新编译
        cache_key = f"{name}:{mtime}"
        
        if not self.auto_reload and cache_key in self.template_cache:
            self.stats['cache_hits'] += 1
            return self.template_cache[cache_key]
        
        self.stats['cache_misses'] += 1
        self.stats['templates_compiled'] += 1
        
        # 编译模板
        try:
            render_func = self.compiler.compile(source, name)
            
            # 包装渲染函数以提供过滤器
            def wrapped_render(context):
                # 合并全局上下文
                full_context = {**self.global_context, **context}
                full_context['_filters'] = self.filters
                
                # 渲染
                return render_func(full_context)
            
            # 缓存结果
            if not self.auto_reload:
                self.template_cache[cache_key] = wrapped_render
                
                # 限制缓存大小
                if len(self.template_cache) > self.cache_size:
                    # 移除最旧的项目
                    oldest_key = next(iter(self.template_cache))
                    del self.template_cache[oldest_key]
            
            return wrapped_render
            
        except Exception as e:
            raise TemplateSecurityError(f"Template compilation failed: {e}")
    
    def render(self, name: str, context: Dict[str, Any] = None) -> str:
        """渲染模板"""
        start_time = time.time()
        
        try:
            # 编译或获取模板
            render_func = self._compile_template(name)
            if not render_func:
                raise ValueError(f"Template not found: {name}")
            
            # 渲染
            result = render_func(context or {})
            
            # 更新统计
            render_time = time.time() - start_time
            self.stats['render_time'] += render_time
            
            return result
            
        except Exception as e:
            raise TemplateSecurityError(f"Template rendering failed: {e}")
    
    def render_string(self, source: str, context: Dict[str, Any] = None) -> str:
        """渲染模板字符串"""
        try:
            render_func = self.compiler.compile(source, '<string>')
            
            # 包装渲染函数
            def wrapped_render(ctx):
                full_context = {**self.global_context, **(ctx or {})}
                full_context['_filters'] = self.filters
                return render_func(full_context)
            
            return wrapped_render(context or {})
            
        except Exception as e:
            raise TemplateSecurityError(f"String template rendering failed: {e}")
    
    def add_global(self, name: str, value: Any):
        """添加全局变量"""
        if not self.security.is_safe_identifier(name):
            raise TemplateSecurityError(f"Unsafe global name: {name}")
        
        self.global_context[name] = value
    
    def clear_cache(self):
        """清除模板缓存"""
        self.template_cache.clear()
        self.file_mtimes.clear()
    
    def get_stats(self) -> Dict[str, Any]:
        """获取引擎统计信息"""
        avg_render_time = (
            self.stats['render_time'] / max(self.stats['templates_compiled'], 1)
        )
        
        cache_hit_rate = (
            self.stats['cache_hits'] / (self.stats['cache_hits'] + self.stats['cache_misses'])
            if (self.stats['cache_hits'] + self.stats['cache_misses']) > 0 else 0
        )
        
        return {
            **self.stats,
            'avg_render_time_ms': avg_render_time * 1000,
            'cache_hit_rate': f"{cache_hit_rate:.2%}",
            'cache_size': len(self.template_cache),
            'filters_count': len(self.filters),
            'global_vars_count': len(self.global_context),
        }


# 使用示例
if __name__ == "__main__":
    # 创建模板引擎
    engine = TemplateEngine(
        template_dirs=['./templates'],
        auto_reload=True,
        safe_mode=True,
        cache_size=50
    )
    
    # 添加自定义过滤器
    @engine.add_filter
    def reverse(value):
        """反转字符串"""
        if isinstance(value, str):
            return value[::-1]
        return value
    
    @engine.add_filter  
    def currency(value, symbol='$'):
        """货币格式化"""
        try:
            num = float(value)
            return f"{symbol}{num:,.2f}"
        except (ValueError, TypeError):
            return value
    
    # 添加全局变量
    engine.add_global('site_name', 'My Awesome Site')
    engine.add_global('current_year', 2024)
    
    # 示例模板
    template_source = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>{{ site_name }} - {{ page_title|default('Home') }}</title>
    </head>
    <body>
        <h1>Welcome to {{ site_name }}</h1>
        
        {% if user %}
            <p>Hello, {{ user.name|upper }}!</p>
            <p>Your balance: {{ user.balance|currency('€') }}</p>
        {% else %}
            <p>Please log in.</p>
        {% endif %}
        
        <h2>Items</h2>
        <ul>
        {% for item in items %}
            <li>{{ item.name }} - {{ item.price|currency }}</li>
        {% else %}
            <li>No items found.</li>
        {% endfor %}
        </ul>
        
        <p>© {{ current_year }} {{ site_name }}</p>
    </body>
    </html>
    """
    
    # 渲染模板
    context = {
        'page_title': 'Dashboard',
        'user': {
            'name': 'john doe',
            'balance': 1234.56
        },
        'items': [
            {'name': 'Product A', 'price': 29.99},
            {'name': 'Product B', 'price': 49.99},
            {'name': 'Product C', 'price': 19.99},
        ]
    }
    
    try:
        result = engine.render_string(template_source, context)
        print("Rendered template:")
        print(result)
        
    except TemplateSecurityError as e:
        print(f"Security error: {e}")
    
    # 显示统计信息
    print("\nTemplate Engine Statistics:")
    for key, value in engine.get_stats().items():
        print(f"  {key}: {value}")

5. 高性能模板渲染系统 {#高性能模板}

5.1 编译期优化技术

python 复制代码
"""
编译期模板优化
支持:常量折叠、死代码消除、循环展开、预计算
"""
import ast
import dis
from typing import Dict, List, Set, Tuple
from dataclasses import dataclass, field
from collections import defaultdict


class TemplateOptimizer:
    """模板优化器"""
    
    def __init__(self):
        self.optimizations_applied = defaultdict(int)
        
    def optimize_ast(self, node: ast.AST) -> ast.AST:
        """优化AST"""
        # 应用优化传递
        old_node = None
        iteration = 0
        
        while node != old_node and iteration < 10:  # 最多10次迭代
            old_node = ast.fix_missing_locations(ast.copy_location(node, node))
            
            # 应用优化
            node = self._constant_folding(node)
            node = self._dead_code_elimination(node)
            node = self._loop_unrolling(node)
            node = self._common_subexpression_elimination(node)
            node = self._inline_small_functions(node)
            
            iteration += 1
        
        return node
    
    def _constant_folding(self, node: ast.AST) -> ast.AST:
        """常量折叠"""
        if isinstance(node, ast.BinOp):
            # 尝试计算常量表达式
            try:
                left = self._evaluate_constant(node.left)
                right = self._evaluate_constant(node.right)
                
                if left is not None and right is not None:
                    result = self._apply_operator(node.op, left, right)
                    if result is not None:
                        self.optimizations_applied['constant_folding'] += 1
                        return ast.Constant(value=result)
            except:
                pass
        
        # 递归处理子节点
        return self._visit_children(node, self._constant_folding)
    
    def _evaluate_constant(self, node: ast.AST):
        """评估常量表达式"""
        if isinstance(node, ast.Constant):
            return node.value
        
        if isinstance(node, ast.UnaryOp):
            operand = self._evaluate_constant(node.operand)
            if operand is None:
                return None
            
            if isinstance(node.op, ast.UAdd):
                return +operand
            elif isinstance(node.op, ast.USub):
                return -operand
            elif isinstance(node.op, ast.Not):
                return not operand
        
        if isinstance(node, ast.BinOp):
            left = self._evaluate_constant(node.left)
            right = self._evaluate_constant(node.right)
            
            if left is None or right is None:
                return None
            
            return self._apply_operator(node.op, left, right)
        
        return None
    
    def _apply_operator(self, op, left, right):
        """应用运算符"""
        try:
            if isinstance(op, ast.Add):
                return left + right
            elif isinstance(op, ast.Sub):
                return left - right
            elif isinstance(op, ast.Mult):
                return left * right
            elif isinstance(op, ast.Div):
                return left / right
            elif isinstance(op, ast.FloorDiv):
                return left // right
            elif isinstance(op, ast.Mod):
                return left % right
            elif isinstance(op, ast.Pow):
                return left ** right
            elif isinstance(op, ast.LShift):
                return left << right
            elif isinstance(op, ast.RShift):
                return left >> right
            elif isinstance(op, ast.BitOr):
                return left | right
            elif isinstance(op, ast.BitAnd):
                return left & right
            elif isinstance(op, ast.BitXor):
                return left ^ right
            elif isinstance(op, ast.And):
                return left and right
            elif isinstance(op, ast.Or):
                return left or right
            elif isinstance(op, ast.Eq):
                return left == right
            elif isinstance(op, ast.NotEq):
                return left != right
            elif isinstance(op, ast.Lt):
                return left < right
            elif isinstance(op, ast.LtE):
                return left <= right
            elif isinstance(op, ast.Gt):
                return left > right
            elif isinstance(op, ast.GtE):
                return left >= right
        except Exception:
            return None
    
    def _dead_code_elimination(self, node: ast.AST) -> ast.AST:
        """死代码消除"""
        if isinstance(node, ast.If):
            # 评估条件
            cond_value = self._evaluate_constant(node.test)
            
            if cond_value is True:
                # 条件为真,只保留then分支
                self.optimizations_applied['dead_code'] += 1
                return self._flatten_body(node.body)
            elif cond_value is False:
                # 条件为假,只保留else分支
                self.optimizations_applied['dead_code'] += 1
                return self._flatten_body(node.orelse)
        
        return self._visit_children(node, self._dead_code_elimination)
    
    def _flatten_body(self, body: List[ast.stmt]) -> ast.stmt:
        """扁平化语句体"""
        if len(body) == 1:
            return body[0]
        else:
            return ast.Module(body=body, type_ignores=[])
    
    def _loop_unrolling(self, node: ast.AST) -> ast.AST:
        """循环展开"""
        if isinstance(node, ast.For):
            # 检查是否可以展开
            if self._is_const_iterable(node.iter):
                iter_values = self._get_const_iterable_values(node.iter)
                
                if iter_values and len(iter_values) <= 4:  # 小循环展开
                    self.optimizations_applied['loop_unrolling'] += 1
                    
                    # 展开循环
                    statements = []
                    for value in iter_values:
                        # 创建赋值语句
                        assign = ast.Assign(
                            targets=[node.target],
                            value=ast.Constant(value=value)
                        )
                        
                        # 复制循环体,替换目标变量
                        body_copy = self._replace_target(node.body, node.target, value)
                        statements.append(assign)
                        statements.extend(body_copy)
                    
                    return ast.Module(body=statements, type_ignores=[])
        
        return self._visit_children(node, self._loop_unrolling)
    
    def _is_const_iterable(self, node: ast.AST) -> bool:
        """检查是否为常量可迭代对象"""
        if isinstance(node, (ast.List, ast.Tuple, ast.Set)):
            return all(isinstance(e, ast.Constant) for e in node.elts)
        elif isinstance(node, ast.Call):
            if isinstance(node.func, ast.Name):
                if node.func.id == 'range':
                    return all(self._evaluate_constant(arg) is not None 
                              for arg in node.args)
        return False
    
    def _get_const_iterable_values(self, node: ast.AST) -> List:
        """获取常量可迭代对象的值"""
        if isinstance(node, (ast.List, ast.Tuple, ast.Set)):
            return [e.value for e in node.elts]
        elif isinstance(node, ast.Call):
            if isinstance(node.func, ast.Name) and node.func.id == 'range':
                args = [self._evaluate_constant(arg) for arg in node.args]
                if len(args) == 1:
                    return list(range(args[0]))
                elif len(args) == 2:
                    return list(range(args[0], args[1]))
                elif len(args) == 3:
                    return list(range(args[0], args[1], args[2]))
        return []
    
    def _replace_target(self, body: List[ast.stmt], target: ast.expr, value) -> List[ast.stmt]:
        """替换目标变量"""
        # 简化实现:在实际应用中应该完整复制AST并替换
        return [ast.copy_location(stmt, stmt) for stmt in body]
    
    def _common_subexpression_elimination(self, node: ast.AST) -> ast.AST:
        """公共子表达式消除"""
        # 收集表达式
        expressions = self._collect_expressions(node)
        
        # 查找重复表达式
        seen = {}
        replacements = {}
        
        for expr_hash, expr in expressions:
            if expr_hash in seen:
                # 创建临时变量
                var_name = f"_cse_{len(replacements)}"
                replacements[expr] = var_name
            else:
                seen[expr_hash] = expr
        
        if replacements:
            self.optimizations_applied['cse'] += len(replacements)
            return self._replace_expressions(node, replacements)
        
        return node
    
    def _collect_expressions(self, node: ast.AST, parent=None) -> List[Tuple[str, ast.expr]]:
        """收集表达式"""
        expressions = []
        
        if isinstance(node, ast.expr) and not isinstance(node, (ast.Constant, ast.Name)):
            # 计算表达式哈希
            expr_hash = self._hash_expression(node)
            expressions.append((expr_hash, node))
        
        # 递归处理子节点
        for child in ast.iter_child_nodes(node):
            expressions.extend(self._collect_expressions(child, node))
        
        return expressions
    
    def _hash_expression(self, node: ast.AST) -> str:
        """计算表达式哈希"""
        # 简化实现:使用字符串表示
        return ast.dump(node)
    
    def _replace_expressions(self, node: ast.AST, replacements: Dict[ast.expr, str]) -> ast.AST:
        """替换表达式"""
        # 简化实现
        return node
    
    def _inline_small_functions(self, node: ast.AST) -> ast.AST:
        """内联小函数"""
        if isinstance(node, ast.Call):
            # 检查是否可以内联
            func = node.func
            if isinstance(func, ast.Name):
                # 在实际应用中,这里会查找函数定义并内联
                pass
        
        return self._visit_children(node, self._inline_small_functions)
    
    def _visit_children(self, node: ast.AST, visitor) -> ast.AST:
        """访问子节点"""
        for field, old_value in ast.iter_fields(node):
            if isinstance(old_value, list):
                new_values = []
                for value in old_value:
                    if isinstance(value, ast.AST):
                        value = visitor(value)
                        if value is None:
                            continue
                        elif not isinstance(value, ast.AST):
                            new_values.extend(value)
                            continue
                    new_values.append(value)
                old_value[:] = new_values
            elif isinstance(old_value, ast.AST):
                new_node = visitor(old_value)
                if new_node is None:
                    delattr(node, field)
                else:
                    setattr(node, field, new_node)
        return node
    
    def get_optimization_report(self) -> Dict[str, int]:
        """获取优化报告"""
        return dict(self.optimizations_applied)


class JITTemplateRenderer:
    """JIT模板渲染器"""
    
    def __init__(self, template_engine: TemplateEngine):
        self.engine = template_engine
        self.jit_cache = {}
        self.profile_data = defaultdict(list)
        
        # JIT编译阈值
        self.jit_threshold = 10  # 执行10次后JIT编译
        self.hot_path_threshold = 0.8  # 80%的时间在热路径上
        
    def render(self, name: str, context: Dict[str, Any] = None) -> str:
        """渲染模板,支持JIT优化"""
        render_count = self.profile_data[name].count('render') if name in self.profile_data else 0
        
        if render_count >= self.jit_threshold and name not in self.jit_cache:
            # JIT编译模板
            self._jit_compile_template(name)
        
        if name in self.jit_cache:
            # 使用JIT编译版本
            jit_func = self.jit_cache[name]
            start_time = time.time()
            result = jit_func(context or {})
            render_time = time.time() - start_time
            
            self.profile_data[name].append(('jit_render', render_time))
            return result
        else:
            # 使用解释版本
            start_time = time.time()
            result = self.engine.render(name, context)
            render_time = time.time() - start_time
            
            self.profile_data[name].append(('render', render_time))
            return result
    
    def _jit_compile_template(self, name: str):
        """JIT编译模板"""
        # 获取模板源代码
        source_data = self.engine._load_template_source(name)
        if not source_data:
            return
        
        source, _ = source_data
        
        try:
            # 分析执行热点
            hot_paths = self._analyze_hot_paths(name)
            
            # 优化热点代码
            optimized_source = self._optimize_hot_paths(source, hot_paths)
            
            # 编译为本地代码
            jit_func = self._compile_to_native(optimized_source, name)
            
            # 缓存结果
            self.jit_cache[name] = jit_func
            
            print(f"JIT compiled template: {name}")
            
        except Exception as e:
            print(f"JIT compilation failed for {name}: {e}")
    
    def _analyze_hot_paths(self, name: str) -> List[Tuple[str, float]]:
        """分析热点路径"""
        profile_entries = self.profile_data.get(name, [])
        
        if not profile_entries:
            return []
        
        # 分析执行时间分布
        # 在实际实现中,这里会使用更复杂的分析
        return [('main', 1.0)]  # 简化
    
    def _optimize_hot_paths(self, source: str, hot_paths: List[Tuple[str, float]]) -> str:
        """优化热点路径"""
        # 应用优化
        optimizer = TemplateOptimizer()
        
        # 解析为AST
        try:
            tree = ast.parse(source)
            
            # 优化AST
            optimized_tree = optimizer.optimize_ast(tree)
            
            # 生成源代码
            optimized_source = ast.unparse(optimized_tree)
            
            # 获取优化报告
            report = optimizer.get_optimization_report()
            if report:
                print(f"Optimizations applied: {report}")
            
            return optimized_source
            
        except Exception as e:
            print(f"Optimization failed: {e}")
            return source
    
    def _compile_to_native(self, source: str, name: str) -> Callable:
        """编译为本地代码"""
        # 在实际实现中,这里会使用Numba、Cython或直接生成机器码
        # 这里使用eval作为简化实现
        
        # 创建安全的执行环境
        namespace = {
            '__builtins__': self.engine.security._create_safe_builtins(),
            '_escape': self.engine.security.sanitize_value,
            '_filters': self.engine.filters,
            **self.engine.global_context,
        }
        
        # 编译代码
        code = compile(source, f'<jit:{name}>', 'eval')
        
        def jit_render(context):
            # 合并上下文
            full_context = {**namespace, **(context or {})}
            
            # 执行
            return eval(code, {}, full_context)
        
        return jit_render
    
    def get_performance_report(self) -> Dict[str, Any]:
        """获取性能报告"""
        report = {
            'jit_compiled_templates': len(self.jit_cache),
            'total_templates': len(self.profile_data),
            'performance_gain': 0.0,
        }
        
        # 计算性能提升
        total_interpreted_time = 0
        total_jit_time = 0
        
        for name, entries in self.profile_data.items():
            for entry_type, time_taken in entries:
                if entry_type == 'render':
                    total_interpreted_time += time_taken
                elif entry_type == 'jit_render':
                    total_jit_time += time_taken
        
        if total_interpreted_time > 0:
            report['performance_gain'] = (
                (total_interpreted_time - total_jit_time) / total_interpreted_time * 100
            )
        
        return report

5.2 模板缓存策略

模板缓存可以通过分层缓存架构优化:
失效机制 缓存策略 缓存层级 变更 变更 触发 清除L1-L3 文件修改时间 清除所有缓存 版本哈希 清除指定模板 手动清除 LFU 热点模板 LRU 普通模板 TTL 临时模板 永久缓存 核心模板 L2: AST缓存 L1: 编译缓存 L3: 字节码缓存 L4: 本地代码缓存 JIT

缓存命中率计算:

Hit Rate = Cache Hits Cache Hits + Cache Misses \text{Hit Rate} = \frac{\text{Cache Hits}}{\text{Cache Hits} + \text{Cache Misses}} Hit Rate=Cache Hits+Cache MissesCache Hits

多级缓存总延迟:

T total = ∑ i = 1 n p i × t i T_{\text{total}} = \sum_{i=1}^{n} p_i \times t_i Ttotal=i=1∑npi×ti

其中 p i p_i pi 是第i级缓存命中概率, t i t_i ti 是第i级缓存访问时间。

6. 完整实战案例:企业级CMS系统 {#实战案例}

python 复制代码
"""
企业级内容管理系统
集成:静态文件服务、模板渲染、CDN、缓存、安全
"""
import asyncio
import aiohttp
import aiofiles
from pathlib import Path
from typing import Dict, Any, Optional, List
from datetime import datetime
import json
import yaml
from dataclasses import dataclass, asdict
import markdown
from enum import Enum


class ContentType(Enum):
    """内容类型"""
    PAGE = "page"
    POST = "post"
    ASSET = "asset"
    TEMPLATE = "template"
    LAYOUT = "layout"


@dataclass
class ContentMetadata:
    """内容元数据"""
    id: str
    type: ContentType
    title: str
    slug: str
    created_at: datetime
    updated_at: datetime
    author: str
    tags: List[str] = field(default_factory=list)
    categories: List[str] = field(default_factory=list)
    template: Optional[str] = None
    layout: Optional[str] = None
    status: str = "published"  # draft, published, archived
    meta: Dict[str, Any] = field(default_factory=dict)
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        data = asdict(self)
        data['created_at'] = self.created_at.isoformat()
        data['updated_at'] = self.updated_at.isoformat()
        data['type'] = self.type.value
        return data


@dataclass  
class Content:
    """内容"""
    metadata: ContentMetadata
    content: str
    rendered: Optional[str] = None
    
    @classmethod
    def from_markdown(cls, path: Path) -> 'Content':
        """从Markdown文件创建内容"""
        # 解析Front Matter和内容
        with open(path, 'r', encoding='utf-8') as f:
            text = f.read()
        
        # 分离Front Matter和内容
        if text.startswith('---'):
            parts = text.split('---', 2)
            if len(parts) >= 3:
                front_matter = yaml.safe_load(parts[1])
                content = parts[2].strip()
            else:
                front_matter = {}
                content = text
        else:
            front_matter = {}
            content = text
        
        # 创建元数据
        metadata = ContentMetadata(
            id=front_matter.get('id', path.stem),
            type=ContentType(front_matter.get('type', 'page')),
            title=front_matter.get('title', path.stem),
            slug=front_matter.get('slug', path.stem),
            created_at=datetime.fromisoformat(front_matter.get('created_at', datetime.now().isoformat())),
            updated_at=datetime.fromisoformat(front_matter.get('updated_at', datetime.now().isoformat())),
            author=front_matter.get('author', 'admin'),
            tags=front_matter.get('tags', []),
            categories=front_matter.get('categories', []),
            template=front_matter.get('template'),
            layout=front_matter.get('layout'),
            status=front_matter.get('status', 'published'),
            meta=front_matter.get('meta', {}),
        )
        
        return cls(metadata=metadata, content=content)
    
    def render_markdown(self) -> str:
        """渲染Markdown为HTML"""
        return markdown.markdown(
            self.content,
            extensions=[
                'extra',
                'codehilite',
                'toc',
                'meta',
                'admonition',
            ]
        )


class ContentStorage:
    """内容存储"""
    
    def __init__(self, content_dir: str = "./content"):
        self.content_dir = Path(content_dir)
        self.content_dir.mkdir(exist_ok=True)
        
        # 缓存
        self.cache = {}
        self.index = {}  # slug -> content_id
        
        # 加载所有内容
        self._load_all_content()
    
    def _load_all_content(self):
        """加载所有内容"""
        for md_file in self.content_dir.rglob("*.md"):
            try:
                content = Content.from_markdown(md_file)
                self.cache[content.metadata.id] = content
                self.index[content.metadata.slug] = content.metadata.id
            except Exception as e:
                print(f"Error loading {md_file}: {e}")
    
    def get_by_id(self, content_id: str) -> Optional[Content]:
        """通过ID获取内容"""
        return self.cache.get(content_id)
    
    def get_by_slug(self, slug: str) -> Optional[Content]:
        """通过slug获取内容"""
        content_id = self.index.get(slug)
        if content_id:
            return self.cache.get(content_id)
        return None
    
    def list_by_type(self, content_type: ContentType, status: str = "published") -> List[Content]:
        """按类型列出内容"""
        return [
            content for content in self.cache.values()
            if content.metadata.type == content_type and content.metadata.status == status
        ]
    
    def list_by_tag(self, tag: str) -> List[Content]:
        """按标签列出内容"""
        return [
            content for content in self.cache.values()
            if tag in content.metadata.tags and content.metadata.status == "published"
        ]
    
    def search(self, query: str) -> List[Content]:
        """搜索内容"""
        results = []
        query_lower = query.lower()
        
        for content in self.cache.values():
            if (query_lower in content.metadata.title.lower() or
                query_lower in content.content.lower()):
                results.append(content)
        
        return results


class CMSServer:
    """CMS服务器"""
    
    def __init__(
        self,
        content_dir: str = "./content",
        template_dir: str = "./templates",
        static_dir: str = "./static",
        host: str = "localhost",
        port: int = 8000
    ):
        self.content_storage = ContentStorage(content_dir)
        
        # 模板引擎
        self.template_engine = TemplateEngine(
            template_dirs=[template_dir],
            auto_reload=True,
            safe_mode=True,
            cache_size=100
        )
        
        # 静态文件服务器
        self.static_server = StaticFileServer(
            root_dir=static_dir,
            cache_size=100,
            max_age=3600,
            enable_gzip=True,
            enable_brotli=True,
            enable_range=True,
            security_headers=True
        )
        
        # CDN集成
        self.cdn_enabled = False
        self.cdn_orchestrator = None
        
        # 服务器配置
        self.host = host
        self.port = port
        
        # 注册模板全局变量
        self._register_globals()
        
        # 注册自定义过滤器
        self._register_filters()
    
    def _register_globals(self):
        """注册全局变量"""
        self.template_engine.add_global('site', {
            'title': 'My CMS',
            'description': 'A modern content management system',
            'url': 'https://example.com',
            'language': 'en',
        })
        
        self.template_engine.add_global('navigation', [
            {'title': 'Home', 'url': '/'},
            {'title': 'Blog', 'url': '/blog'},
            {'title': 'About', 'url': '/about'},
            {'title': 'Contact', 'url': '/contact'},
        ])
        
        self.template_engine.add_global('now', datetime.now)
    
    def _register_filters(self):
        """注册自定义过滤器"""
        
        @self.template_engine.add_filter
        def markdown(value):
            """Markdown过滤器"""
            return markdown.markdown(value)
        
        @self.template_engine.add_filter
        def date(value, format_str="%Y-%m-%d"):
            """日期格式化过滤器"""
            if isinstance(value, str):
                try:
                    value = datetime.fromisoformat(value)
                except ValueError:
                    return value
            
            if isinstance(value, datetime):
                return value.strftime(format_str)
            
            return value
        
        @self.template_engine.add_filter
        def excerpt(value, length=200):
            """摘要过滤器"""
            if len(value) <= length:
                return value
            
            # 在完整单词处截断
            truncated = value[:length]
            if value[length] != ' ':
                last_space = truncated.rfind(' ')
                if last_space > 0:
                    truncated = truncated[:last_space]
            
            return truncated + '...'
        
        @self.template_engine.add_filter
        def pluralize(value, singular="", plural="s"):
            """复数化过滤器"""
            try:
                num = int(value)
                return singular if num == 1 else plural
            except:
                return plural
    
    async def handle_request(self, path: str, method: str = "GET", headers: Dict = None) -> tuple:
        """处理HTTP请求"""
        headers = headers or {}
        
        # 静态文件请求
        if path.startswith('/static/'):
            return await self._handle_static_request(path[8:], method, headers)
        
        # API请求
        if path.startswith('/api/'):
            return await self._handle_api_request(path[5:], method, headers)
        
        # 内容页面请求
        return await self._handle_page_request(path, method, headers)
    
    async def _handle_static_request(self, path: str, method: str, headers: Dict) -> tuple:
        """处理静态文件请求"""
        if self.cdn_enabled and self.cdn_orchestrator:
            # 重定向到CDN
            cdn_url = await self.cdn_orchestrator.get_best_cdn_url(path)
            if cdn_url:
                return 302, {'Location': cdn_url}, b''
        
        # 本地服务
        status, headers, content = self.static_server.serve_file(path, method, headers)
        return status, headers, content
    
    async def _handle_api_request(self, path: str, method: str, headers: Dict) -> tuple:
        """处理API请求"""
        if path == 'content' and method == 'GET':
            # 获取内容列表
            content_type = headers.get('X-Content-Type', 'page')
            contents = self.content_storage.list_by_type(ContentType(content_type))
            
            data = [c.metadata.to_dict() for c in contents]
            return 200, {'Content-Type': 'application/json'}, json.dumps(data).encode()
        
        elif path.startswith('content/') and method == 'GET':
            # 获取单个内容
            content_id = path[8:]
            content = self.content_storage.get_by_id(content_id)
            
            if content:
                data = {
                    'metadata': content.metadata.to_dict(),
                    'content': content.content,
                    'rendered': content.render_markdown(),
                }
                return 200, {'Content-Type': 'application/json'}, json.dumps(data).encode()
            else:
                return 404, {}, b'Not Found'
        
        elif path == 'search' and method == 'GET':
            # 搜索内容
            query = headers.get('X-Search-Query', '')
            results = self.content_storage.search(query)
            
            data = [{
                'id': r.metadata.id,
                'title': r.metadata.title,
                'slug': r.metadata.slug,
                'excerpt': r.content[:200] + '...' if len(r.content) > 200 else r.content,
            } for r in results]
            
            return 200, {'Content-Type': 'application/json'}, json.dumps(data).encode()
        
        return 404, {}, b'Not Found'
    
    async def _handle_page_request(self, path: str, method: str, headers: Dict) -> tuple:
        """处理页面请求"""
        # 规范化路径
        if path == '/':
            path = '/index'
        
        # 移除前导斜杠
        if path.startswith('/'):
            slug = path[1:]
        else:
            slug = path
        
        # 查找内容
        content = self.content_storage.get_by_slug(slug)
        
        if not content or content.metadata.status != 'published':
            # 返回404页面
            return await self._render_error(404, "Page Not Found")
        
        # 渲染内容
        html = await self._render_content(content)
        
        # 添加响应头
        response_headers = {
            'Content-Type': 'text/html; charset=utf-8',
            'Cache-Control': 'public, max-age=300',  # 5分钟缓存
            'X-Content-ID': content.metadata.id,
            'X-Generated-At': datetime.now().isoformat(),
        }
        
        return 200, response_headers, html.encode('utf-8')
    
    async def _render_content(self, content: Content) -> str:
        """渲染内容"""
        # 渲染Markdown
        rendered_content = content.render_markdown()
        
        # 准备模板上下文
        context = {
            'page': {
                'title': content.metadata.title,
                'content': rendered_content,
                'metadata': content.metadata.to_dict(),
            },
            'site': self.template_engine.global_context['site'],
            'navigation': self.template_engine.global_context['navigation'],
            'related_posts': self.content_storage.list_by_tag(content.metadata.tags[0]) 
                            if content.metadata.tags else [],
        }
        
        # 确定模板
        template_name = content.metadata.template or 'page.html'
        
        # 渲染模板
        return self.template_engine.render(template_name, context)
    
    async def _render_error(self, status_code: int, message: str) -> tuple:
        """渲染错误页面"""
        context = {
            'error': {
                'code': status_code,
                'message': message,
            },
            'site': self.template_engine.global_context['site'],
            'navigation': self.template_engine.global_context['navigation'],
        }
        
        html = self.template_engine.render('error.html', context)
        
        headers = {
            'Content-Type': 'text/html; charset=utf-8',
            'Cache-Control': 'no-cache',
        }
        
        return status_code, headers, html.encode('utf-8')
    
    async def start_server(self):
        """启动HTTP服务器"""
        import aiohttp
        from aiohttp import web
        
        app = web.Application()
        
        async def handle(request):
            path = request.path
            method = request.method
            headers = dict(request.headers)
            
            status, headers, body = await self.handle_request(path, method, headers)
            
            response = web.Response(
                status=status,
                headers=headers,
                body=body
            )
            
            return response
        
        app.router.add_route('*', '/{path:.*}', handle)
        
        runner = web.AppRunner(app)
        await runner.setup()
        
        site = web.TCPSite(runner, self.host, self.port)
        await site.start()
        
        print(f"CMS server started at http://{self.host}:{self.port}")
        
        # 保持运行
        try:
            while True:
                await asyncio.sleep(3600)
        except KeyboardInterrupt:
            print("\nShutting down...")
        finally:
            await runner.cleanup()
    
    def enable_cdn(self, config: Dict[str, Any]):
        """启用CDN支持"""
        self.cdn_enabled = True
        self.cdn_orchestrator = CDNOrchestrator()
        
        # 配置CDN后端
        for provider_config in config.get('providers', []):
            provider = CDNProvider(provider_config['name'])
            
            if provider == CDNProvider.AWS_CLOUDFRONT:
                backend = S3CDNBackend(provider_config['config'])
            elif provider == CDNProvider.CLOUDFLARE:
                backend = S3CDNBackend(provider_config['config'])
            else:
                continue
            
            self.cdn_orchestrator.register_backend(provider, backend)
        
        print(f"CDN enabled with {len(self.cdn_orchestrator.backends)} providers")
    
    def get_stats(self) -> Dict[str, Any]:
        """获取系统统计"""
        template_stats = self.template_engine.get_stats()
        static_stats = self.static_server.get_stats()
        
        return {
            'content': {
                'total': len(self.content_storage.cache),
                'published': len([c for c in self.content_storage.cache.values() 
                                 if c.metadata.status == 'published']),
                'by_type': {
                    t.value: len(self.content_storage.list_by_type(t))
                    for t in ContentType
                },
            },
            'templates': template_stats,
            'static_files': static_stats,
            'cdn_enabled': self.cdn_enabled,
            'cdn_providers': list(self.cdn_orchestrator.backends.keys()) 
                           if self.cdn_orchestrator else [],
        }


# 示例内容结构
SAMPLE_CONTENT = {
    "index.md": """---
title: Welcome to Our Site
slug: index
type: page
author: admin
created_at: 2024-01-01T00:00:00
updated_at: 2024-01-01T00:00:00
tags: [welcome]
layout: default
template: home.html
status: published
---

# Welcome to Our CMS

This is a **modern content management system** built with Python.

## Features

* Static file serving with CDN support
* Template rendering with Jinja2-like syntax
* Markdown content support
* Built-in caching and optimization
* Security features

## Get Started

1. Create content in Markdown format
2. Design templates in HTML
3. Upload static assets
4. Deploy and enjoy!
""",
    
    "blog/first-post.md": """---
title: My First Blog Post
slug: first-post
type: post
author: admin
created_at: 2024-01-02T10:00:00
updated_at: 2024-01-02T10:00:00
tags: [blog, python, web]
categories: [tutorials]
layout: post
template: post.html
status: published
---

# Getting Started with Web Development

This is my first blog post about web development with Python.

## Introduction

Python is a great language for web development. With frameworks like Flask and Django, you can build powerful web applications quickly.

## Key Concepts

### Templates
Templates allow you to separate your presentation logic from your business logic.

### Static Files
Static files like CSS, JavaScript, and images are served efficiently with caching.

### Security
Always sanitize user input and use proper authentication.

## Conclusion

Web development with Python is both fun and productive. Start building today!
""",
}


# 示例模板
SAMPLE_TEMPLATES = {
    "templates/base.html": """<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ site.title }} - {{ page.title }}</title>
    <link rel="stylesheet" href="/static/css/style.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
    <nav class="navbar">
        <div class="container">
            <a href="/" class="logo">{{ site.title }}</a>
            <ul class="nav-menu">
                {% for item in navigation %}
                <li><a href="{{ item.url }}">{{ item.title }}</a></li>
                {% endfor %}
            </ul>
        </div>
    </nav>
    
    <main class="container">
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        <div class="container">
            <p>&copy; {{ now().year }} {{ site.title }}. All rights reserved.</p>
        </div>
    </footer>
    
    <script src="/static/js/main.js"></script>
</body>
</html>
""",
    
    "templates/home.html": """{% extends "base.html" %}

{% block content %}
<div class="hero">
    <h1>{{ page.title }}</h1>
    <div class="content">
        {{ page.content|safe }}
    </div>
</div>

<div class="features">
    <div class="feature">
        <i class="fas fa-bolt"></i>
        <h3>Fast</h3>
        <p>Lightning-fast static file serving with CDN support.</p>
    </div>
    <div class="feature">
        <i class="fas fa-shield-alt"></i>
        <h3>Secure</h3>
        <p>Built-in security features and sanitization.</p>
    </div>
    <div class="feature">
        <i class="fas fa-code"></i>
        <h3>Flexible</h3>
        <p>Custom templates and Markdown support.</p>
    </div>
</div>
{% endblock %}
""",
    
    "templates/post.html": """{% extends "base.html" %}

{% block content %}
<article class="post">
    <header>
        <h1>{{ page.title }}</h1>
        <div class="meta">
            <span class="author">
                <i class="fas fa-user"></i> {{ page.metadata.author }}
            </span>
            <span class="date">
                <i class="fas fa-calendar"></i> {{ page.metadata.created_at|date }}
            </span>
            {% if page.metadata.tags %}
            <span class="tags">
                <i class="fas fa-tags"></i>
                {% for tag in page.metadata.tags %}
                <a href="/tag/{{ tag }}">{{ tag }}</a>{% if not loop.last %}, {% endif %}
                {% endfor %}
            </span>
            {% endif %}
        </div>
    </header>
    
    <div class="content">
        {{ page.content|safe }}
    </div>
    
    {% if related_posts %}
    <div class="related-posts">
        <h3>Related Posts</h3>
        <ul>
            {% for post in related_posts %}
            {% if post.metadata.id != page.metadata.id %}
            <li><a href="/{{ post.metadata.slug }}">{{ post.metadata.title }}</a></li>
            {% endif %}
            {% endfor %}
        </ul>
    </div>
    {% endif %}
</article>
{% endblock %}
""",
    
    "templates/error.html": """{% extends "base.html" %}

{% block content %}
<div class="error-page">
    <h1>{{ error.code }} - {{ error.message }}</h1>
    <p>Sorry, the page you're looking for doesn't exist.</p>
    <a href="/" class="btn">Go Home</a>
</div>
{% endblock %}
""",
}


# 示例静态文件
SAMPLE_STATIC = {
    "static/css/style.css": """/* Base Styles */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
    line-height: 1.6;
    color: #333;
    background: #f5f5f5;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 20px;
}

/* Navigation */
.navbar {
    background: white;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    padding: 1rem 0;
}

.logo {
    font-size: 1.5rem;
    font-weight: bold;
    color: #333;
    text-decoration: none;
}

.nav-menu {
    display: flex;
    list-style: none;
    gap: 2rem;
}

.nav-menu a {
    color: #666;
    text-decoration: none;
    transition: color 0.3s;
}

.nav-menu a:hover {
    color: #007bff;
}

/* Main Content */
main {
    padding: 3rem 0;
}

.hero {
    text-align: center;
    padding: 4rem 0;
}

.hero h1 {
    font-size: 3rem;
    margin-bottom: 1.5rem;
    color: #222;
}

.content {
    font-size: 1.1rem;
    line-height: 1.8;
    color: #444;
}

/* Features */
.features {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin-top: 3rem;
}

.feature {
    background: white;
    padding: 2rem;
    border-radius: 8px;
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
    text-align: center;
}

.feature i {
    font-size: 2.5rem;
    color: #007bff;
    margin-bottom: 1rem;
}

.feature h3 {
    margin-bottom: 1rem;
    color: #333;
}

/* Posts */
.post {
    background: white;
    padding: 2rem;
    border-radius: 8px;
    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}

.post header {
    margin-bottom: 2rem;
    border-bottom: 1px solid #eee;
    padding-bottom: 1rem;
}

.post h1 {
    font-size: 2.5rem;
    margin-bottom: 1rem;
}

.meta {
    display: flex;
    gap: 1.5rem;
    color: #666;
    font-size: 0.9rem;
}

.meta i {
    margin-right: 0.5rem;
}

.tags a {
    color: #007bff;
    text-decoration: none;
}

.tags a:hover {
    text-decoration: underline;
}

.related-posts {
    margin-top: 3rem;
    padding-top: 2rem;
    border-top: 1px solid #eee;
}

.related-posts ul {
    list-style: none;
}

.related-posts li {
    margin: 0.5rem 0;
}

.related-posts a {
    color: #007bff;
    text-decoration: none;
}

.related-posts a:hover {
    text-decoration: underline;
}

/* Error Page */
.error-page {
    text-align: center;
    padding: 4rem 0;
}

.error-page h1 {
    font-size: 4rem;
    color: #dc3545;
    margin-bottom: 1rem;
}

.btn {
    display: inline-block;
    background: #007bff;
    color: white;
    padding: 0.75rem 1.5rem;
    border-radius: 4px;
    text-decoration: none;
    margin-top: 1rem;
}

.btn:hover {
    background: #0056b3;
}

/* Footer */
footer {
    background: #333;
    color: white;
    padding: 2rem 0;
    text-align: center;
    margin-top: 3rem;
}
""",
    
    "static/js/main.js": """// Main JavaScript
document.addEventListener('DOMContentLoaded', function() {
    console.log('CMS loaded');
    
    // Smooth scrolling for anchor links
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function(e) {
            e.preventDefault();
            const target = document.querySelector(this.getAttribute('href'));
            if (target) {
                target.scrollIntoView({ behavior: 'smooth' });
            }
        });
    });
    
    // Mobile menu toggle (if needed)
    const menuToggle = document.querySelector('.menu-toggle');
    const navMenu = document.querySelector('.nav-menu');
    
    if (menuToggle && navMenu) {
        menuToggle.addEventListener('click', function() {
            navMenu.classList.toggle('active');
        });
    }
});
""",
}


async def setup_sample_cms():
    """设置示例CMS"""
    import os
    import shutil
    
    # 创建目录结构
    directories = [
        './content',
        './content/blog',
        './templates',
        './static',
        './static/css',
        './static/js',
    ]
    
    for directory in directories:
        os.makedirs(directory, exist_ok=True)
    
    # 写入示例内容
    for path, content in SAMPLE_CONTENT.items():
        full_path = os.path.join('./content', path)
        os.makedirs(os.path.dirname(full_path), exist_ok=True)
        
        with open(full_path, 'w', encoding='utf-8') as f:
            f.write(content)
    
    # 写入示例模板
    for path, content in SAMPLE_TEMPLATES.items():
        os.makedirs(os.path.dirname(path), exist_ok=True)
        
        with open(path, 'w', encoding='utf-8') as f:
            f.write(content)
    
    # 写入示例静态文件
    for path, content in SAMPLE_STATIC.items():
        os.makedirs(os.path.dirname(path), exist_ok=True)
        
        with open(path, 'w', encoding='utf-8') as f:
            f.write(content)
    
    print("Sample CMS setup complete!")
    print("Content files created in ./content")
    print("Templates created in ./templates")
    print("Static files created in ./static")


async def run_cms():
    """运行CMS服务器"""
    # 设置示例内容
    await setup_sample_cms()
    
    # 创建CMS服务器
    cms = CMSServer(
        content_dir='./content',
        template_dir='./templates',
        static_dir='./static',
        host='localhost',
        port=8000
    )
    
    # 可选:启用CDN
    # cdn_config = {
    #     'providers': [
    #         {
    #             'name': 'cloudflare',
    #             'config': {
    #                 'endpoint_url': 'https://r2.cloudflarestorage.com',
    #                 'access_key_id': 'YOUR_KEY',
    #                 'secret_access_key': 'YOUR_SECRET',
    #                 'bucket': 'my-cms-assets'
    #             }
    #         }
    #     ]
    # }
    # cms.enable_cdn(cdn_config)
    
    # 显示统计信息
    stats = cms.get_stats()
    print("\nCMS Statistics:")
    print(f"Content: {stats['content']['total']} items")
    print(f"  Published: {stats['content']['published']}")
    print(f"  By type: {stats['content']['by_type']}")
    
    # 启动服务器
    await cms.start_server()


if __name__ == "__main__":
    # 运行CMS
    asyncio.run(run_cms())

7. 性能优化与安全 {#性能优化安全}

7.1 综合性能优化策略

python 复制代码
"""
综合性能优化策略
包含:缓存、压缩、CDN、代码拆分、懒加载
"""
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
import time
import hashlib
import zlib
import gzip
import brotli


class OptimizationLevel(Enum):
    """优化级别"""
    NONE = 0
    BASIC = 1      # 基本压缩和缓存
    ADVANCED = 2   # 高级压缩和CDN
    AGGRESSIVE = 3 # 激进优化(可能影响兼容性)


@dataclass
class OptimizationProfile:
    """优化配置"""
    level: OptimizationLevel
    enable_gzip: bool
    enable_brotli: bool
    enable_cache: bool
    enable_cdn: bool
    enable_minify: bool
    enable_bundle: bool
    enable_lazyload: bool
    cache_ttl: int  # 秒
    
    @classmethod
    def from_level(cls, level: OptimizationLevel) -> 'OptimizationProfile':
        """根据级别创建配置"""
        if level == OptimizationLevel.NONE:
            return cls(
                level=level,
                enable_gzip=False,
                enable_brotli=False,
                enable_cache=False,
                enable_cdn=False,
                enable_minify=False,
                enable_bundle=False,
                enable_lazyload=False,
                cache_ttl=0
            )
        elif level == OptimizationLevel.BASIC:
            return cls(
                level=level,
                enable_gzip=True,
                enable_brotli=False,
                enable_cache=True,
                enable_cdn=False,
                enable_minify=True,
                enable_bundle=True,
                enable_lazyload=False,
                cache_ttl=300  # 5分钟
            )
        elif level == OptimizationLevel.ADVANCED:
            return cls(
                level=level,
                enable_gzip=True,
                enable_brotli=True,
                enable_cache=True,
                enable_cdn=True,
                enable_minify=True,
                enable_bundle=True,
                enable_lazyload=True,
                cache_ttl=3600  # 1小时
            )
        else:  # AGGRESSIVE
            return cls(
                level=level,
                enable_gzip=True,
                enable_brotli=True,
                enable_cache=True,
                enable_cdn=True,
                enable_minify=True,
                enable_bundle=True,
                enable_lazyload=True,
                cache_ttl=86400  # 24小时
            )


class PerformanceOptimizer:
    """性能优化器"""
    
    def __init__(self, profile: OptimizationProfile):
        self.profile = profile
        self.cache = {}
        self.metrics = {
            'cache_hits': 0,
            'cache_misses': 0,
            'compression_savings': 0,
            'requests_served': 0,
        }
        
    def optimize_static_file(self, content: bytes, content_type: str) -> Tuple[bytes, Dict[str, str]]:
        """优化静态文件"""
        self.metrics['requests_served'] += 1
        
        # 生成缓存键
        cache_key = self._generate_cache_key(content, content_type)
        
        # 检查缓存
        if self.profile.enable_cache and cache_key in self.cache:
            cached = self.cache[cache_key]
            if not self._is_cache_expired(cached['timestamp']):
                self.metrics['cache_hits'] += 1
                return cached['content'], cached['headers']
        
        self.metrics['cache_misses'] += 1
        
        original_size = len(content)
        optimized_content = content
        headers = {
            'Content-Type': content_type,
        }
        
        # 应用优化
        if self.profile.enable_minify and self._should_minify(content_type):
            optimized_content = self._minify_content(optimized_content, content_type)
        
        if self.profile.enable_gzip and self._should_compress(content_type):
            gzipped = gzip.compress(optimized_content, compresslevel=6)
            if len(gzipped) < len(optimized_content):
                optimized_content = gzipped
                headers['Content-Encoding'] = 'gzip'
                self.metrics['compression_savings'] += (len(content) - len(gzipped))
        
        elif self.profile.enable_brotli and self._should_compress(content_type):
            brotlied = brotli.compress(optimized_content)
            if len(brotlied) < len(optimized_content):
                optimized_content = brotlied
                headers['Content-Encoding'] = 'br'
                self.metrics['compression_savings'] += (len(content) - len(brotlied))
        
        # 添加缓存头
        if self.profile.enable_cache:
            headers['Cache-Control'] = f'public, max-age={self.profile.cache_ttl}'
            headers['ETag'] = f'"{hashlib.md5(optimized_content).hexdigest()}"'
            
            # 缓存结果
            self.cache[cache_key] = {
                'content': optimized_content,
                'headers': headers,
                'timestamp': time.time(),
            }
            
            # 清理过期缓存
            self._cleanup_cache()
        
        return optimized_content, headers
    
    def _generate_cache_key(self, content: bytes, content_type: str) -> str:
        """生成缓存键"""
        content_hash = hashlib.md5(content).hexdigest()
        profile_hash = hashlib.md5(str(self.profile.level.value).encode()).hexdigest()
        return f"{content_hash}:{profile_hash}:{content_type}"
    
    def _is_cache_expired(self, timestamp: float) -> bool:
        """检查缓存是否过期"""
        return time.time() - timestamp > self.profile.cache_ttl
    
    def _cleanup_cache(self):
        """清理缓存"""
        current_time = time.time()
        expired_keys = [
            key for key, value in self.cache.items()
            if current_time - value['timestamp'] > self.profile.cache_ttl
        ]
        
        for key in expired_keys:
            del self.cache[key]
        
        # 限制缓存大小
        max_cache_size = 1000
        if len(self.cache) > max_cache_size:
            # 移除最旧的条目
            sorted_items = sorted(self.cache.items(), key=lambda x: x[1]['timestamp'])
            items_to_remove = sorted_items[:len(self.cache) - max_cache_size]
            for key, _ in items_to_remove:
                del self.cache[key]
    
    def _should_minify(self, content_type: str) -> bool:
        """检查是否应该最小化"""
        minifiable_types = [
            'text/html',
            'text/css',
            'application/javascript',
            'application/json',
        ]
        return any(content_type.startswith(t) for t in minifiable_types)
    
    def _minify_content(self, content: bytes, content_type: str) -> bytes:
        """最小化内容"""
        if content_type.startswith('text/html'):
            return self._minify_html(content)
        elif content_type.startswith('text/css'):
            return self._minify_css(content)
        elif content_type.startswith('application/javascript'):
            return self._minify_js(content)
        elif content_type.startswith('application/json'):
            return self._minify_json(content)
        return content
    
    def _minify_html(self, content: bytes) -> bytes:
        """最小化HTML"""
        # 简化实现:移除多余空白
        text = content.decode('utf-8')
        
        # 移除注释(小心条件注释)
        lines = []
        in_comment = False
        
        for line in text.split('\n'):
            stripped = line.strip()
            if stripped.startswith('<!--'):
                if not stripped.startswith('<!--['):  # 不是条件注释
                    in_comment = True
                lines.append(line)
            elif stripped.endswith('-->'):
                in_comment = False
            elif not in_comment:
                # 压缩空白
                compressed = re.sub(r'\s+', ' ', line.strip())
                if compressed:
                    lines.append(compressed)
        
        return '\n'.join(lines).encode('utf-8')
    
    def _minify_css(self, content: bytes) -> bytes:
        """最小化CSS"""
        text = content.decode('utf-8')
        
        # 移除注释
        text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
        
        # 压缩空白
        text = re.sub(r'\s+', ' ', text)
        text = re.sub(r'\s*([{}:;,])\s*', r'\1', text)
        
        # 移除最后一个分号
        text = re.sub(r';}', '}', text)
        
        return text.encode('utf-8')
    
    def _minify_js(self, content: bytes) -> bytes:
        """最小化JavaScript"""
        # 简化实现:在实际应用中使用专业工具如terser
        text = content.decode('utf-8')
        
        # 移除单行注释
        text = re.sub(r'//.*', '', text)
        
        # 移除多行注释(小心正则表达式字面量)
        lines = []
        in_comment = False
        
        for line in text.split('\n'):
            if '/*' in line and not in_comment:
                in_comment = True
                before = line.split('/*')[0]
                if '*/' in line:
                    in_comment = False
                    after = line.split('*/')[1]
                    lines.append(before + after)
                else:
                    lines.append(before)
            elif '*/' in line and in_comment:
                in_comment = False
                lines.append(line.split('*/')[1])
            elif not in_comment:
                lines.append(line)
        
        text = '\n'.join(lines)
        
        # 压缩空白(小心字符串)
        text = re.sub(r'\s+', ' ', text)
        text = re.sub(r'\s*([=+\-*/%&|^<>!?:;,{}()[\]])\s*', r'\1', text)
        
        return text.encode('utf-8')
    
    def _minify_json(self, content: bytes) -> bytes:
        """最小化JSON"""
        try:
            data = json.loads(content.decode('utf-8'))
            return json.dumps(data, separators=(',', ':')).encode('utf-8')
        except:
            return content
    
    def _should_compress(self, content_type: str) -> bool:
        """检查是否应该压缩"""
        compressible_types = [
            'text/',
            'application/javascript',
            'application/json',
            'application/xml',
            'image/svg+xml',
        ]
        
        non_compressible_types = [
            'image/jpeg',
            'image/png',
            'image/gif',
            'image/webp',
            'video/',
            'audio/',
            'application/zip',
            'application/gzip',
        ]
        
        # 检查是否可压缩
        for t in compressible_types:
            if content_type.startswith(t):
                return True
        
        # 检查是否不应压缩
        for t in non_compressible_types:
            if content_type.startswith(t):
                return False
        
        return False
    
    def get_performance_metrics(self) -> Dict[str, Any]:
        """获取性能指标"""
        cache_hit_rate = (
            self.metrics['cache_hits'] / (self.metrics['cache_hits'] + self.metrics['cache_misses'])
            if (self.metrics['cache_hits'] + self.metrics['cache_misses']) > 0 else 0
        )
        
        avg_saving_per_request = (
            self.metrics['compression_savings'] / self.metrics['requests_served']
            if self.metrics['requests_served'] > 0 else 0
        )
        
        return {
            **self.metrics,
            'cache_hit_rate': f"{cache_hit_rate:.2%}",
            'avg_saving_bytes': avg_saving_per_request,
            'cache_size': len(self.cache),
            'optimization_level': self.profile.level.name,
        }


class SecurityHardener:
    """安全加固器"""
    
    def __init__(self):
        self.security_headers = {
            'X-Content-Type-Options': 'nosniff',
            'X-Frame-Options': 'DENY',
            'X-XSS-Protection': '1; mode=block',
            'Referrer-Policy': 'strict-origin-when-cross-origin',
            'Content-Security-Policy': self._default_csp(),
            'Permissions-Policy': self._default_permissions_policy(),
        }
    
    def _default_csp(self) -> str:
        """默认内容安全策略"""
        return (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; "
            "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; "
            "img-src 'self' data: https:; "
            "font-src 'self' https://cdnjs.cloudflare.com; "
            "connect-src 'self'; "
            "frame-ancestors 'none'; "
            "base-uri 'self'; "
            "form-action 'self';"
        )
    
    def _default_permissions_policy(self) -> str:
        """默认权限策略"""
        return (
            "camera=(), "
            "microphone=(), "
            "geolocation=(), "
            "payment=()"
        )
    
    def add_security_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
        """添加安全头"""
        return {**headers, **self.security_headers}
    
    def sanitize_template_output(self, output: str) -> str:
        """净化模板输出"""
        # HTML转义
        escaped = (
            output.replace('&', '&amp;')
                  .replace('<', '&lt;')
                  .replace('>', '&gt;')
                  .replace('"', '&quot;')
                  .replace("'", '&#x27;')
        )
        
        # 移除危险属性
        dangerous_patterns = [
            r'\bon\w+\s*=',  # 内联事件处理器
            r'javascript:',   # JavaScript协议
            r'data:',         # 数据协议(限制某些类型)
        ]
        
        for pattern in dangerous_patterns:
            escaped = re.sub(pattern, '[removed]', escaped, flags=re.IGNORECASE)
        
        return escaped
    
    def validate_file_path(self, path: str, root_dir: str) -> bool:
        """验证文件路径安全性"""
        try:
            full_path = Path(root_dir) / path.lstrip('/')
            resolved = full_path.resolve()
            
            # 检查路径遍历
            root = Path(root_dir).resolve()
            return root in resolved.parents or root == resolved
        except:
            return False


# 综合优化管理器
class OptimizationManager:
    """综合优化管理器"""
    
    def __init__(self, optimization_level: OptimizationLevel = OptimizationLevel.ADVANCED):
        self.profile = OptimizationProfile.from_level(optimization_level)
        self.performance_optimizer = PerformanceOptimizer(self.profile)
        self.security_hardener = SecurityHardener()
        
        # 资源映射表
        self.resource_map = {}
        
    async def optimize_response(
        self,
        content: bytes,
        content_type: str,
        path: str = ''
    ) -> Tuple[bytes, Dict[str, str]]:
        """优化HTTP响应"""
        # 性能优化
        optimized_content, headers = self.performance_optimizer.optimize_static_file(
            content, content_type
        )
        
        # 安全加固
        headers = self.security_hardener.add_security_headers(headers)
        
        # 添加资源映射头
        if path in self.resource_map:
            headers['X-Resource-Version'] = self.resource_map[path]
        
        return optimized_content, headers
    
    def register_resource(self, path: str, content: bytes):
        """注册资源并生成版本哈希"""
        content_hash = hashlib.md5(content).hexdigest()[:8]
        self.resource_map[path] = content_hash
    
    def get_optimization_report(self) -> Dict[str, Any]:
        """获取优化报告"""
        perf_metrics = self.performance_optimizer.get_performance_metrics()
        
        return {
            'performance': perf_metrics,
            'security_headers': list(self.security_hardener.security_headers.keys()),
            'optimization_level': self.profile.level.name,
            'resource_map_size': len(self.resource_map),
        }

7.2 安全最佳实践

python 复制代码
"""
Web应用安全最佳实践
包含:输入验证、输出编码、CSRF保护、CORS配置
"""
import secrets
from typing import Optional, List
from datetime import datetime, timedelta
from dataclasses import dataclass


@dataclass
class SecurityConfig:
    """安全配置"""
    # CSRF配置
    csrf_secret: str
    csrf_token_expiry: int = 3600  # 1小时
    csrf_cookie_name: str = 'csrf_token'
    csrf_header_name: str = 'X-CSRF-Token'
    
    # CORS配置
    cors_allowed_origins: List[str] = None
    cors_allowed_methods: List[str] = None
    cors_allowed_headers: List[str] = None
    cors_allow_credentials: bool = False
    
    # 速率限制
    rate_limit_enabled: bool = True
    rate_limit_window: int = 60  # 秒
    rate_limit_max_requests: int = 100
    
    # 其他安全配置
    hsts_enabled: bool = True
    hsts_max_age: int = 31536000  # 1年
    clickjacking_protection: bool = True
    content_sniffing_protection: bool = True
    
    def __post_init__(self):
        if self.cors_allowed_origins is None:
            self.cors_allowed_origins = ['*']
        if self.cors_allowed_methods is None:
            self.cors_allowed_methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
        if self.cors_allowed_headers is None:
            self.cors_allowed_headers = ['Content-Type', 'Authorization']


class CSRFProtection:
    """CSRF保护"""
    
    def __init__(self, config: SecurityConfig):
        self.config = config
        self.tokens = {}  # token -> expiry_time
        
    def generate_token(self, user_id: str = None) -> str:
        """生成CSRF令牌"""
        token = secrets.token_urlsafe(32)
        expiry = datetime.now() + timedelta(seconds=self.config.csrf_token_expiry)
        
        self.tokens[token] = {
            'expiry': expiry,
            'user_id': user_id,
        }
        
        return token
    
    def validate_token(self, token: str, user_id: str = None) -> bool:
        """验证CSRF令牌"""
        if token not in self.tokens:
            return False
        
        token_data = self.tokens[token]
        
        # 检查过期
        if datetime.now() > token_data['expiry']:
            del self.tokens[token]
            return False
        
        # 检查用户ID(如果提供)
        if user_id and token_data['user_id'] and token_data['user_id'] != user_id:
            return False
        
        return True
    
    def clean_expired_tokens(self):
        """清理过期令牌"""
        now = datetime.now()
        expired_tokens = [
            token for token, data in self.tokens.items()
            if now > data['expiry']
        ]
        
        for token in expired_tokens:
            del self.tokens[token]


class RateLimiter:
    """速率限制器"""
    
    def __init__(self, window: int, max_requests: int):
        self.window = window
        self.max_requests = max_requests
        self.requests = {}  # key -> [timestamp1, timestamp2, ...]
        
    def is_allowed(self, key: str) -> Tuple[bool, int]:
        """检查是否允许请求"""
        now = time.time()
        
        if key not in self.requests:
            self.requests[key] = []
        
        # 清理旧请求
        self.requests[key] = [
            ts for ts in self.requests[key]
            if now - ts < self.window
        ]
        
        # 检查请求数量
        if len(self.requests[key]) >= self.max_requests:
            retry_after = int(self.requests[key][0] + self.window - now)
            return False, retry_after
        
        # 记录新请求
        self.requests[key].append(now)
        
        # 清理过期的键
        self._cleanup()
        
        return True, 0
    
    def _cleanup(self):
        """清理过期数据"""
        now = time.time()
        expired_keys = []
        
        for key, timestamps in self.requests.items():
            valid_timestamps = [ts for ts in timestamps if now - ts < self.window * 2]
            if not valid_timestamps:
                expired_keys.append(key)
            else:
                self.requests[key] = valid_timestamps
        
        for key in expired_keys:
            del self.requests[key]


class InputValidator:
    """输入验证器"""
    
    @staticmethod
    def validate_email(email: str) -> bool:
        """验证电子邮件地址"""
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return bool(re.match(pattern, email))
    
    @staticmethod
    def validate_url(url: str) -> bool:
        """验证URL"""
        pattern = r'^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$'
        return bool(re.match(pattern, url))
    
    @staticmethod
    def sanitize_input(input_str: str, max_length: int = 1000) -> str:
        """净化输入"""
        # 截断长度
        if len(input_str) > max_length:
            input_str = input_str[:max_length]
        
        # 移除控制字符(除了换行和制表符)
        input_str = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', input_str)
        
        return input_str.strip()
    
    @staticmethod
    def validate_file_extension(filename: str, allowed_extensions: List[str]) -> bool:
        """验证文件扩展名"""
        ext = Path(filename).suffix.lower()
        return ext in allowed_extensions


class SecurityMiddleware:
    """安全中间件"""
    
    def __init__(self, config: SecurityConfig):
        self.config = config
        self.csrf = CSRFProtection(config)
        self.rate_limiter = RateLimiter(
            config.rate_limit_window,
            config.rate_limit_max_requests
        )
        
    async def process_request(self, request: Dict) -> Optional[Tuple[int, Dict, bytes]]:
        """处理请求安全检查"""
        client_ip = request.get('remote_addr', '')
        
        # 速率限制
        if self.config.rate_limit_enabled:
            allowed, retry_after = self.rate_limiter.is_allowed(client_ip)
            if not allowed:
                headers = {
                    'Retry-After': str(retry_after),
                    'Content-Type': 'application/json',
                }
                body = json.dumps({
                    'error': 'Rate limit exceeded',
                    'retry_after': retry_after,
                }).encode()
                return 429, headers, body
        
        # CSRF保护(对于非GET请求)
        if request['method'] not in ('GET', 'HEAD', 'OPTIONS'):
            csrf_token = (
                request['headers'].get(self.config.csrf_header_name) or
                request['cookies'].get(self.config.csrf_cookie_name)
            )
            
            if not csrf_token or not self.csrf.validate_token(csrf_token):
                return 403, {}, b'CSRF token validation failed'
        
        return None
    
    def add_security_headers(self, headers: Dict) -> Dict:
        """添加安全头"""
        security_headers = {}
        
        # HSTS
        if self.config.hsts_enabled:
            security_headers['Strict-Transport-Security'] = f'max-age={self.config.hsts_max_age}'
        
        # 点击劫持保护
        if self.config.clickjacking_protection:
            security_headers['X-Frame-Options'] = 'DENY'
        
        # 内容嗅探保护
        if self.config.content_sniffing_protection:
            security_headers['X-Content-Type-Options'] = 'nosniff'
        
        # CORS头
        if 'Origin' in headers:
            origin = headers['Origin']
            if origin in self.config.cors_allowed_origins or '*' in self.config.cors_allowed_origins:
                security_headers['Access-Control-Allow-Origin'] = (
                    origin if self.config.cors_allow_credentials else '*'
                )
                security_headers['Access-Control-Allow-Methods'] = ', '.join(
                    self.config.cors_allowed_methods
                )
                security_headers['Access-Control-Allow-Headers'] = ', '.join(
                    self.config.cors_allowed_headers
                )
                
                if self.config.cors_allow_credentials:
                    security_headers['Access-Control-Allow-Credentials'] = 'true'
        
        return {**headers, **security_headers}
    
    def generate_csrf_cookie(self) -> Tuple[str, str]:
        """生成CSRF Cookie"""
        token = self.csrf.generate_token()
        cookie_value = f"{self.config.csrf_cookie_name}={token}; HttpOnly; SameSite=Strict"
        return token, cookie_value

8. 监控与调试 {#监控调试}

8.1 综合监控系统

python 复制代码
"""
综合监控系统
包含:性能监控、错误追踪、资源使用、安全审计
"""
import time
import psutil
from typing import Dict, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from collections import deque
import traceback


@dataclass
class MetricPoint:
    """指标数据点"""
    timestamp: datetime
    value: float
    tags: Dict[str, str] = field(default_factory=dict)


@dataclass
class PerformanceMetric:
    """性能指标"""
    name: str
    unit: str
    values: deque
    max_points: int = 1000
    
    def __post_init__(self):
        self.values = deque(maxlen=self.max_points)
    
    def add_point(self, value: float, tags: Dict[str, str] = None):
        """添加数据点"""
        point = MetricPoint(
            timestamp=datetime.now(),
            value=value,
            tags=tags or {}
        )
        self.values.append(point)
    
    def get_stats(self) -> Dict[str, float]:
        """获取统计信息"""
        if not self.values:
            return {}
        
        values = [p.value for p in self.values]
        
        return {
            'count': len(values),
            'min': min(values),
            'max': max(values),
            'mean': sum(values) / len(values),
            'p50': self._percentile(values, 50),
            'p90': self._percentile(values, 90),
            'p95': self._percentile(values, 95),
            'p99': self._percentile(values, 99),
        }
    
    def _percentile(self, values: List[float], p: float) -> float:
        """计算百分位数"""
        if not values:
            return 0
        
        sorted_values = sorted(values)
        k = (len(sorted_values) - 1) * p / 100
        f = int(k)
        c = k - f
        
        if f == len(sorted_values) - 1:
            return sorted_values[f]
        
        return sorted_values[f] + c * (sorted_values[f + 1] - sorted_values[f])


@dataclass
class AlertRule:
    """告警规则"""
    metric_name: str
    condition: str  # 'gt', 'lt', 'eq', 'neq'
    threshold: float
    duration: int  # 持续秒数
    severity: str  # 'info', 'warning', 'critical'
    message: str
    
    def check(self, metric: PerformanceMetric) -> bool:
        """检查是否触发告警"""
        if not metric.values:
            return False
        
        # 检查最近的数据点
        recent_points = list(metric.values)[-self.duration:]
        if len(recent_points) < self.duration:
            return False
        
        # 检查条件
        values = [p.value for p in recent_points]
        
        if self.condition == 'gt':
            return all(v > self.threshold for v in values)
        elif self.condition == 'lt':
            return all(v < self.threshold for v in values)
        elif self.condition == 'eq':
            return all(v == self.threshold for v in values)
        elif self.condition == 'neq':
            return all(v != self.threshold for v in values)
        
        return False


class MonitoringSystem:
    """监控系统"""
    
    def __init__(self):
        self.metrics: Dict[str, PerformanceMetric] = {}
        self.alerts: List[AlertRule] = []
        self.alert_history: deque = deque(maxlen=1000)
        self.error_log: deque = deque(maxlen=1000)
        
        # 注册系统指标
        self._register_system_metrics()
        
        # 启动监控循环
        self._start_monitoring()
    
    def _register_system_metrics(self):
        """注册系统指标"""
        self.metrics['cpu_percent'] = PerformanceMetric('cpu_percent', '%')
        self.metrics['memory_percent'] = PerformanceMetric('memory_percent', '%')
        self.metrics['disk_io_read'] = PerformanceMetric('disk_io_read', 'bytes/s')
        self.metrics['disk_io_write'] = PerformanceMetric('disk_io_write', 'bytes/s')
        self.metrics['network_io_sent'] = PerformanceMetric('network_io_sent', 'bytes/s')
        self.metrics['network_io_recv'] = PerformanceMetric('network_io_recv', 'bytes/s')
        
        # 应用指标
        self.metrics['request_duration'] = PerformanceMetric('request_duration', 'ms')
        self.metrics['template_render_time'] = PerformanceMetric('template_render_time', 'ms')
        self.metrics['static_file_serve_time'] = PerformanceMetric('static_file_serve_time', 'ms')
        self.metrics['cache_hit_rate'] = PerformanceMetric('cache_hit_rate', '%')
        
        # 业务指标
        self.metrics['active_connections'] = PerformanceMetric('active_connections', 'count')
        self.metrics['requests_per_second'] = PerformanceMetric('requests_per_second', 'rps')
        self.metrics['errors_per_second'] = PerformanceMetric('errors_per_second', 'eps')
    
    def _start_monitoring(self):
        """启动监控循环"""
        import threading
        
        def monitor_loop():
            while True:
                self._collect_system_metrics()
                self._check_alerts()
                time.sleep(1)  # 每秒收集一次
        
        thread = threading.Thread(target=monitor_loop, daemon=True)
        thread.start()
    
    def _collect_system_metrics(self):
        """收集系统指标"""
        # CPU使用率
        cpu_percent = psutil.cpu_percent(interval=0.1)
        self.metrics['cpu_percent'].add_point(cpu_percent)
        
        # 内存使用率
        memory = psutil.virtual_memory()
        self.metrics['memory_percent'].add_point(memory.percent)
        
        # 磁盘IO
        disk_io = psutil.disk_io_counters()
        if disk_io:
            self.metrics['disk_io_read'].add_point(disk_io.read_bytes)
            self.metrics['disk_io_write'].add_point(disk_io.write_bytes)
        
        # 网络IO
        net_io = psutil.net_io_counters()
        if net_io:
            self.metrics['network_io_sent'].add_point(net_io.bytes_sent)
            self.metrics['network_io_recv'].add_point(net_io.bytes_recv)
    
    def record_request(self, duration_ms: float, status_code: int, path: str):
        """记录请求指标"""
        self.metrics['request_duration'].add_point(
            duration_ms,
            {'status': str(status_code), 'path': path}
        )
        
        # 更新RPS
        now = time.time()
        if 'last_request_time' not in self.__dict__:
            self.last_request_time = now
            self.request_count = 0
        
        self.request_count += 1
        
        if now - self.last_request_time >= 1:
            rps = self.request_count / (now - self.last_request_time)
            self.metrics['requests_per_second'].add_point(rps)
            self.last_request_time = now
            self.request_count = 0
        
        # 记录错误
        if status_code >= 400:
            self.metrics['errors_per_second'].add_point(1)
    
    def record_template_render(self, duration_ms: float, template_name: str):
        """记录模板渲染指标"""
        self.metrics['template_render_time'].add_point(
            duration_ms,
            {'template': template_name}
        )
    
    def record_static_file_serve(self, duration_ms: float, file_path: str):
        """记录静态文件服务指标"""
        self.metrics['static_file_serve_time'].add_point(
            duration_ms,
            {'file': file_path}
        )
    
    def record_cache_hit(self, hit: bool, cache_type: str):
        """记录缓存命中率"""
        value = 100.0 if hit else 0.0
        self.metrics['cache_hit_rate'].add_point(
            value,
            {'type': cache_type}
        )
    
    def add_alert_rule(self, rule: AlertRule):
        """添加告警规则"""
        self.alerts.append(rule)
    
    def _check_alerts(self):
        """检查告警"""
        for rule in self.alerts:
            metric = self.metrics.get(rule.metric_name)
            if metric and rule.check(metric):
                alert = {
                    'timestamp': datetime.now(),
                    'rule': rule,
                    'metric_value': metric.values[-1].value if metric.values else 0,
                    'severity': rule.severity,
                    'message': rule.message,
                }
                self.alert_history.append(alert)
                
                # 触发告警动作
                self._trigger_alert(alert)
    
    def _trigger_alert(self, alert: Dict):
        """触发告警"""
        # 在实际应用中,这里会发送邮件、Slack消息等
        print(f"ALERT [{alert['severity'].upper()}]: {alert['message']}")
        print(f"  Metric: {alert['rule'].metric_name} = {alert['metric_value']}")
        print(f"  Time: {alert['timestamp']}")
    
    def record_error(self, error: Exception, context: Dict = None):
        """记录错误"""
        error_entry = {
            'timestamp': datetime.now(),
            'type': error.__class__.__name__,
            'message': str(error),
            'traceback': traceback.format_exc(),
            'context': context or {},
        }
        self.error_log.append(error_entry)
    
    def get_metrics_report(self) -> Dict[str, Dict]:
        """获取指标报告"""
        report = {}
        
        for name, metric in self.metrics.items():
            stats = metric.get_stats()
            if stats:
                report[name] = {
                    'unit': metric.unit,
                    'stats': stats,
                    'recent_values': [
                        {'timestamp': p.timestamp.isoformat(), 'value': p.value}
                        for p in list(metric.values)[-10:]  # 最近10个值
                    ]
                }
        
        return report
    
    def get_alert_report(self) -> List[Dict]:
        """获取告警报告"""
        return list(self.alert_history)
    
    def get_error_report(self) -> List[Dict]:
        """获取错误报告"""
        return list(self.error_log)
    
    def get_health_status(self) -> Dict[str, Any]:
        """获取健康状态"""
        # 检查关键指标
        critical_metrics = ['cpu_percent', 'memory_percent', 'requests_per_second']
        
        health_status = 'healthy'
        issues = []
        
        for metric_name in critical_metrics:
            metric = self.metrics.get(metric_name)
            if metric and metric.values:
                latest_value = metric.values[-1].value
                
                if metric_name == 'cpu_percent' and latest_value > 90:
                    health_status = 'degraded'
                    issues.append(f"High CPU usage: {latest_value}%")
                
                elif metric_name == 'memory_percent' and latest_value > 90:
                    health_status = 'degraded'
                    issues.append(f"High memory usage: {latest_value}%")
                
                elif metric_name == 'requests_per_second' and latest_value > 1000:
                    health_status = 'degraded'
                    issues.append(f"High request rate: {latest_value} rps")
        
        # 检查错误率
        error_metric = self.metrics.get('errors_per_second')
        if error_metric and error_metric.values:
            error_rate = sum(p.value for p in error_metric.values) / len(error_metric.values)
            if error_rate > 10:  # 每秒10个错误
                health_status = 'unhealthy'
                issues.append(f"High error rate: {error_rate:.2f} errors/second")
        
        return {
            'status': health_status,
            'timestamp': datetime.now().isoformat(),
            'issues': issues,
            'active_alerts': len(self.alert_history),
            'recent_errors': len(self.error_log),
        }

9. 部署与扩展 {#部署扩展}

9.1 云原生部署配置

yaml 复制代码
# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cms-server
  labels:
    app: cms
spec:
  replicas: 3
  selector:
    matchLabels:
      app: cms
  template:
    metadata:
      labels:
        app: cms
    spec:
      containers:
      - name: cms
        image: myregistry/cms:latest
        ports:
        - containerPort: 8000
        env:
        - name: REDIS_URL
          valueFrom:
            secretKeyRef:
              name: cms-secrets
              key: redis-url
        - name: CDN_CONFIG
          valueFrom:
            configMapKeyRef:
              name: cms-config
              key: cdn-config
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5
        volumeMounts:
        - name: content-volume
          mountPath: /app/content
          readOnly: true
        - name: template-volume
          mountPath: /app/templates
          readOnly: true
        - name: static-volume
          mountPath: /app/static
          readOnly: true
        - name: cache-volume
          mountPath: /tmp/cache
      volumes:
      - name: content-volume
        persistentVolumeClaim:
          claimName: content-pvc
      - name: template-volume
        configMap:
          name: templates-config
      - name: static-volume
        emptyDir: {}
      - name: cache-volume
        emptyDir: {}
---
# kubernetes/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: cms-service
spec:
  selector:
    app: cms
  ports:
  - port: 80
    targetPort: 8000
  type: ClusterIP
---
# kubernetes/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cms-ingress
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/proxy-buffering: "on"
    nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - cms.example.com
    secretName: cms-tls
  rules:
  - host: cms.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: cms-service
            port:
              number: 80

9.2 自动扩展策略

python 复制代码
"""
自动扩展策略
基于负载预测的自动扩展
"""
import time
from typing import Dict, List
from dataclasses import dataclass
from datetime import datetime, timedelta
import numpy as np
from sklearn.linear_model import LinearRegression


@dataclass
class ScalingRule:
    """扩展规则"""
    metric: str
    operator: str  # 'gt', 'lt', 'avg_gt', 'avg_lt'
    threshold: float
    duration: int  # 持续秒数
    action: str  # 'scale_up', 'scale_down'
    amount: int  # 调整数量


class PredictiveScaler:
    """预测性扩展器"""
    
    def __init__(self, history_window: int = 3600):
        self.history_window = history_window
        self.metric_history = []
        self.scaling_history = []
        
        # 预测模型
        self.prediction_model = LinearRegression()
        
    def add_metric_point(self, metric: str, value: float, timestamp: datetime):
        """添加指标数据点"""
        self.metric_history.append({
            'timestamp': timestamp,
            'metric': metric,
            'value': value,
        })
        
        # 清理旧数据
        cutoff = datetime.now() - timedelta(seconds=self.history_window)
        self.metric_history = [
            point for point in self.metric_history
            if point['timestamp'] > cutoff
        ]
    
    def predict_load(self, metric: str, minutes_ahead: int = 5) -> float:
        """预测未来负载"""
        # 获取历史数据
        metric_data = [
            point for point in self.metric_history
            if point['metric'] == metric
        ]
        
        if len(metric_data) < 10:
            return 0
        
        # 准备训练数据
        timestamps = [
            point['timestamp'].timestamp() for point in metric_data
        ]
        values = [point['value'] for point in metric_data]
        
        # 训练模型
        X = np.array(timestamps).reshape(-1, 1)
        y = np.array(values)
        
        self.prediction_model.fit(X, y)
        
        # 预测未来
        future_time = time.time() + minutes_ahead * 60
        prediction = self.prediction_model.predict([[future_time]])
        
        return float(prediction[0])
    
    def should_scale(self, rules: List[ScalingRule], current_replicas: int) -> Dict[str, Any]:
        """检查是否需要扩展"""
        for rule in rules:
            if self._check_rule(rule):
                return {
                    'should_scale': True,
                    'rule': rule,
                    'current_replicas': current_replicas,
                    'new_replicas': self._calculate_new_replicas(rule, current_replicas),
                }
        
        return {'should_scale': False}
    
    def _check_rule(self, rule: ScalingRule) -> bool:
        """检查规则是否满足"""
        # 获取相关指标数据
        metric_data = [
            point for point in self.metric_history
            if point['metric'] == rule.metric
        ]
        
        if not metric_data:
            return False
        
        # 获取最近的数据
        recent_data = metric_data[-rule.duration:]
        if len(recent_data) < rule.duration:
            return False
        
        values = [point['value'] for point in recent_data]
        
        if rule.operator == 'gt':
            return all(v > rule.threshold for v in values)
        elif rule.operator == 'lt':
            return all(v < rule.threshold for v in values)
        elif rule.operator == 'avg_gt':
            avg = sum(values) / len(values)
            return avg > rule.threshold
        elif rule.operator == 'avg_lt':
            avg = sum(values) / len(values)
            return avg < rule.threshold
        
        return False
    
    def _calculate_new_replicas(self, rule: ScalingRule, current_replicas: int) -> int:
        """计算新的副本数"""
        if rule.action == 'scale_up':
            new_replicas = current_replicas + rule.amount
            return min(new_replicas, 10)  # 最大10个副本
        else:  # scale_down
            new_replicas = current_replicas - rule.amount
            return max(new_replicas, 1)  # 最少1个副本
    
    def get_scaling_recommendation(self, current_replicas: int) -> Dict[str, Any]:
        """获取扩展建议"""
        # 基于预测的扩展
        predicted_cpu = self.predict_load('cpu_percent', minutes_ahead=5)
        predicted_rps = self.predict_load('requests_per_second', minutes_ahead=5)
        
        recommendation = {
            'predicted_cpu': predicted_cpu,
            'predicted_rps': predicted_rps,
            'current_replicas': current_replicas,
            'recommended_replicas': current_replicas,
            'reason': 'No scaling needed',
        }
        
        # 基于预测的扩展规则
        if predicted_cpu > 80 or predicted_rps > current_replicas * 100:
            # 预测到高负载,提前扩展
            needed_replicas = max(
                int(predicted_rps / 100) + 1,
                int(predicted_cpu / 20) + 1
            )
            recommendation['recommended_replicas'] = min(needed_replicas, 10)
            recommendation['reason'] = f'Predicted high load: CPU={predicted_cpu:.1f}%, RPS={predicted_rps:.1f}'
        
        elif predicted_cpu < 20 and predicted_rps < current_replicas * 50:
            # 预测到低负载,考虑缩减
            recommendation['recommended_replicas'] = max(current_replicas - 1, 1)
            recommendation['reason'] = f'Predicted low load: CPU={predicted_cpu:.1f}%, RPS={predicted_rps:.1f}'
        
        return recommendation

10. 总结与展望 {#总结}

10.1 关键技术总结

通过本文的深度探讨,我们系统性地介绍了静态文件处理与模板渲染的完整技术栈:

  1. 静态文件处理

    • 高性能静态文件服务器实现
    • CDN集成与智能路由
    • 缓存策略与版本控制
    • 压缩与优化算法
  2. 模板渲染系统

    • 安全的模板引擎设计
    • 编译期优化技术
    • JIT编译与预编译
    • 模板继承与组件化
  3. 性能优化

    • 多级缓存架构
    • 资源合并与懒加载
    • 预测性扩展
    • 实时监控与调优
  4. 安全防护

    • 输入验证与输出编码
    • CSRF与XSS防护
    • 内容安全策略
    • 安全头配置

10.2 性能基准对比

技术方案 请求延迟 吞吐量 内存使用 适用场景
基础静态服务 50-100ms 1000 rps 小型应用
CDN加速 10-30ms 10000+ rps 全球用户
模板预编译 5-20ms 5000 rps 动态内容
JIT编译 1-10ms 10000+ rps 很高 高性能需求

10.3 数学优化模型回顾

  1. 缓存命中率优化
    Hit Rate opt = 1 − ∏ i = 1 n ( 1 − p i ) \text{Hit Rate}{\text{opt}} = 1 - \prod{i=1}^{n}(1 - p_i) Hit Rateopt=1−i=1∏n(1−pi)

    其中 p i p_i pi 是第i级缓存命中概率。

  2. 负载预测模型
    y ^ t + h = α + β t + ∑ i = 1 p ϕ i y t − i + ϵ t \hat{y}{t+h} = \alpha + \beta t + \sum{i=1}^{p} \phi_i y_{t-i} + \epsilon_t y^t+h=α+βt+i=1∑pϕiyt−i+ϵt

    使用ARIMA模型进行时间序列预测。

  3. 资源分配优化
    min ⁡ ∑ i = 1 n c i x i s.t. ∑ i = 1 n r i x i ≥ R \min \sum_{i=1}^{n} c_i x_i \quad \text{s.t.} \quad \sum_{i=1}^{n} r_i x_i \geq R mini=1∑ncixis.t.i=1∑nrixi≥R

    线性规划优化资源分配。

10.4 未来发展趋势

  1. AI驱动的优化

    • 基于使用模式的智能预加载
    • 自适应压缩算法
    • 预测性缓存预热
  2. 边缘计算集成

    • 边缘节点模板渲染
    • 地理感知CDN路由
    • 边缘缓存同步
  3. WebAssembly应用

    • 客户端模板渲染
    • 安全沙箱执行
    • 跨平台一致性
  4. 量子安全技术

    • 后量子密码学
    • 量子安全哈希
    • 量子密钥分发

10.5 最佳实践建议

  1. 开发阶段

    • 使用版本化静态资源
    • 实施自动化测试
    • 建立性能基准
  2. 部署阶段

    • 配置多层缓存
    • 启用安全头
    • 设置监控告警
  3. 运维阶段

    • 定期性能分析
    • 安全漏洞扫描
    • 容量规划预测
  4. 持续改进

    • A/B测试优化策略
    • 用户行为分析
    • 技术栈定期评估

10.6 结语

静态文件处理与模板渲染是现代Web应用的基石。通过本文的深度剖析,我们不仅掌握了核心技术原理和实现方法,更重要的是建立了完整的性能优化和安全防护体系。随着技术的不断发展,这些基础技术将继续演进,但核心原则------性能、安全、可维护性------将始终是优秀Web应用的追求目标。

记住:优秀的Web应用 = 高效的静态服务 + 智能的模板渲染 + 全面的安全防护 + 持续的优化改进


相关推荐
木土雨成小小测试员4 小时前
Python测试开发之跨域请求
开发语言·python
deephub4 小时前
PyCausalSim:基于模拟的因果发现的Python框架
开发语言·python·机器学习·因果发现
南_山无梅落4 小时前
8.Python3字典(dict):键值的增删改查_入门到进阶
python·算法
爱尔兰极光4 小时前
Python--常量和变量
开发语言·python
黑客思维者4 小时前
Python modbus-tk在配电物联网边缘网关的应用
开发语言·python·物联网
Cigaretter74 小时前
Day 30 类的定义与方法
开发语言·python
裤裤兔4 小时前
python2与python3的兼容
开发语言·python·numpy
我送炭你添花13 小时前
Pelco KBD300A 模拟器:03.Pelco-P 协议 8 字节完整拆解 + 与 Pelco-D 一一对应终极对照表
python·测试工具·运维开发
R.lin13 小时前
Java 8日期时间API完全指南
java·开发语言·python