7.1 HTTP 客户端封装

7.1.1 基础 HTTP 客户端

在 Sciter-JS 中,我们可以使用内置的网络 API 或封装自己的 HTTP 客户端来处理网络请求。

graph TB
    A[HTTP 客户端] --> B[请求拦截器]
    A --> C[响应拦截器]
    A --> D[缓存管理]
    A --> E[错误处理]
    
    B --> F[认证处理]
    B --> G[请求日志]
    B --> H[参数处理]
    
    C --> I[响应转换]
    C --> J[错误检查]
    C --> K[数据验证]
    
    D --> L[内存缓存]
    D --> M[本地存储]
    D --> N[缓存策略]
// 基础 HTTP 客户端
class HttpClient {
    constructor(options = {}) {
        this.baseURL = options.baseURL || '';
        this.timeout = options.timeout || 10000;
        this.headers = options.headers || {};
        this.interceptors = {
            request: [],
            response: []
        };
    }
    
    // 请求拦截器
    addRequestInterceptor(interceptor) {
        this.interceptors.request.push(interceptor);
    }
    
    // 响应拦截器
    addResponseInterceptor(interceptor) {
        this.interceptors.response.push(interceptor);
    }
    
    // 核心请求方法
    async request(config) {
        try {
            // 合并配置
            const requestConfig = {
                method: 'GET',
                headers: { ...this.headers },
                timeout: this.timeout,
                ...config,
                url: this.buildURL(config.url)
            };
            
            // 执行请求拦截器
            const processedConfig = await this.executeRequestInterceptors(requestConfig);
            
            // 发送请求
            const response = await this.sendRequest(processedConfig);
            
            // 执行响应拦截器
            const processedResponse = await this.executeResponseInterceptors(response);
            
            return processedResponse;
        } catch (error) {
            throw this.handleError(error);
        }
    }
    
    // 便捷方法
    get(url, config = {}) {
        return this.request({ ...config, method: 'GET', url });
    }
    
    post(url, data, config = {}) {
        return this.request({ ...config, method: 'POST', url, data });
    }
    
    put(url, data, config = {}) {
        return this.request({ ...config, method: 'PUT', url, data });
    }
    
    delete(url, config = {}) {
        return this.request({ ...config, method: 'DELETE', url });
    }
}

7.2 请求拦截和响应处理

7.2.1 认证拦截器

// 认证拦截器
class AuthInterceptor {
    constructor(tokenProvider) {
        this.tokenProvider = tokenProvider;
    }
    
    // 请求拦截器
    requestInterceptor = async (config) => {
        const token = await this.tokenProvider.getToken();
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    }
    
    // 响应拦截器
    responseInterceptor = async (response) => {
        // 处理 401 未授权
        if (response.status === 401) {
            await this.tokenProvider.refreshToken();
            throw new Error('TOKEN_EXPIRED');
        }
        return response;
    }
}

// Token 提供者
class TokenProvider {
    constructor() {
        this.token = null;
        this.refreshToken = null;
        this.refreshPromise = null;
    }
    
    async getToken() {
        if (!this.token || this.isTokenExpired()) {
            await this.refreshTokens();
        }
        return this.token;
    }
    
    async refreshTokens() {
        // 防止并发刷新
        if (this.refreshPromise) {
            return this.refreshPromise;
        }
        
        this.refreshPromise = this.performTokenRefresh();
        
        try {
            await this.refreshPromise;
        } finally {
            this.refreshPromise = null;
        }
    }
    
    isTokenExpired() {
        if (!this.token) return true;
        
        try {
            const payload = JSON.parse(atob(this.token.split('.')[1]));
            return Date.now() >= payload.exp * 1000;
        } catch {
            return true;
        }
    }
}

7.3 数据缓存策略

7.3.1 缓存管理器

