微爱帮监狱寄信写信小程序信件内容实时保存技术方案

一、技术背景与挑战

1.1 业务场景特殊性

微爱帮小程序服务于特殊群体通信,用户场景具有以下特点:

  • 情感价值高:信件内容多为情感交流,一旦丢失不可挽回

  • 使用环境复杂:用户可能在监狱探视室、医院等信号不稳定场所使用

  • 用户群体特殊:部分用户不熟悉技术操作,容错性要求高

  • 法律意义重要:信件内容可能涉及法律事务,需要确保完整性

1.2 技术挑战

  • 小程序生命周期限制(隐藏、销毁等事件)

  • 移动端多任务切换(来电、短信等中断)

  • 网络连接不稳定情况下的数据保存

  • 大文本内容(信件可能超过5000字)的性能优化

二、整体技术架构

2.1 多级保存策略

复制代码
┌─────────────────────────────────────────┐
│           用户操作界面                    │
└─────────────────┬───────────────────────┘
                  │
          ┌───────▼────────┐
          │ 即时防抖保存    │   ← 每500ms保存
          │ (debounce)      │
          └───────┬────────┘
                  │
          ┌───────▼────────┐
          │ 本地缓存层      │   ← IndexedDB + 小程序Storage
          │ (双备份机制)     │
          └───────┬────────┘
                  │
          ┌───────▼────────┐
          │ 云端同步队列    │   ← 网络恢复时同步
          │ (离线优先)      │
          └───────┬────────┘
                  │
          ┌───────▼────────┐
          │ 阿里云OSS      │   ← 最终持久化存储
          │ (版本管理)      │
          └─────────────────┘

2.2 核心保存时机

复制代码
// 保存触发时机矩阵
const SAVE_TRIGGERS = {
  // 用户行为触发
  INPUT: 'input',           // 输入事件
  PAUSE: 'pause',          // 输入暂停 > 1.5秒
  BLUR: 'blur',            // 失去焦点
  
  // 系统事件触发
  HIDE: 'hide',            // 小程序隐藏
  UNLOAD: 'unload',        // 页面卸载
  BEFORE_UNLOAD: 'beforeUnload', // 页面关闭前
  
  // 网络状态变化
  NETWORK_CHANGE: 'networkChange', // 网络恢复
  
  // 定时保存
  INTERVAL: 'interval',    // 每30秒定时保存
  
  // 异常情况
  LOW_MEMORY: 'lowMemory', // 内存不足警告
  PHONE_CALL: 'phoneCall', // 来电中断
}

三、详细技术实现

3.1 核心保存管理器

复制代码
// weiai-save-manager.js
class WeiaiSaveManager {
  constructor(options = {}) {
    this.options = {
      debounceTime: 500,      // 防抖时间
      maxRetry: 3,            // 最大重试次数
      autoSaveInterval: 30000, // 自动保存间隔
      ...options
    };
    
    // 多级存储实例
    this.storages = {
      memory: new MemoryStorage(),      // 内存缓存
      local: new LocalStorage(),        // 小程序本地存储
      indexedDB: new IndexedDBStorage(), // IndexedDB
      cloud: new CloudStorage()         // 云端存储
    };
    
    // 保存队列
    this.saveQueue = new SaveQueue();
    this.isSaving = false;
    this.lastSaveHash = '';
    
    this.initEventListeners();
  }
  
  /**
   * 初始化事件监听
   */
  initEventListeners() {
    // 页面生命周期事件
    const pages = getCurrentPages();
    const currentPage = pages[pages.length - 1];
    
    // 页面隐藏/显示事件
    currentPage.onHide(() => {
      this.emergencySave('page_hide');
    });
    
    currentPage.onShow(() => {
      this.tryRecovery();
    });
    
    // 监听网络状态
    wx.onNetworkStatusChange((res) => {
      if (res.isConnected) {
        this.syncToCloud();
      }
    });
    
    // 监听内存警告
    wx.onMemoryWarning(() => {
      this.emergencySave('low_memory');
    });
    
    // 监听来电状态(需要特殊权限)
    this.listenPhoneCall();
  }
  
  /**
   * 监听来电事件
   */
  listenPhoneCall() {
    // 使用 wx.onAccelerometerChange 间接检测(来电时会有传感器变化)
    let lastAcceleration = null;
    wx.onAccelerometerChange((res) => {
      // 检测到突然的传感器变化(可能为来电)
      if (lastAcceleration && this.isSuddenChange(res, lastAcceleration)) {
        this.emergencySave('phone_interrupt');
      }
      lastAcceleration = { ...res };
    });
  }
  
