ai 搜索 | Cloudflare Vectorize 实现ai问答 | 设计文档

3529 字
18 分钟
ai 搜索 | Cloudflare Vectorize 实现ai问答 | 设计文档

ai 搜索 | Cloudflare Vectorize 实现ai问答 | 设计文档#

背景#

博客已有 Pagefind 全文搜索,但它是关键词匹配——用户搜「缓存穿透怎么解决」,如果文章标题和正文中没有这几个字,就搜不到。

需要的是语义搜索:用户提问,AI 基于博客内容生成回答,并附上参考文章链接。

架构#

┌─────────────────────────────────────────────┐
│ 前端 Svelte 聊天组件 │
│ POST /api/ai-chat → SSE 流式接收 │
└──────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Cloudflare Worker │
│ 1. 问题 → embedding 向量 │
│ 2. Vectorize 检索 topK 相似段落 │
│ 3. 拼接 prompt(系统指令 + 检索结果 + 问题)│
│ 4. 调用 LLM 流式生成回答 │
│ 5. SSE 返回 chunk + 参考文章 │
└──────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Cloudflare Vectorize │
│ 存储所有文章段落的向量 + metadata │
└─────────────────────────────────────────────┘

核心是 RAG(Retrieval-Augmented Generation):先检索再生成。

文档分块策略#

直接把整篇文章向量化效果差——一篇 3000 字的文章,embedding 模型只能捕捉到主题,丢失细节。

做法是按 Markdown heading 切段:

## 缓存穿透
缓存穿透是指查询一个一定不存在的数据...
### 解决方案
1. 布隆过滤器...
2. 缓存空值...

每个段落保留上下文:文章标题 + 日期 + 分类 + 标签 + 章节标题路径 + 正文。打包成一个 chunk:

文章:Redis 缓存设计
日期:2025-03-15
分类:笔记
标签:Redis, 缓存
章节:缓存问题 > 缓存穿透
缓存穿透是指查询一个一定不存在的数据...

空字段(如无分类、无标签)自动省略,通过 .filter(Boolean) 过滤空行。过滤条件:正文少于 50 字的段落丢弃(太短没有检索价值)。

实现细节#

分块逻辑在 scripts/build-vectorize-index.jssplitByHeadings() 中:

function splitByHeadings(content, articleTitle) {
const lines = content.split("\n");
const chunks = [];
let currentHeadingPath = [];
let currentContent = [];
function flush() {
const text = currentContent.join("\n").trim();
if (text.length < 50) return;
chunks.push({ heading: currentHeadingPath.join(" > ") || articleTitle, text });
}
for (const line of lines) {
const headingMatch = line.match(/^(#{1,4})\s+(.+)/);
if (headingMatch) {
flush();
currentContent = [];
const level = headingMatch[1].length;
const title = headingMatch[2].trim();
currentHeadingPath = currentHeadingPath.slice(0, level - 1);
currentHeadingPath[level - 1] = title;
} else {
currentContent.push(line);
}
}
flush();
return chunks;
}

关键点:

  • 只识别 # ~ #### 四级标题,遇到标题就切段
  • currentHeadingPath 维护层级路径,如 缓存问题 > 缓存穿透 > 解决方案
  • 每个 chunk 的 ID 由 slug::heading 哈希生成,保证同一章节始终对应同一向量 ID
  • 每个 chunk 携带 metadata(articleTitlearticlePathpublishedcategorytagsheadingexcerpt),供检索结果展示和去重使用

与 LangChain 递归分块的对比#

维度Heading 分块(本博客)LangChain RecursiveCharacterTextSplitter
切分依据语义边界:Markdown 标题层级字符边界:分隔符列表递归切分
文档结构理解理解章节层级关系纯文本处理,无结构感知
语义完整性高,每个 chunk 对应完整小节低,可能把一句话切成两半
Chunk 大小控制被动,取决于标题下内容多少主动,可精确控制 chunk_size
超长处理无,一个标题下几千字仍作为一个 chunk有,超长自动继续递归切分
元信息注入自动注入文章标题、日期、分类、标签、章节路径默认不注入,需额外处理
实现复杂度简单(正则匹配标题)中等(递归逻辑 + 分隔符管理)
适用场景技术文档、博客等结构清晰的 Markdown小说、散文、无结构文本

LangChain 递归分块的核心逻辑(伪代码):

separators = ["\n\n", "\n", ". ", " ", ""] # 从大到小
for sep in separators:
chunks = text.split(sep)
if all(len(c) <= chunk_size for c in chunks):
break
# 太长的继续用更小的分隔符递归切分

本博客方案的潜在问题:如果一个标题下的内容非常长(比如一个 # 下面有几千字),生成的 chunk 会很大,可能导致 embedding 质量下降、检索粒度太粗。

改进方向:先按 Heading 切分保留语义边界,再对超长段落做二次字符切分,同时让子 chunk 继承父标题路径。

向量化与存储#

构建脚本#

scripts/build-vectorize-index.js 负责:

  1. 读取 src/content/posts/ 下所有非 draft 文章
  2. 按 heading 切段,生成 chunk 列表
  3. 调用 embedding API 生成向量
  4. 批量写入 Cloudflare Vectorize

支持增量更新——通过 .vectorize-manifest.json 记录每篇文章的内容 hash,只处理新增/修改/删除的文章。具体用法和底层 API 见下文「向量索引上传」小节。

Embedding 来源#

两种模式,三者齐备时走第三方 API,否则用 Cloudflare Workers AI 免费模型:

模式模型维度特点
第三方 APIQwen3-Embedding-8B1024中文效果好,需要 API Key
Workers AIbge-base-en-v1.5768免费,英文为主

对话模型同样有两种模式:

模式模型特点
第三方 APIDeepSeek-V4-Flash效果好,需要 API Key
Workers AIllama-3-8b-instruct免费,英文为主

配置在 .env 中(仅敏感信息),参考 .env.example

Terminal window
# Cloudflare 凭证(必填)
CLOUDFLARE_API_TOKEN=xxx
CLOUDFLARE_ACCOUNT_ID=xxx
# 第三方 AI API Key(可选,有则走第三方 API,无则回退 Workers AI)
AI_API_KEY=sk-xxx

非敏感配置(API 地址、模型名称、向量维度等)统一在 aiSearchConfig.ts 中管理,无需在 .env 重复配置。

Cloudflare 凭证获取#

CLOUDFLARE_API_TOKEN

  1. 登录 Cloudflare Dashboard
  2. 右上角头像 → My ProfileAPI TokensCreate Token
  3. 选择 Create Custom Token,权限勾选:
    • AccountVectorizeEdit
    • AccountWorkers AIUse
  4. 创建后复制 Token(页面关闭后无法再查看)

CLOUDFLARE_ACCOUNT_ID

  1. 登录 Cloudflare Dashboard
  2. 点击任意域名 → 概览页右侧栏 API 区域 → 复制 Account ID
  3. 或直接从 URL 中复制:https://dash.cloudflare.com/<account_id>/...

AI_API_KEY(可选):

当前使用魔搭社区(ModelScope)的免费接口,注册即可获取:

  1. 注册 ModelScope
  2. 右上角头像 → API-KEY 管理创建 API Key

Worker Secret 配置#

Worker 运行时需要的 AI_API_KEY 不能写在代码中,需在 Cloudflare Dashboard 配置:

  1. 登录 Cloudflare Dashboard
  2. Workers & Pages → 选择你的 Worker → SettingsVariables and Secrets
  3. 添加 Secret 类型变量:AI_API_KEY,值为第三方 API 的 Key

向量索引上传#

构建脚本 scripts/build-vectorize-index.js 通过 Cloudflare REST API 操作 Vectorize(不是 Wrangler CLI):

Terminal window
# 首次使用前,确保 .env 已配置 CLOUDFLARE_API_TOKEN 和 CLOUDFLARE_ACCOUNT_ID
# 增量更新(只处理新增/修改/删除的文章)
node scripts/build-vectorize-index.js
# 全量重建(删除旧索引,重新创建并上传所有文章向量)
node scripts/build-vectorize-index.js --force

全量重建流程:删除旧索引 → 创建新索引(指定维度和 cosine 度量)→ 分批生成 embedding → 分批插入向量。增量更新通过 .vectorize-manifest.json 记录每篇文章的内容 hash,只处理有变化的文章。

底层 API 调用:

// 插入向量
await fetch(`${API_BASE}/vectorize/v2/indexes/${INDEX_NAME}/insert`, {
method: "POST",
headers: { Authorization: `Bearer ${API_TOKEN}` },
body: JSON.stringify({ vectors }),
});
// 删除向量
await fetch(`${API_BASE}/vectorize/v2/indexes/${INDEX_NAME}/delete-by-ids`, {
method: "POST",
headers: { Authorization: `Bearer ${API_TOKEN}` },
body: JSON.stringify({ ids: batch }),
});
// 查询(Worker 运行时通过绑定调用,不需要 API Token)
const results = await env.VECTORIZE.query(queryVector, {
topK: 10,
returnMetadata: true,
});

Worker 端问答流程#

src/worker.js 中的 handleAIChat 处理 /api/ai-chat

1. 统一配置管理#

所有 AI 相关配置集中在 src/config/aiSearchConfig.ts,前端组件、构建脚本、Worker 三方共享:

export const aiSearchConfig = {
apiUrl: "https://api-inference.modelscope.cn/v1", // API 地址
modelName: "deepseek-ai/DeepSeek-V4-Flash", // LLM 对话模型
embeddingModel: "Qwen/Qwen3-Embedding-8B", // 向量模型
vectorizeDim: 1024, // 向量维度
batchSize: 500, // 构建脚本批大小
embedBatchSize: 50, // Embedding 请求批大小
indexName: "blog-ai-search", // Vectorize 索引名
};

Worker 运行时通过 getAiConfig(env) 读取配置,非敏感项从 aiSearchConfig 取值,仅 API Key 从环境变量 env.AI_API_KEY 注入。当配置项和 API Key 都存在时走第三方 API,否则回退到 Cloudflare Workers AI 内置模型。

2. Embedding#

async function getEmbedding(env, text) {
const cfg = getAiConfig(env);
if (useThirdParty(env)) {
const res = await fetch(buildApiUrl(cfg.apiUrl, "/v1/embeddings"), {
method: "POST",
headers: {
Authorization: `Bearer ${cfg.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: cfg.embeddingModel,
input: text,
dimensions: cfg.vectorizeDim,
encoding_format: "float",
}),
});
if (!res.ok)
throw new Error(`Embedding API ${res.status}: ${await res.text()}`);
const data = await res.json();
return data.data?.[0]?.embedding;
}
const result = await env.AI.run("@cf/baai/bge-base-en-v1.5", { text });
return result.data[0];
}

模型名和维度均从 aiSearchConfig 读取,切换模型只需改配置文件。

3. 向量检索#

const queryVector = await getEmbedding(env, question);
const results = await env.VECTORIZE.query(queryVector, {
topK: 10,
returnMetadata: true,
});

过滤 score < 0.2 的低相似度结果,按 articlePath 去重后拼接上下文。每条匹配结果格式为 【文章标题 - 章节标题】\n摘要,用 --- 分隔。去重后的文章信息(标题、路径、发布日期、摘要、相似度分数)作为 refs 事件先于回答发送。

4. Prompt 拼接与 system 注入防护#

系统提示(system prompt)决定了 AI 的人格和行为准则。如果用户通过构造请求在 history 中插入 role: "system" 的消息,就可能覆盖或绕过预设人格——这称为 system 注入攻击

防护措施:在拼接 messages 之前,过滤掉 history 中所有 role === "system" 的条目,确保只有服务端硬编码的 systemPrompt 生效:

const safeHistory = history.filter((m) => m.role !== "system").slice(-6);
const messages = [
{ role: "system", content: systemPrompt },
...safeHistory,
{ role: "user", content: question },
];

当前人格设定为猫娘「喵墩」,系统提示包含完整的角色背景、语言规范、性格画像等,与博客检索规则拼接后传给 LLM。

5. 流式返回#

SSE 格式,四种事件类型:

data: {"type":"refs","articles":[...]} ← 参考文章(先发)
data: {"type":"chunk","text":"..."} ← 回答文本片段
data: {"type":"error","error":"..."} ← 错误信息
data: {"type":"done"} ← 结束

LLM 后端由 aiSearchConfig 决定:配置了 apiUrl + modelName + AI_API_KEY 时走第三方 API(OpenAI 兼容格式流),否则回退 Workers AI(@cf/meta/llama-3-8b-instruct,Cloudflare 原生流)。

前端组件#

src/components/controls/AISearch.svelte,Svelte 5 + runes。

核心交互:

  • 入口:导航栏「工具」菜单触发 toggle-ai-search 事件,或 Ctrl+K 快捷键
  • 聊天面板:全屏遮罩 + 居中卡片,移动端底部弹起
  • 流式渲染:逐字显示(打字机效果),光标闪烁动画
  • 参考文章:AI 回答下方显示可点击的原文链接
  • 多轮对话:保留最近 6 条历史消息作为上下文
  • Markdown 渲染:使用 marked 库,支持代码块、列表、引用
  • 会话管理:localStorage 持久化,支持新建、切换、删除、上限控制
  • 建议按钮:空对话时显示预设问题,点击直接发送
  • 模型名显示:标题栏展示当前使用的对话模型名称
  • 停止生成:生成中可点击停止按钮中断流式响应
  • 错误处理:SSE error 事件和请求异常均以引用块形式展示

关键状态管理:

let messages = $state<Message[]>([]);
let isLoading = $state(false);
let abortCtrl: AbortController | null = null;
let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
let sessionId = $state("");
let sessionList = $state<SessionMeta[]>([]);
let showSessionList = $state(false);

流式读取使用 ReadableStream + TextDecoder,逐行解析 SSE data。生成中可通过 AbortController 中断请求,同时取消 reader。前端处理四种 SSE 事件:refs(参考文章)、chunk(文本片段)、error(错误信息)、done(结束)。

会话管理#

数据结构设计#

interface SessionMeta {
id: string; // 会话唯一标识
title: string; // 自动提取的第一条用户消息(前20字)
updatedAt: number; // 最后更新时间戳
}

localStorage 存储策略:

  • ai-chat:sessions — 会话元数据列表(JSON 数组)
  • ai-chat:session:{id} — 单个会话的完整消息记录

新建会话#

标题栏「新建会话」按钮(add-circle-outline 图标),点击后:

  1. 保存当前会话到 localStorage
  2. 生成新的 sessionId
  3. 清空消息列表,开始新对话
function startNewSession() {
saveCurrentSession();
sessionId = generateSessionId();
messages = [];
showSessionList = false;
}

历史会话列表#

标题栏「历史」按钮(history 图标),点击展开/收起下拉面板:

{#if showSessionList}
<div class="ai-session-list">
{#each sessionList as sess}
<button
class="ai-session-item"
class:ai-session-item-active={sess.id === sessionId}
onclick={() => switchSession(sess.id)}
>
<div class="ai-session-info">
<span class="ai-session-title">{sess.title}</span>
<span class="ai-session-time">{formatTime(sess.updatedAt)}</span>
</div>
<!-- 悬停显示删除按钮 -->
<span onclick={(e) => { e.stopPropagation(); deleteSession(sess.id); }}>
<Icon icon="material-symbols:close" size="sm" />
</span>
</button>
{/each}
</div>
{/if}

会话切换#

点击历史会话项切换到该会话,自动保存当前会话后加载目标会话的消息:

function switchSession(id: string) {
if (id === sessionId) { showSessionList = false; return; }
saveCurrentSession();
sessionId = id;
messages = loadSessionMessages(id);
showSessionList = false;
scrollToBottom();
}

会话删除#

鼠标悬停会话项时出现删除按钮(close 图标),删除后若删除的是当前会话则自动新建会话:

function deleteSession(id: string) {
localStorage.removeItem(STORAGE_SESSION_PREFIX + id);
sessionList = sessionList.filter((s) => s.id !== id);
saveSessionListToStorage(sessionList);
if (id === sessionId) {
startNewSession();
}
}

会话上限与自动清理#

最多保留 20 个会话,超出时自动清理最旧的:

const MAX_SESSIONS = 20;
// 保存时检查上限
if (sessionList.length > MAX_SESSIONS) {
const removed = sessionList.splice(MAX_SESSIONS);
for (const s of removed) {
localStorage.removeItem(STORAGE_SESSION_PREFIX + s.id);
}
}

自动保存时机#

  • 每次 AI 回复完成后send()finally 块中调用 saveCurrentSession()
  • 组件卸载时onMount 返回的清理函数中保存
  • 新建/切换会话前:先保存当前会话再操作
function saveCurrentSession() {
if (!sessionId || messages.length === 0) return;
if (messages.some((m) => m.streaming)) return; // 流式中不保存
localStorage.setItem(STORAGE_SESSION_PREFIX + sessionId, JSON.stringify(messages));
// 更新 sessionList 元数据...
}

初始化恢复#

组件挂载时自动恢复最近会话:

onMount(() => {
sessionList = loadSessionListFromStorage();
if (sessionList.length > 0) {
const latest = sessionList[0];
sessionId = latest.id;
messages = loadSessionMessages(latest.id);
} else {
sessionId = generateSessionId();
}
// ...
return () => {
saveCurrentSession(); // 卸载时保存
};
});

敏感配置处理#

非敏感配置集中在 src/config/aiSearchConfig.ts,三方共享。敏感信息(API Key)的配置方式见上文「Worker Secret 配置」小节。

资源消耗#

资源免费额度单次问答消耗日均可用
Workers AI10k tokens/天~500-1000 tokens10-20 次
Vectorize30M 查询/月1 次查询~1M 次/天
Worker 请求100k/天1 次请求100k 次/天

个人博客完全够用。

文件清单#

文件作用
scripts/build-vectorize-index.js向量索引构建脚本
src/worker.jshandleAIChatWorker 端 RAG 问答 API
src/config/aiSearchConfig.tsAI 搜索统一配置中心
src/components/controls/AISearch.svelte前端聊天 UI
.env.example环境变量模板(含获取说明)
wrangler.tomlVectorize + AI 绑定配置
.vectorize-manifest.json增量更新 manifest(gitignored)

踩坑记录#

1. 向量维度不一致

构建脚本用第三方 API 生成 1024 维向量,但 Vectorize 索引创建时指定了 768 维(Workers AI 的默认值)——写入直接报错。必须保证 VECTORIZE_DIM 和索引创建时的 --dimensions 一致。

2. Embedding URL 拼接

第三方 API 的 base URL 可能带 /v1/chat/completions 后缀,需要统一清理再拼 /v1/embeddings

function buildApiUrl(base, suffix) {
return base
.replace(/\/+$/, "")
.replace(/\/v1\/?$/, "")
.replace(/\/chat\/completions\/?$/, "") + suffix;
}

3. Workers AI 流式响应格式

Workers AI 的流式响应和 OpenAI SSE 格式不同,不能用同一套解析逻辑。需要分别处理,且 Workers AI 可能回退到非流式模式(需要额外处理 response 字段)。

4. 相似度阈值

Vectorize 返回的 cosine score 分布因 embedding 模型而异。实测 Qwen3-Embedding 的 score 整体偏低,0.2 以下基本是无关内容。阈值需要根据实际效果调整。

ai 搜索 | Cloudflare Vectorize 实现ai问答 | 设计文档
https://tblog.mmzhiku.xyz/posts/ai/ai-blog-ai-search-vectorize/
作者
MmzMing
发布于
2026-05-14
许可协议
CC BY-NC-SA 4.0

评论区

看板娘
公告
友链 互换友链

正在招募技术类博客友链,要求原创、稳定更新。点击了解更多。

查看详情
维护 服务器升级

本周日凌晨 2:00-4:00 进行服务器维护,期间站点可能短暂无法访问。

欢迎 关于我的介绍

欢迎来到我的博客,我是深耕java、python和react技术开发。热爱技术、持续学习,欢迎同好交流探讨,也欢迎大佬互换友链。

查看详情
音乐
封面

音乐

暂未播放

0:00
0:00
暂无歌词
标签
# AI 6 # 认证 5 # 安全 4 # 登录 3 # Skill 2 # Redis 2 # Bitmap 2 # 部署 2 # Java 2 # 并发编程 2 # 性能优化 2 # 前端 1 # 博客 1 # Prompt 1 # 工作流 1 # RAG 1 # Cloudflare 1 # 缓存设计 1 # 高性能 1 # Bot 1 # Umami 1 # Vercel 1 # 线程池 1 # 虚拟线程 1 # 分布式 1 # JWT 1 # OAuth2 1 # MinIO 1 # 文件存储 1 # 扫码登录 1 # WebSocket 1 # Agent 1 # Oracle 1 # 数据库 1
目录

隐私政策

更新日期: 2026/5/19
生效日期: 2026/5/19

导言#

MmzMing的知识库 是一款由 MmzMing(以下简称“我们”)提供的产品。您在使用我们的服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明,在使用我们的服务时,我们如何收集、使用、储存和分享这些信息,以及我们为您提供的访问、更新、控制和保护这些信息的方式。

本《隐私政策》与您所使用的 MmzMing的知识库 服务息息相关,希望您仔细阅读,在需要时,按照本《隐私政策》的指引,作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇,我们尽量以简明扼要的表述,并提供进一步说明的链接,以便您的理解。

您使用或继续使用我们的服务,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。

如对本《隐私政策》或相关事宜有任何问题,请通过 784774835@qq.com 与我们联系。

1. 我们收集的信息#

我们或我们的第三方合作伙伴提供服务时,可能会收集、储存和使用下列与您有关的信息。如果您不提供相关信息,可能无法注册成为我们的用户或无法享受我们提供的某些服务,或者无法达到相关服务拟达到的效果。

  • 个人信息:您在注册账户或使用我们的服务时,向我们提供的相关个人信息,例如电话号码、电子邮件等。
  • 日志信息:指您使用我们的服务时,系统可能通过 cookies、标识符及相关技术收集的信息,包括您的 设备信息浏览信息点击信息,并将该等信息储存为日志信息,为您提供个性化的用户体验、保障服务安全。您可以通过浏览器设置拒绝或管理 cookie、标识符或相关技术的使用。
  • 位置信息:指您开启设备定位功能并使用我们基于位置提供的相关服务时,收集的有关您位置的信息,包括:
    • 您通过具有定位功能的移动设备使用我们的服务时,通过 GPS 或 WiFi 等方式收集的您的地理位置信息;
    • 您可以通过关闭定位功能,停止对您的地理位置信息的收集。

2. 信息的存储#

2.1 信息存储的方式和期限#

我们会通过安全的方式存储您的信息,包括本地存储(例如利用 APP 进行数据缓存)、数据库和服务器日志。

一般情况下,我们只会在为实现服务目的所必需的时间内或法律法规规定的条件下存储您的个人信息。

2.2 信息存储的地域#

我们会按照法律法规规定,将境内收集的用户个人信息存储于中国境内。

目前我们不会跨境传输或存储您的个人信息。将来如需跨境传输或存储的,我们会向您告知信息出境的目的、接收方、安全保证措施和安全风险,并征得您的同意。

2.3 产品或服务停止运营时的通知#

当我们的产品或服务发生停止运营的情况时,我们将以推送通知、公告等形式通知您,并在合理期限内删除您的个人信息或进行匿名化处理,法律法规另有规定的除外。

3. 信息安全#

我们使用各种安全技术和程序,以防信息的丢失、不当使用、未经授权阅览或披露。例如,在某些服务中,我们将利用加密技术(例如 SSL)来保护您提供的个人信息。但请您理解,由于技术的限制以及可能存在的各种恶意手段,在互联网行业,即便竭尽所能加强安全措施,也不可能始终保证信息百分之百的安全。您需要了解,您接入我们的服务所用的系统和通讯网络,有可能因我们可控范围外的因素而出现问题。

4. 我们如何使用信息#

我们可能将在向您提供服务的过程之中所收集的信息用作下列用途:

  • 向您提供服务;
  • 在我们提供服务时,用于身份验证、客户服务、安全防范、诈骗监测、存档和备份用途,确保我们向您提供的产品和服务的安全性;
  • 帮助我们设计新服务,改善我们现有服务;
  • 使我们更加了解您如何接入和使用我们的服务,从而针对性地回应您的个性化需求,例如语言设定、位置设定、个性化的帮助服务和指示,或对您和其他用户作出其他方面的回应;
  • 向您提供与您更加相关的广告以替代普遍投放的广告;
  • 评估我们服务中的广告和其他促销及推广活动的效果,并加以改善;
  • 软件认证或管理软件升级;
  • 让您参与有关我们产品和服务的调查。

5. 信息共享#

目前,我们不会主动共享或转让您的个人信息至第三方,如存在其他共享或转让您的个人信息或您需要我们将您的个人信息共享或转让至第三方情形时,我们会直接或确认第三方征得您对上述行为的明示同意。

为了投放广告,评估、优化广告投放效果等目的,我们需要向广告主及其代理商等第三方合作伙伴共享您的部分数据,要求其严格遵守我们关于数据隐私保护的措施与要求,包括但不限于根据数据保护协议、承诺书及相关数据处理政策进行处理,避免识别出个人身份,保障隐私安全。

我们不会向合作伙伴分享可用于识别您个人身份的信息(例如您的姓名或电子邮件地址),除非您明确授权。

我们不会对外公开披露所收集的个人信息,如必须公开披露时,我们会向您告知此次公开披露的目的、披露信息的类型及可能涉及的敏感信息,并征得您的明示同意。

随着我们业务的持续发展,我们有可能进行合并、收购、资产转让等交易,我们将告知您相关情形,按照法律法规及不低于本《隐私政策》所要求的标准继续保护或要求新的控制者继续保护您的个人信息。

另外,根据相关法律法规及国家标准,以下情形中,我们可能会共享、转让、公开披露个人信息无需事先征得您的授权同意:

  • 与国家安全、国防安全直接相关的;
  • 与公共安全、公共卫生、重大公共利益直接相关的;
  • 犯罪侦查、起诉、审判和判决执行等直接相关的;
  • 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
  • 个人信息主体自行向社会公众公开个人信息的;
  • 从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。

6. 您的权利#

在您使用我们的服务期间,我们可能会视产品具体情况为您提供相应的操作设置,以便您可以查询、删除、更正或撤回您的相关个人信息,您可参考相应的具体指引进行操作。此外,我们还设置了投诉举报渠道,您的意见将会得到及时的处理。如果您无法通过上述途径和方式行使您的个人信息主体权利,您可以通过本《隐私政策》中提供的联系方式提出您的请求,我们会按照法律法规的规定予以反馈。

当您决定不再使用我们的产品或服务时,可以申请注销账户。注销账户后,除法律法规另有规定外,我们将删除或匿名化处理您的个人信息。

7. 变更#

我们可能适时修订本《隐私政策》的条款。当变更发生时,我们会在版本更新时向您提示新的《隐私政策》,并向您说明生效日期。请您仔细阅读变更后的《隐私政策》内容,若您继续使用我们的服务,即表示您同意我们按照更新后的《隐私政策》处理您的个人信息。

8. 未成年人保护#

我们鼓励父母或监护人指导未满十八岁的未成年人使用我们的服务。我们建议未成年人鼓励他们的父母或监护人阅读本《隐私政策》,并建议未成年人在提交的个人信息之前寻求父母或监护人的同意和指导。