// 缓存策略枚举
const CacheStrategy = {
    CACHE_FIRST: 'cache-first',
    NETWORK_FIRST: 'network-first',
    CACHE_ONLY: 'cache-only',
    NETWORK_ONLY: 'network-only',
    STALE_WHILE_REVALIDATE: 'stale-while-revalidate'
};

// 内存缓存存储
class MemoryCacheStorage {
    constructor(maxSize = 100) {
        this.cache = new Map();
        this.maxSize = maxSize;
        this.accessOrder = [];
    }
    
    async get(key) {
        const item = this.cache.get(key);
        if (!item) return null;
        
        if (Date.now() > item.expiry) {
            this.cache.delete(key);
            this.removeFromAccessOrder(key);
            return null;
        }
        
        // 更新访问顺序
        this.updateAccessOrder(key);
        return item.value;
    }
    
    async set(key, value, ttl = 300000) {
        // 检查容量限制
        if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
            this.evictLeastRecentlyUsed();
        }
        
        this.cache.set(key, {
            value,
            expiry: Date.now() + ttl
        });
        
        this.updateAccessOrder(key);
    }
    
    // LRU 淘汰策略
    evictLeastRecentlyUsed() {
        if (this.accessOrder.length > 0) {
            const lruKey = this.accessOrder[0];
            this.cache.delete(lruKey);
            this.accessOrder.shift();
        }
    }
    
    updateAccessOrder(key) {
        this.removeFromAccessOrder(key);
        this.accessOrder.push(key);
    }
    
    removeFromAccessOrder(key) {
        const index = this.accessOrder.indexOf(key);
        if (index > -1) {
            this.accessOrder.splice(index, 1);
        }
    }
}

7.4 数据处理和转换

7.4.1 数据转换器

// 数据转换器基类
class DataTransformer {
    transform(data) {
        throw new Error('子类必须实现 transform 方法');
    }
    
    reverse(data) {
        throw new Error('子类必须实现 reverse 方法');
    }
}

// 日期转换器
class DateTransformer extends DataTransformer {
    constructor(options = {}) {
        super();
        this.dateFields = options.dateFields || [];
        this.autoDetect = options.autoDetect !== false;
    }
    
    transform(data) {
        if (!data || typeof data !== 'object') return data;
        
        const result = Array.isArray(data) ? [] : {};
        
        for (const [key, value] of Object.entries(data)) {
            if (this.shouldTransformField(key, value)) {
                result[key] = this.parseDate(value);
            } else if (typeof value === 'object') {
                result[key] = this.transform(value);
            } else {
                result[key] = value;
            }
        }
        
        return result;
    }
    
    shouldTransformField(fieldName, value) {
        if (this.dateFields.length > 0) {
            return this.dateFields.includes(fieldName);
        }
        
        if (!this.autoDetect) return false;
        
        return typeof value === 'string' && 
               (this.isDateField(fieldName) || this.isDateString(value));
    }
    
    isDateField(fieldName) {
        const datePatterns = [
            /date/i, /time/i, /created/i, /updated/i
        ];
        return datePatterns.some(pattern => pattern.test(fieldName));
    }
    
    isDateString(str) {
        if (typeof str !== 'string') return false;
        return /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(str);
    }
    
    parseDate(dateString) {
        try {
            const date = new Date(dateString);
            return isNaN(date.getTime()) ? dateString : date;
        } catch {
            return dateString;
        }
    }
}

7.4.2 数据验证系统

// 数据验证器
class DataValidator {
    constructor() {
        this.rules = new Map();
        this.setupBuiltinRules();
    }
    
    // 验证数据
    validate(data, schema) {
        const errors = [];
        this.validateObject(data, schema, [], errors);
        
        return {
            isValid: errors.length === 0,
            errors
        };
    }
    
    validateObject(data, schema, path, errors) {
        if (!schema || typeof schema !== 'object') return;
        
        for (const [field, rules] of Object.entries(schema)) {
            const fieldPath = [...path, field];
            const value = data?.[field];
            
            this.validateField(value, rules, fieldPath, errors);
        }
    }
    
