开源分享:实习中开发的单点登录Cookie管理神器 | 从痛点到产品的完整实践

CookieFlow

🚀 一个简洁现代的Cookie复制和管理工具

📖 开发背景

单点登录导致不同环境需要使用不同个cookie,手动去控制台粘贴过于繁琐麻烦,于是开发这款插件用于提高调试效率。


🛠️ 使用技术栈


✨ 插件功能

  • ✅ 复制源地址cookies到目标地址
  • ✅ 清空目标地址的cookies
  • ✅ 历史记录支持且可删除
  • URL合法性校验
  • ✅ 消息提示弹窗
  • ✅ 自动读取当前页面URL

🏗️ 项目架构

参考百度TDA平台中AI助手组件的架构设计:UI层与业务逻辑抽离,状态管理与工具函数抽离,采用高内聚低耦合、单一职责原则的分层架构。

scss 复制代码
🎨 UI层 (Popup.jsx) ← 纯视图渲染
    ↓
⚛️ Hook层 (useCookieFlow.js) ← 状态管理 + 副作用处理
    ↓
🔧 Utils层 (cookie.js, storage.js, urlValidation.js) ← 纯函数工具集

📁 代码结构

csharp 复制代码
CookieFlow/
├── 📁 assets/
│   ├── 📄 icon.svg              # 插件图标
│   └── 📄 icon*.png             # 不同尺寸图标
├── 📁 src/
│   ├── 📁 components/
│   │   └── 📄 UrlInput.jsx      # URL输入框组件
│   ├── 📁 hooks/
│   │   └── 📄 useCookieFlow.js  # 自定义业务Hook
│   ├── 📁 pages/
│   │   └── 📁 popup/
│   │       ├── 📄 index.html    # 弹窗页面模板
│   │       ├── 📄 index.jsx     # React应用入口
│   │       └── 📄 Popup.jsx     # 主弹窗组件
│   ├── 📁 styles/
│   │   └── 📄 index.css         # 全局样式
│   └── 📁 utils/                # 工具函数层
│       ├── 📄 cookie.js         # Cookie操作核心逻辑
│       ├── 📄 storage.js        # Chrome存储API封装
│       ├── 📄 history.js        # 历史记录处理逻辑
│       ├── 📄 message.jsx       # 消息提示封装
│       ├── 📄 urlValidation.js  # URL校验工具
│       └── 📄 init.js           # 初始化逻辑
├── 📄 manifest.json             # Chrome扩展配置
├── 📄 package.json              # 项目依赖配置
└── 📄 vite.config.js            # Vite构建配置

🚀 快速开始

开发命令

bash 复制代码
# 安装依赖
npm install

# 开发模式
npm run dev

# 构建生产版本
npm run build