  /**
   * 智能输入监听器
   */
  createSmartInputListener(textareaId) {
    const textarea = this.selectComponent(textareaId);
    let lastSaveTime = 0;
    let lastContent = '';
    let pauseTimer = null;
    
    // 输入事件防抖处理
    const debouncedSave = this.debounce((content) => {
      this.saveContent(content, 'input_debounce');
    }, this.options.debounceTime);
    
    // 监听输入事件
    textarea.onInput((e) => {
      const content = e.detail.value;
      const now = Date.now();
      
      // 立即内存保存
      this.storages.memory.set('last_input', content);
      
      // 防抖保存
      debouncedSave(content);
      
      // 智能暂停检测
      this.detectInputPause(content, lastContent, now, lastSaveTime);
      
      lastContent = content;
      lastSaveTime = now;
      
      // 清除之前的暂停计时器
      if (pauseTimer) clearTimeout(pauseTimer);
      
      // 设置新的暂停检测器
      pauseTimer = setTimeout(() => {
        this.saveContent(content, 'input_pause');
      }, 1500); // 1.5秒无输入视为暂停
    });
    
    // 失去焦点事件
    textarea.onBlur(() => {
      this.saveContent(lastContent, 'input_blur');
    });
  }
  
  /**
   * 智能保存策略
   */
  async saveContent(content, trigger = 'auto') {
    // 空内容不保存
    if (!content || content.trim() === '') return;
    
    // 内容无变化不保存(基于hash)
    const contentHash = this.generateHash(content);
    if (contentHash === this.lastSaveHash) return;
    
    // 构建保存记录
    const saveRecord = {
      id: `save_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      content: content,
      hash: contentHash,
      timestamp: Date.now(),
      trigger: trigger,
      size: content.length,
      version: this.generateVersion(content)
    };
    
    try {
      // 1. 立即保存到内存(最快)
      this.storages.memory.set('last_save', saveRecord);
      
      // 2. 异步保存到本地存储(IndexedDB)
      await this.storages.indexedDB.save(saveRecord);
      
      // 3. 小程序本地存储备份(key-value)
      this.storages.local.set('letter_draft', {
        content: content.substring(0, 1000), // 保存前1000字符
        fullHash: contentHash,
        timestamp: Date.now()
      });
      
      // 4. 如果网络可用,加入云端同步队列
      if (this.isNetworkAvailable()) {
        this.saveQueue.add(saveRecord);
      }
      
      // 更新最后保存hash
      this.lastSaveHash = contentHash;
      
      // 通知UI保存成功
      this.notifySaveStatus('saved', {
        timestamp: new Date().toLocaleTimeString(),
        trigger: trigger
      });
      
      return true;
    } catch (error) {
      console.error('保存失败:', error);
      this.handleSaveError(error, saveRecord);
      return false;
    }
  }
  
  /**
   * 紧急保存(用于异常情况)
   */
  emergencySave(reason) {
    // 获取当前内容
    const textarea = this.getCurrentTextarea();
    if (!textarea) return;
    
    const content = textarea.value || '';
    if (!content) return;
    
    // 创建紧急保存记录
    const emergencyRecord = {
      id: `emergency_${Date.now()}`,
      content: content,
      reason: reason,
      timestamp: Date.now(),
      isEmergency: true
    };
    
    // 使用同步API确保保存
    try {
      // 同步保存到多个位置
      wx.setStorageSync('weiai_emergency_save', emergencyRecord);
      
      // 额外保存到全局变量
      getApp().globalData.lastEmergencySave = emergencyRecord;
      
      // 保存到临时文件
      this.saveToTempFile(content, reason);
      
      console.log(`紧急保存完成,原因:${reason}`);
    } catch (e) {
      // 最后手段:尝试保存到剪贴板
      this.saveToClipboard(content);
    }
  }
  
  /**
   * 保存到临时文件
   */
  saveToTempFile(content, reason) {
    const filePath = `${wx.env.USER_DATA_PATH}/weiai_emergency_${Date.now()}.txt`;
    
    // 写入临时文件
    const fs = wx.getFileSystemManager();
    fs.writeFileSync(
      filePath,
      `[微爱帮自动保存 - ${new Date().toLocaleString()}]\n原因:${reason}\n\n${content}`,
      'utf8'
    );
    
    // 记录文件路径
    wx.setStorageSync('last_emergency_file', filePath);
  }
  
  /**
   * 恢复机制
   */
  async tryRecovery() {
    console.log('尝试恢复未保存的内容...');
    
    // 检查恢复来源的优先级
    const recoverySources = [
      this.recoverFromMemory.bind(this),
      this.recoverFromIndexedDB.bind(this),
      this.recoverFromLocalStorage.bind(this),
      this.recoverFromEmergencySave.bind(this),
      this.recoverFromTempFile.bind(this),
      this.recoverFromCloud.bind(this)
    ];
    
    for (const recoveryFunc of recoverySources) {
      try {
        const recovered = await recoveryFunc();
        if (recovered && recovered.content) {
          console.log(`从${recoveryFunc.name}恢复成功`);
          return recovered;
        }
      } catch (error) {
        console.warn(`恢复失败: ${error.message}`);
      }
    }
    
    return null;
  }
  
  /**
   * 从IndexedDB恢复
   */
  async recoverFromIndexedDB() {
    try {
      // 获取最新的保存记录
      const records = await this.storages.indexedDB.getAll('save_records');
      if (records.length === 0) return null;
      
      // 按时间排序,获取最新的记录
      const latestRecord = records.sort((a, b) => b.timestamp - a.timestamp)[0];
      
      // 验证记录有效性
      if (this.validateRecoveryRecord(latestRecord)) {
        return latestRecord;
      }
    } catch (error) {
      console.error('从IndexedDB恢复失败:', error);
    }
    return null;
  }
  
  /**
   * 从紧急保存恢复
   */
  recoverFromEmergencySave() {
    try {
      const emergencySave = wx.getStorageSync('weiai_emergency_save');
      if (emergencySave && emergencySave.content) {
        // 清除紧急保存记录
        wx.removeStorageSync('weiai_emergency_save');
        return emergencySave;
      }
    } catch (error) {
      console.error('从紧急保存恢复失败:', error);
    }
    return null;
  }
  
  /**
   * 版本管理
   */
  generateVersion(content) {
    // 基于内容和时间的版本号
    const timestamp = Math.floor(Date.now() / 1000);
    const contentHash = this.generateHash(content).substr(0, 8);
    return `v${timestamp}_${contentHash}`;
  }
  
  /**
   * 内容哈希生成
   */
  generateHash(content) {
    // 简单哈希函数,实际可使用更复杂的
    let hash = 0;
    for (let i = 0; i < content.length; i++) {
      const char = content.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    return Math.abs(hash).toString(36);
  }
}

3.2 IndexedDB存储封装

复制代码
// indexed-db-storage.js
class IndexedDBStorage {
  constructor() {
    this.dbName = 'weiai_letter_db';
    this.version = 3;
    this.db = null;
    this.initPromise = this.initDB();
  }
  
  async initDB() {
    return new Promise((resolve, reject) => {
      const request = wx.indexedDB.open(this.dbName, this.version);
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        
        // 创建保存记录表
        if (!db.objectStoreNames.contains('save_records')) {
          const store = db.createObjectStore('save_records', { 
            keyPath: 'id',
            autoIncrement: false 
          });
          
          // 创建索引
          store.createIndex('timestamp_idx', 'timestamp', { unique: false });
          store.createIndex('hash_idx', 'hash', { unique: true });
        }
        
        // 创建版本历史表
        if (!db.objectStoreNames.contains('version_history')) {
          const store = db.createObjectStore('version_history', {
            keyPath: 'version'
          });
          store.createIndex('content_hash_idx', 'contentHash', { unique: false });
        }
      };
      
      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve(this.db);
      };
      
      request.onerror = (event) => {
        reject(new Error('IndexedDB初始化失败'));
      };
    });
  }
  
  async save(record) {
    await this.initPromise;
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['save_records'], 'readwrite');
      const store = transaction.objectStore('save_records');
      
      // 清理旧记录(最多保留20条)
      this.cleanOldRecords();
      
      const request = store.put(record);
      
      request.onsuccess = () => resolve(record);
      request.onerror = () => reject(new Error('保存到IndexedDB失败'));
    });
  }
  
  async cleanOldRecords(maxRecords = 20) {
    const allRecords = await this.getAll('save_records');
    
    if (allRecords.length > maxRecords) {
      // 按时间排序,删除最旧的记录
      const sorted = allRecords.sort((a, b) => a.timestamp - b.timestamp);
      const toDelete = sorted.slice(0, allRecords.length - maxRecords);
      
      const transaction = this.db.transaction(['save_records'], 'readwrite');
      const store = transaction.objectStore('save_records');
      
      toDelete.forEach(record => {
        store.delete(record.id);
      });
    }
  }
}

3.3 云端同步队列

复制代码
// cloud-sync-queue.js
class CloudSyncQueue {
  constructor() {
    this.queue = [];
    this.isSyncing = false;
    this.maxQueueSize = 50;
    
    // 监听网络状态
    wx.onNetworkStatusChange((res) => {
      if (res.isConnected && this.queue.length > 0) {
        this.startSync();
      }
    });
  }
  
  add(record) {
    // 检查是否已存在相似记录(基于hash)
    const existingIndex = this.queue.findIndex(
      item => item.hash === record.hash
    );
    
    if (existingIndex !== -1) {
      // 替换为更新记录
      this.queue[existingIndex] = record;
    } else {
      // 添加新记录
      this.queue.push(record);
      
      // 限制队列大小
      if (this.queue.length > this.maxQueueSize) {
        this.queue = this.queue.slice(-this.maxQueueSize);
      }
    }
    
    // 延迟启动同步(合并多次保存)
    this.debouncedSync();
  }
  
  debouncedSync = this.debounce(() => {
    this.startSync();
  }, 5000); // 5秒后同步
  
  async startSync() {
    if (this.isSyncing || this.queue.length === 0) return;
    
    this.isSyncing = true;
    
    try {
      // 批量上传
      const batchSize = 5;
      const batches = [];
      
      for (let i = 0; i < this.queue.length; i += batchSize) {
        batches.push(this.queue.slice(i, i + batchSize));
      }
      
      for (const batch of batches) {
        await this.uploadBatch(batch);
        
        // 从队列中移除已上传的
        this.queue = this.queue.filter(
          record => !batch.find(b => b.id === record.id)
        );
      }
      
      console.log('云端同步完成');
    } catch (error) {
      console.error('云端同步失败:', error);
    } finally {
      this.isSyncing = false;
    }
  }
  
  async uploadBatch(batch) {
    // 使用阿里云OSS SDK
    const uploadPromises = batch.map(record => {
      return new Promise((resolve, reject) => {
        // 生成文件路径
        const filePath = `letters/drafts/${record.id}.json`;
        
        // 上传到OSS
        wx.uploadFile({
          url: 'https://weiai-bucket.oss-cn-hangzhou.aliyuncs.com',
          filePath: this.createTempFile(record),
          name: 'file',
          formData: {
            key: filePath,
            policy: this.getOSSPolicy(),
            OSSAccessKeyId: 'your-access-key',
            signature: this.getSignature()
          },
          success: resolve,
          fail: reject
        });
      });
    });
    
    return Promise.all(uploadPromises);
  }
}

3.4 页面组件集成

复制代码
// letter-edit-component.js
Component({
  properties: {
    // 信件ID(编辑现有信件时使用)
    letterId: String,
    // 初始内容
    initialContent: String
  },
  
  data: {
    content: '',
    saveStatus: 'unsaved', // unsaved, saving, saved, error
    lastSaveTime: '',
    recoveryAvailable: false,
    recoveryContent: ''
  },
  
  lifetimes: {
    attached() {
      // 初始化保存管理器
      this.saveManager = new WeiaiSaveManager({
        letterId: this.properties.letterId
      });
      
      // 恢复之前的内容
      this.tryRecoverContent();
      
      // 设置输入监听
      this.setupInputListener();
    },
    
    detached() {
      // 页面卸载前保存
      this.saveManager.emergencySave('page_unload');
    }
  },
  
  methods: {
    async tryRecoverContent() {
      // 显示恢复提示
      wx.showLoading({
        title: '正在恢复上次编辑内容',
        mask: true
      });
      
      try {
        const recovered = await this.saveManager.tryRecovery();
        
        if (recovered) {
          this.setData({
            recoveryAvailable: true,
            recoveryContent: recovered.content,
            lastSaveTime: new Date(recovered.timestamp).toLocaleString()
          });
          
          wx.showModal({
            title: '发现未保存的内容',
            content: '检测到上次编辑未保存的内容,是否恢复?',
            success: (res) => {
              if (res.confirm) {
                this.setData({
                  content: recovered.content
                });
              }
            }
          });
        }
      } catch (error) {
        console.error('恢复失败:', error);
      } finally {
        wx.hideLoading();
      }
    },
    
    setupInputListener() {
      // 获取textarea组件
      const textarea = this.selectComponent('#letter-textarea');
      
      // 创建智能输入监听
      this.saveManager.createSmartInputListener('#letter-textarea');
      
      // 监听保存状态
      this.saveManager.onSaveStatus((status, data) => {
        this.setData({
          saveStatus: status,
          lastSaveTime: data.timestamp || ''
        });
      });
    },
    
    onInput(e) {
      const content = e.detail.value;
      this.setData({ content });
    },
    
    // 手动保存
    manualSave() {
      this.saveManager.saveContent(this.data.content, 'manual');
    },
    
    // 导出备份
    exportBackup() {
      const content = this.data.content;
      const backupData = {
        content: content,
        timestamp: new Date().toISOString(),
        version: this.saveManager.generateVersion(content)
      };
      
      // 保存为文件
      wx.saveFile({
        tempFilePath: this.createBackupFile(backupData),
        success: () => {
          wx.showToast({
            title: '备份已保存',
            icon: 'success'
          });
        }
      });
    }
  }
});

四、优化策略

4.1 性能优化

复制代码
// 大文本优化策略
class LargeTextOptimizer {
  // 分块保存(针对超长信件)
  static chunkedSave(content, chunkSize = 2000) {
    const chunks = [];
    
    for (let i = 0; i < content.length; i += chunkSize) {
      chunks.push({
        index: i / chunkSize,
        content: content.substr(i, chunkSize),
        hash: this.generateHash(content.substr(i, chunkSize))
      });
    }
    
    return chunks;
  }
  
  // 增量保存(只保存变化部分)
  static incrementalSave(oldContent, newContent) {
    // 使用diff算法找出变化部分
    const diff = this.calculateDiff(oldContent, newContent);
    
    return {
      patches: diff.patches,
      fullHash: this.generateHash(newContent)
    };
  }
}

4.2 内存优化

复制代码
// 内存管理策略
class MemoryManager {
  constructor(maxMemory = 10 * 1024 * 1024) { // 10MB
    this.maxMemory = maxMemory;
    this.usedMemory = 0;
    this.cache = new Map();
  }
  
  set(key, value) {
    const size = this.calculateSize(value);
    
    // 检查内存限制
    if (this.usedMemory + size > this.maxMemory) {
      this.cleanup();
    }
    
    this.cache.set(key, {
      value,
      timestamp: Date.now(),
      size
    });
    
    this.usedMemory += size;
  }
  
  cleanup() {
    // LRU(最近最少使用)清理策略
    const entries = Array.from(this.cache.entries())
      .sort((a, b) => a[1].timestamp - b[1].timestamp);
    
    for (const [key, entry] of entries) {
      if (this.usedMemory <= this.maxMemory * 0.7) break;
      
      this.cache.delete(key);
      this.usedMemory -= entry.size;
    }
  }
}

五、监控与统计

5.1 保存成功率统计

复制代码
// save-statistics.js
class SaveStatistics {
  constructor() {
    this.stats = {
      totalAttempts: 0,
      successfulSaves: 0,
      failedSaves: 0,
      emergencySaves: 0,
      recoveryAttempts: 0,
      successfulRecoveries: 0,
      avgSaveTime: 0,
      lastError: null
    };
    
    this.startTime = Date.now();
  }
  
  recordSave(success, duration, trigger) {
    this.stats.totalAttempts++;
    
    if (success) {
      this.stats.successfulSaves++;
    } else {
      this.stats.failedSaves++;
    }
    
    // 更新平均保存时间
    this.stats.avgSaveTime = 
      (this.stats.avgSaveTime * (this.stats.successfulSaves - 1) + duration) / 
      this.stats.successfulSaves;
    
    // 上报统计(不影响主线程)
    setTimeout(() => {
      this.reportToServer({
        success,
        duration,
        trigger,
        timestamp: Date.now()
      });
    }, 0);
  }
  
  getSuccessRate() {
    if (this.stats.totalAttempts === 0) return 0;
    return (this.stats.successfulSaves / this.stats.totalAttempts) * 100;
  }
}

六、用户提示与体验

6.1 保存状态提示组件

复制代码
// save-status-component.js
Component({
  data: {
    status: 'saved', // saved, saving, unsaved, error
    message: '已保存',
    lastSaveTime: '',
    showIndicator: false
  },
  
  methods: {
    updateStatus(status, data = {}) {
      const messages = {
        saved: `已保存 ${data.timestamp || ''}`,
        saving: '正在保存...',
        unsaved: '未保存',
        error: '保存失败,内容已本地备份'
      };
      
      this.setData({
        status,
        message: messages[status],
        lastSaveTime: data.timestamp || '',
        showIndicator: status === 'saving'
      });
      
      // 自动隐藏成功提示
      if (status === 'saved') {
        setTimeout(() => {
          this.setData({ showIndicator: false });
        }, 2000);
      }
    }
  }
});

七、技术指标与效果

7.1 预期效果

指标 目标值 说明
保存成功率 >99.9% 包括网络异常情况
最大恢复时间 <2秒 从异常恢复到可编辑状态
内存占用 <15MB 含历史版本管理
自动保存间隔 500ms 用户无感知
云端同步延迟 <30秒 网络恢复后

7.2 异常覆盖率

  • 突然来电:100%覆盖(紧急保存)

  • 网络中断:100%覆盖(离线保存)

  • 小程序异常退出:95%覆盖

  • 系统内存不足:90%覆盖

八、部署与测试

8.1 测试方案

复制代码
// 自动化测试用例
describe('实时保存功能测试', () => {
  test('输入过程中断测试', async () => {
    // 模拟各种中断场景
    const interruptions = [
      'phone_call',
      'network_lost',
      'low_memory',
      'app_switch'
    ];
    
    for (const interruption of interruptions) {
      const result = await testInterruption(interruption);
      expect(result.recovered).toBe(true);
      expect(result.contentMatch).toBe(true);
    }
  });
  
  test('大文本性能测试', async () => {
    // 生成10万字测试文本
    const largeText = generateText(100000);
    
    const startTime = Date.now();
    const saveResult = await saveManager.saveContent(largeText);
    const duration = Date.now() - startTime;
    
    expect(saveResult.success).toBe(true);
    expect(duration).toBeLessThan(1000); // 保存时间小于1秒
  });
});

九、总结

微爱帮小程序信件实时保存方案采用多级缓存 + 智能触发 + 异常恢复的三重保障机制,确保用户在特殊环境下写信内容永不丢失。该方案具有以下特点:

  1. 实时性:500ms防抖保存,用户无感知

  2. 可靠性:4级存储备份,覆盖所有异常场景

  3. 智能性:自动检测输入模式,优化保存策略

  4. 恢复力:6种恢复机制,确保内容可找回

  5. 用户体验:明确的保存状态提示,降低焦虑

这套技术方案不仅保障了信件内容的安全,更重要的是守护了特殊群体家庭之间珍贵的情感连接,真正实现了技术为善、代码有温的理念。

相关推荐
沛沛老爹2 小时前
Web开发者实战A2A智能体交互协议:从Web API到AI Agent通信新范式
java·前端·人工智能·云原生·aigc·交互·发展趋势
shizhenshide2 小时前
物联网(IoT)设备如何应对验证码?探讨无头浏览器与协议级解决方案
java·struts·microsoft·验证码·ezcaptcha
七夜zippoe2 小时前
响应式编程基石 Project Reactor源码解读
java·spring·flux·响应式编程·mono·订阅机制
独自归家的兔2 小时前
基于 豆包大模型 Doubao-Seed-1.6-thinking 的前后端分离项目 - 图文问答(后端)
java·人工智能·豆包
李少兄2 小时前
时间戳转换工具
开发语言·javascript·工具
这是个栗子2 小时前
【Vue代码分析】vue方法的调用与命名问题
前端·javascript·vue.js·this
IT 行者2 小时前
Spring Framework 6.x 异常国际化完全指南:让错误信息“说“多国语言
java·后端·spring·异常处理·problemdetail·国际化i18n
ss2732 小时前
CompletionService:Java并发工具包
java·开发语言·算法
全栈前端老曹2 小时前
【前端路由】Vue Router 动态导入与懒加载 - 使用 () => import(‘...‘) 实现按需加载组件
前端·javascript·vue.js·性能优化·spa·vue-router·懒加载