    validateField(value, rules, path, errors) {
        if (Array.isArray(rules)) {
            rules.forEach(rule => this.validateField(value, rule, path, errors));
            return;
        }
        
        if (typeof rules === 'string') {
            this.applyRule(value, rules, path, errors);
        }
    }
    
    applyRule(value, ruleName, path, errors) {
        const rule = this.rules.get(ruleName);
        if (!rule) return;
        
        const result = rule(value);
        if (!result.isValid) {
            errors.push({
                path: path.join('.'),
                rule: ruleName,
                message: result.message,
                value
            });
        }
    }
    
    setupBuiltinRules() {
        // 必填验证
        this.rules.set('required', (value) => {
            const isValid = value !== null && value !== undefined && value !== '';
            return {
                isValid,
                message: isValid ? '' : '此字段为必填项'
            };
        });
        
        // 邮箱验证
        this.rules.set('email', (value) => {
            const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            const isValid = emailPattern.test(value);
            return {
                isValid,
                message: isValid ? '' : '邮箱格式不正确'
            };
        });
    }
}

7.5 实践练习

7.5.1 完整的数据服务实现

// 用户数据服务
class UserService {
    constructor() {
        this.http = new HttpClient({
            baseURL: '/api',
            timeout: 10000
        });
        
        this.setupInterceptors();
        this.setupTransformers();
        this.setupValidators();
    }
    
    setupInterceptors() {
        const tokenProvider = new TokenProvider();
        const authInterceptor = new AuthInterceptor(tokenProvider);
        
        this.http.addRequestInterceptor(authInterceptor.requestInterceptor);
        this.http.addResponseInterceptor(authInterceptor.responseInterceptor);
    }
    
    setupTransformers() {
        this.transformer = new DateTransformer({
            dateFields: ['createdAt', 'updatedAt']
        });
    }
    
    setupValidators() {
        this.validator = new DataValidator();
        
        this.userSchema = {
            name: ['required'],
            email: ['required', 'email'],
            age: ['required']
        };
    }
    
    // 获取用户列表
    async getUsers(params = {}) {
        try {
            const response = await this.http.get('/users', { params });
            return this.transformer.transform(response.data);
        } catch (error) {
            throw new Error(`获取用户列表失败: ${error.message}`);
        }
    }
    
    // 创建用户
    async createUser(userData) {
        // 验证数据
        const validation = this.validator.validate(userData, this.userSchema);
        if (!validation.isValid) {
            throw new Error('数据验证失败');
        }
        
        try {
            const response = await this.http.post('/users', userData);
            return this.transformer.transform(response.data);
        } catch (error) {
            throw new Error(`创建用户失败: ${error.message}`);
        }
    }
    
    // 更新用户
    async updateUser(id, userData) {
        try {
            const response = await this.http.put(`/users/${id}`, userData);
            return this.transformer.transform(response.data);
        } catch (error) {
            throw new Error(`更新用户失败: ${error.message}`);
        }
    }
    
    // 删除用户
    async deleteUser(id) {
        try {
            await this.http.delete(`/users/${id}`);
            return true;
        } catch (error) {
            throw new Error(`删除用户失败: ${error.message}`);
        }
    }
}

7.5.2 使用示例