安装使用

  1. 克隆项目代码
  2. 运行 npm install 安装依赖
  3. 运行 npm run build 构建项目
  4. 在Chrome中打开扩展程序页面 (chrome://extensions/)
  5. 启用"开发者模式"
  6. 点击"加载已解压的扩展程序",选择项目的 dist 目录

🔌 Chrome API 使用

本项目主要使用以下Chrome扩展API:

  • chrome.cookies - 管理浏览器Cookie数据

    • getAll() - 获取指定URL的Cookie
    • set() - 设置Cookie
    • remove() - 删除Cookie
  • chrome.tabs - 获取浏览器标签页信息

    • query() - 查询当前活跃标签页
  • chrome.storage.local - 本地数据存储

    • get() - 读取本地存储
    • set() - 保存本地存储

🔗 开源地址

GitHub : Mebius1916/CookieFlow

本项目采用开源协议,欢迎提交 PR 参与开源贡献!🎉


📚 源码讲解

1️⃣ Cookie复制功能

功能流程: 输入验证 → 获取源Cookie → 设置目标Cookie → 保存历史记录 → 显示结果

1.1 顶层处理函数

位于 useCookieFlow.js 中的主处理函数,负责状态管理和流程控制:

javascript 复制代码
const handleCopyCookies = async () => {
  setCopyLoading(true);  // 设置加载状态
  
  try {
    const result = await copyCookies(sourceUrl, targetUrl);
    if (result) {
      // 更新历史记录UI
      await updateHistory(setSourceHistory, setTargetHistory);
    }
  } finally {
    setCopyLoading(false);  // 恢复按钮状态
  }
};
1.2 核心业务逻辑

位于 cookie.js 中的 copyCookies 函数,实现完整的复制流程:

javascript 复制代码
export const copyCookies = async (sourceUrl, targetUrl) => {
  // 1. URL预处理:提取origin部分(协议+域名+端口)
  const processedSourceUrl = extractOrigin(sourceUrl);
  const processedTargetUrl = extractOrigin(targetUrl);
  
  // 2. URL合法性验证
  const { validateFormUrls } = await import('./urlValidation');
  const validation = validateFormUrls(processedSourceUrl, processedTargetUrl);
  
  if (!validation.isValid) {
    const { message } = await import('antd');
    message.error(validation.errors[0]);
    return false;
  }
  
  try {
    // 3. 获取源地址所有Cookie
    const cookies = await getCookies(processedSourceUrl);
    
    // 4. 检查Cookie数量
    if (cookies.length === 0) {
      const { showInfo } = await import('./message');
      showInfo('源地址没有可复制的Cookie');
      return true;
    }
    
    // 5. 批量设置目标地址Cookie
    const result = await setCookies(cookies, processedTargetUrl);
    
    // 6. 保存操作记录到历史
    await saveCookieOperation(processedSourceUrl, processedTargetUrl, result.successCount);
    
    // 7. 显示操作结果
    const { showOperationResult } = await import('./message');
    showOperationResult('复制', result);
    
    return true;
  } catch (error) {
    console.error('复制Cookie失败:', error);
    const { showError } = await import('./message');
    showError('复制Cookie');
    return false;
  }
};
1.3 Cookie获取与设置

获取Cookie:使用Chrome API获取指定域名下的所有Cookie

javascript 复制代码
export const getCookies = async (url) => {
  if (!isValidUrl(url)) {
    console.error('无效的URL格式:', url);
    return [];
  }
  
  try {
    return await chrome.cookies.getAll({ url });
  } catch (error) {
    console.error('获取Cookie失败:', error);
    return [];
  }
};

设置Cookie:批量设置Cookie到目标URL,保持原有属性

javascript 复制代码
export const setCookies = async (cookies, targetUrl) => {
  if (!isValidUrl(targetUrl)) {
    return { successCount: 0 };
  }
  
  let successCount = 0;
  
  for (const cookie of cookies) {
    try {
      await chrome.cookies.set({
        url: targetUrl,
        name: cookie.name,
        value: cookie.value,
        domain: cookie.domain,
        path: cookie.path,
        secure: cookie.secure,
        httpOnly: cookie.httpOnly,
        sameSite: cookie.sameSite,
        expirationDate: cookie.expirationDate,
      });
      successCount++;
    } catch (error) {
      console.error(`设置Cookie失败: ${cookie.name}`, error);
    }
  }
  
  return { successCount };
};

2️⃣ Cookie清空功能

功能流程: URL验证 → 获取所有Cookie → 逐个删除 → 显示结果

2.1 清空处理函数
javascript 复制代码
const handleClearCookies = async () => {
  setClearLoading(true);
  
  try {
    await clearCookies(targetUrl);
  } finally {
    setClearLoading(false);
  }
};
2.2 清空实现逻辑
javascript 复制代码
export const clearCookies = async (url) => {
  if (!url) {
    const { message } = await import('antd');
    message.error('目标地址不能为空');
    return { successCount: 0 };
  }

  // 提取URL的origin部分
  const processedUrl = extractOrigin(url);

  if (!isValidUrl(processedUrl)) {
    const { message } = await import('antd');
    message.error('无效的URL格式');
    return { successCount: 0 };
  }
  
  try {
    // 获取所有Cookie
    const cookies = await chrome.cookies.getAll({ url: processedUrl });
    let successCount = 0;
    
    // 逐个删除Cookie
    for (const cookie of cookies) {
      try {
        await chrome.cookies.remove({ 
          url: processedUrl, 
          name: cookie.name 
        });
        successCount++;
      } catch (error) {
        console.error(`删除Cookie失败: ${cookie.name}`, error);
      }
    }

    // 显示操作结果
    const { showOperationResult } = await import('./message');
    showOperationResult('清除', { successCount });

    return { successCount };
  } catch (error) {
    console.error('清除Cookie失败:', error);
    const { showError } = await import('./message');
    showError('清除Cookie');
    return { successCount: 0 };
  }
};

3️⃣ 历史记录管理

3.1 数据结构设计

历史记录采用数组结构,每条记录包含:

javascript 复制代码
const record = {
  source: 'https://example.com',      // 源地址
  target: 'http://localhost:8080',   // 目标地址  
  timestamp: Date.now(),             // 操作时间戳
  count: 5                           // 成功复制的Cookie数量
};
3.2 存储机制

保存操作记录:每次复制操作后自动保存

javascript 复制代码
export const saveCookieOperation = async (source, target, successCount) => {
  try {
    const record = {
      source,
      target, 
      timestamp: Date.now(),
      count: successCount
    };
    
    // 获取现有历史记录
    const history = await getCookieHistory();
    
    // 新记录插入首位,限制最多10条
    const newHistory = [record, ...(history || [])].slice(0, 10);
    
    // 保存到Chrome本地存储
    await saveCookieHistory(newHistory);
    
    return true;
  } catch (error) {
    console.error('保存Cookie操作记录失败:', error);
    return false;
  }
};

存储API封装 (位于 storage.js):

javascript 复制代码
// 读取历史记录
export const getCookieHistory = async () => {
  try {
    const result = await chrome.storage.local.get('cookieHistory');
    return result.cookieHistory || [];
  } catch (error) {
    console.error('获取Cookie历史记录失败:', error);
    return [];
  }
};

// 保存历史记录
export const saveCookieHistory = async (history) => {
  try {
    await chrome.storage.local.set({ cookieHistory: history });
  } catch (error) {
    console.error('保存Cookie历史记录失败:', error);
    throw error;
  }
};
3.3 历史记录提取

从完整历史记录中提取URL列表,用于下拉框显示:

javascript 复制代码
// 通用历史记录获取函数
const getUrlHistory = async (field) => {
  try {
    const history = await getCookieHistory();
    if (!history || history.length === 0) return [];
    
    // 使用Set去重,slice限制最多10条
    return [...new Set(history.map(item => item[field]))].slice(0, 10);
  } catch (error) {
    console.error(`获取${field}历史记录失败:`, error);
    return [];
  }
};

// 导出特定类型的历史记录获取函数
export const getSourceUrlHistory = () => getUrlHistory('source');
export const getTargetUrlHistory = () => getUrlHistory('target');
3.4 并发更新优化

使用 Promise.all 并发请求,提升性能:

javascript 复制代码
export const updateHistory = async (setSourceHistory, setTargetHistory) => {
  try {
    // 并发获取源地址和目标地址历史记录
    const [sourceHistory, targetHistory] = await Promise.all([
      getSourceUrlHistory(),
      getTargetUrlHistory()
    ]);
    
    // 更新UI状态
    setSourceHistory(sourceHistory || []);
    setTargetHistory(targetHistory || []);
  } catch (error) {
    console.error('更新历史记录失败:', error);
  }
};

4️⃣ 历史记录删除

4.1 UI交互设计

UrlInput.jsx 组件中,鼠标悬停时显示删除按钮:

javascript 复制代码
<div className="flex items-center justify-between max-w-64 cursor-pointer py-1 px-2 hover:bg-gray-100 group">
  <span className="truncate flex-1 mr-2">{url}</span>
  <DeleteOutlined
    className="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
    onClick={(e) => handleDeleteHistory(index, e)}
  />
</div>
4.2 删除事件处理
javascript 复制代码
const handleDeleteHistory = (index, e) => {
  e.stopPropagation();  // 阻止事件冒泡,避免触发选择操作
  if (onDeleteHistory) {
    onDeleteHistory(index);
  }
};
4.3 删除业务逻辑

Hook层处理

javascript 复制代码
const handleDeleteSourceHistory = async (index) => {
  try {
    await deleteSourceUrlHistory(index);
    // 删除后重新获取最新历史记录
    const newSourceHistory = await getSourceUrlHistory();
    setSourceHistory(newSourceHistory || []);
  } catch (error) {
    console.error('删除源地址历史记录失败:', error);
    showError('删除历史记录');
  }
};

Utils层实现

javascript 复制代码
// 通用删除函数
const deleteUrlHistory = async (index, field) => {
  try {
    const history = await getCookieHistory();
    if (!history || history.length === 0) return;
    
    // 获取去重后的URL列表
    const urls = [...new Set(history.map(item => item[field]))].slice(0, 10);
    const urlToDelete = urls[index];
    
    if (urlToDelete) {
      // 过滤掉包含该URL的所有记录
      const newHistory = history.filter(item => item[field] !== urlToDelete);
      await saveCookieHistory(newHistory);
    }
  } catch (error) {
    console.error(`删除${field}历史记录失败:`, error);
    throw error;
  }
};

// 导出特定类型的删除函数
export const deleteSourceUrlHistory = (index) => 
  deleteUrlHistory(index, 'source');

export const deleteTargetUrlHistory = (index) => 
  deleteUrlHistory(index, 'target');

5️⃣ URL校验机制

5.1 基础校验函数
javascript 复制代码
export const isValidUrl = (url) => {
  try {
    new URL(url);  // 使用浏览器原生URL构造函数校验
    return true;
  } catch (error) {
    return false;
  }
};
5.2 表单验证

提供详细的错误信息用于用户提示:

javascript 复制代码
export const validateFormUrls = (sourceUrl, targetUrl) => {
  const errors = [];
  
  if (!sourceUrl || !sourceUrl.trim()) {
    errors.push('源地址不能为空');
  }
  
  if (!targetUrl || !targetUrl.trim()) {
    errors.push('目标地址不能为空');
  }
  
  if (sourceUrl && sourceUrl.trim() && !isValidUrl(sourceUrl.trim())) {
    errors.push('源地址格式不正确');
  }
  
  if (targetUrl && targetUrl.trim() && !isValidUrl(targetUrl.trim())) {
    errors.push('目标地址格式不正确');
  }
  
  return {
    isValid: errors.length === 0,
    errors
  };
};
5.3 URL处理

提取URL的origin部分,确保Cookie操作的准确性:

javascript 复制代码
export const extractOrigin = (url) => {
  try {
    const urlObj = new URL(url);
    return urlObj.origin;  // 返回 "协议://域名:端口"
  } catch (error) {
    console.error('URL格式无效:', url);
    return url;
  }
};

6️⃣ 消息提示系统

位于 message.jsx 中,封装Antd的message组件:

javascript 复制代码
// 操作结果提示
export const showOperationResult = (operation, result) => {
  if (!result) {
    message.error(`${operation}Cookie失败`);
    return;
  }
  
  if (result.successCount > 0) {
    message.success(`成功${operation} ${result.successCount} 个Cookie`);
  } else {
    message.info(`没有Cookie被${operation}`);
  }
};

// 错误提示
export const showError = (action) => {
  message.error(`${action}失败`);
};

// 信息提示  
export const showInfo = (content) => {
  message.info(content);
};
相关推荐
修己xj10 分钟前
告别手动存图!这款叫 Fatkun 的浏览器插件,简直是素材收集神器
前端
笑尘~Y36 分钟前
每日技术面试高频题精选
面试
拼尽全力前进38 分钟前
Guava Cache vs Caffeine 面试详解
面试·职场和发展·guava
袋鼠云数栈1 小时前
从前端到基础设施,ACOS 如何打通企业全链路可观测
运维·前端·人工智能·数据治理·数据智能
AskHarries1 小时前
系统提示词、开发者指令和用户输入的优先级
java·前端·数据库
Moment1 小时前
长上下文会最终杀死 Rag 吗?
前端·javascript·后端
qcx232 小时前
【系统学AI】25 论文导读 ①:两篇改变 AI 的开山之作——Attention Is All You Need & ReAct
前端·人工智能·react.js·transformer
kyriewen3 小时前
大文件上传最全指南:分片、断点续传、秒传,一篇就够了
前端·javascript·面试
我叫黑大帅3 小时前
解决聊天页内部滚轮改为页面滚动问题
javascript·后端·面试
郑洁文3 小时前
基于Python的Web命令执行漏洞自动化检测系统
前端·python·网络安全·自动化