<!DOCTYPE html>
<html>
<head>
    <title>网络请求示例</title>
    <style>
        .user-list {
            margin: 20px;
        }
        
        .user-item {
            padding: 10px;
            border: 1px solid #ddd;
            margin: 5px 0;
            border-radius: 4px;
        }
        
        .form-group {
            margin: 10px 0;
        }
        
        .form-group label {
            display: block;
            margin-bottom: 5px;
        }
        
        .form-group input {
            width: 200px;
            padding: 5px;
        }
        
        .btn {
            padding: 8px 16px;
            margin: 5px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        
        .btn-primary {
            background-color: #007bff;
            color: white;
        }
        
        .error {
            color: red;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>用户管理</h1>
        
        <div class="form-section">
            <h2>添加用户</h2>
            <div class="form-group">
                <label>姓名:</label>
                <input type="text" id="name" placeholder="请输入姓名">
            </div>
            <div class="form-group">
                <label>邮箱:</label>
                <input type="email" id="email" placeholder="请输入邮箱">
            </div>
            <div class="form-group">
                <label>年龄:</label>
                <input type="number" id="age" placeholder="请输入年龄">
            </div>
            <button class="btn btn-primary" onclick="createUser()">添加用户</button>
            <div id="error" class="error"></div>
        </div>
        
        <div class="user-list">
            <h2>用户列表</h2>
            <button class="btn" onclick="loadUsers()">刷新列表</button>
            <div id="users"></div>
        </div>
    </div>
    
    <script>
        // 初始化用户服务
        const userService = new UserService();
        
        // 加载用户列表
        async function loadUsers() {
            try {
                const users = await userService.getUsers();
                displayUsers(users);
            } catch (error) {
                showError(error.message);
            }
        }
        
        // 显示用户列表
        function displayUsers(users) {
            const container = document.getElementById('users');
            container.innerHTML = '';
            
            users.forEach(user => {
                const userDiv = document.createElement('div');
                userDiv.className = 'user-item';
                userDiv.innerHTML = `
                    <strong>${user.name}</strong> (${user.email})
                    <br>年龄: ${user.age}
                    <br>创建时间: ${user.createdAt ? user.createdAt.toLocaleString() : 'N/A'}
                    <button class="btn" onclick="deleteUser(${user.id})">删除</button>
                `;
                container.appendChild(userDiv);
            });
        }
        
        // 创建用户
        async function createUser() {
            const userData = {
                name: document.getElementById('name').value,
                email: document.getElementById('email').value,
                age: parseInt(document.getElementById('age').value)
            };
            
            try {
                await userService.createUser(userData);
                clearForm();
                loadUsers();
                showError(''); // 清除错误信息
            } catch (error) {
                showError(error.message);
            }
        }
        
        // 删除用户
        async function deleteUser(id) {
            if (confirm('确定要删除这个用户吗?')) {
                try {
                    await userService.deleteUser(id);
                    loadUsers();
                } catch (error) {
                    showError(error.message);
                }
            }
        }
        
        // 清除表单
        function clearForm() {
            document.getElementById('name').value = '';
            document.getElementById('email').value = '';
            document.getElementById('age').value = '';
        }
        
        // 显示错误信息
        function showError(message) {
            document.getElementById('error').textContent = message;
        }
        
        // 页面加载时获取用户列表
        document.addEventListener('DOMContentLoaded', loadUsers);
    </script>
</body>
</html>

7.6 本章小结

7.6.1 核心概念

  • HTTP 客户端封装: 提供统一的网络请求接口
  • 拦截器模式: 在请求和响应过程中插入自定义逻辑
  • 缓存策略: 提高应用性能和用户体验
  • 数据转换: 统一前后端数据格式
  • 数据验证: 确保数据的完整性和正确性

7.6.2 技术要点

  1. 请求拦截器用于添加认证信息、日志记录等
  2. 响应拦截器用于错误处理、数据转换等
  3. 多级缓存提供更好的性能和可靠性
  4. 数据转换器支持链式组合和双向转换
  5. 验证系统支持复杂的验证规则和自定义验证

7.6.3 最佳实践

  • 合理设置缓存策略和过期时间
  • 使用拦截器统一处理通用逻辑
  • 实现完善的错误处理机制
  • 对敏感数据进行验证和转换
  • 提供友好的用户反馈

7.6.4 下一章预告

下一章我们将学习性能优化与调试技巧,包括: - 性能监控和分析 - 内存管理和优化 - 渲染性能优化 - 调试工具和技巧 - 性能测试方法