资讯动态

不用向量数据库!用SQLite+FTS5给AI加长期记忆

发布时间:2026/9/1 10:26:14 来源:尧图企业网站定制
做 AI 应用时很多朋友一上来就奔着向量数据库去装了 Milvus、Chroma、pgvector配置一堆参数结果小项目里数据量只有几千条查询效果和直接搜关键词差不了多少反而把部署复杂度拉高了好几倍。这篇文章想分享一套更轻的思路不用向量数据库也能给 AI 加上长期记忆。核心实现基于开源组件 SQLite FTS5 全文检索配合摘要化存储和分层记忆策略成本低、易落地、可维护性也不错。1. 什么是 AI 长期记忆为什么它这么重要1.1 从“每次都是新朋友”说起用过 ChatGPT、Claude 这类大模型产品的朋友应该都有体会你不主动告诉它之前的对话内容它往往不记得你们上次聊了什么。这是大模型本身的设计特点——它的上下文窗口有限一旦对话超出长度旧内容就会被截断或遗忘。放到真实业务里这个“遗忘”问题就会被放大客服机器人无法记住用户之前反馈过的工单信息。情感陪伴类应用无法记住用户的喜好、经历和情绪变化。知识库问答系统只能做单轮查询无法根据用户历史行为给出个性化回答。教育辅导工具无法持续跟踪学生的学习进度。所谓 AI 长期记忆就是让应用能够把用户的历史交互内容以某种结构化或半结构化的方式保存下来并在后续对话中按需取用让模型“看起来像”有持续记忆能力。1.2 向量数据库在长期记忆中的角色目前主流的长期记忆实现方式是 RAGRetrieval-Augmented Generation检索增强生成。它先把文档或历史记录切块通过 Embedding 模型转成向量存入向量数据库用户提问时再把问题转成向量在向量库中做相似度检索最后把命中的上下文塞给大模型。这套流程确实有效尤其是处理非结构化文档、语义相似检索、大规模数据检索时向量数据库优势明显。但问题也很现实痛点说明部署复杂Milvus 需要依赖 etcd、MinIO、Pulsar 等组件学习成本高资源占用高对内存、CPU 有一定要求个人服务器扛不住索引构建耗时数据量少时效果体现不出来数据量大时构建又要等Embedding 成本每次写入和查询都要调用 Embedding 接口产生额外费用运维负担索引重建、分片配置、容量规划都需要专人维护如果你的业务数据量不大、场景以关键词匹配为主、团队没有专门的运维资源那么“为了长期记忆而硬上向量数据库”并不是最优解。1.3 没有向量数据库长期记忆怎么做不用向量数据库不代表不做检索。我们可以换一个思路记忆落库时使用结构化关系型数据库如 SQLite存储对话记录和用户属性。对历史内容做摘要化保存核心信息而非全部原文。检索时通过关键词匹配、全文检索、规则过滤找到与当前话题相关的记忆片段。把命中的内容拼装进 Prompt引导模型基于记忆回答。这套方案适合个人项目、中小型应用、以及以“关键事实记忆 关键词回忆”为主要场景的 AI 应用。它与向量检索并不互斥可以先用轻量方案跑通业务等数据量真的上来了再平滑升级到向量数据库。2. 核心概念拆解记忆系统应该分成哪几层2.1 记忆获取层获取记忆的入口通常是用户与 AI 的对话内容。但原始对话不能直接全部塞进数据库因为冗余信息太多用户可能反复表达同一个意思。存储成本和检索质量都会受影响。个人隐私信息需要过滤和脱敏。推荐的策略是每次对话结束后从对话内容中抽取结构化信息包括时间、参与人、话题关键词、核心意图、事实性结论等。这部分可以使用大模型辅助抽取也可以先用规则提取后期再优化。2.2 记忆存储层存储层解决“记忆放在哪里、怎么组织”的问题。轻量方案推荐使用 SQLite它本身就是开源的关系型数据库单文件存储无需独立服务。配合 FTS5 扩展可以做到轻量级全文检索。存储内容建议分两类事实型记忆用户的偏好、姓名、习惯、约束条件等。这类信息变化频率低直接用字段存储。事件型记忆某次对话发生的关键事件如“昨天说项目下周上线”。这类信息需要带时间戳和上下文摘要。2.3 记忆检索层检索层决定“对话发生时哪些记忆该被拉出来”。对于轻量方案选择有LIKE 模糊查询适合关键词直接匹配。FTS5 全文检索适合多个关键词组合检索支持排序。标签过滤为记忆打上标签按标签缩小范围。时间衰减默认优先取近期的记忆防止旧记忆淹没新信息。2.4 记忆注入层检索到记忆后还需要把它们组织成 Prompt 的一部分。常见的做法是在 System Prompt 中增加一段“记忆档案”区域把当前用户的历史背景、最近对话摘要、相关事件罗列出来让模型在回答时参照这些信息。注入时需要注意控制注入长度避免超出上下文窗口。按相关度排序只取 Top N 条记忆。明确告诉模型“这些背景信息可能不完整回答时结合当前问题判断”。3. 关键技术选型SQLite 为什么够了3.1 SQLite 的特性SQLite 是一个轻量级的关系数据库它以单个文件形式存储全部数据不需要独立的数据库服务进程。Python 标准库自带 sqlite3 模块开箱即用部署成本极低。对于 AI 应用来说SQLite 的相关特性包括支持事务数据一致性有保障。单文件模式便于备份和迁移。类 SQL 语法对开发者友好。支持 JSON 字段存储适合存放扩展属性。支持 FTS5 全文检索扩展能实现简单的关键词搜索。3.2 FTS5 全文检索能力FTS5 是 SQLite 的一个全文检索扩展它提供倒排索引支持对文本内容做快速关键词搜索。与 LIKE 相比FTS5 优势体现在查询性能更高。支持 AND、OR、NOT 等组合查询。支持结果排序和片段高亮。内置分词器可以按 Unicode61 处理中英文。需要注意FTS5 的分词器是通用型分词对中文没有专门优化。对于对话记忆这种短文本场景直接按关键词匹配通常已经够用。如果后续需要更精准的中文语义检索再引入向量方案也不迟。3.3 什么时候该升级到向量数据库这里给出几个判断信号出现其中多个时说明你可能需要引入向量数据库对话记忆量突破数十万条关键词检索结果噪声变大。用户提问经常“换一种说法”但语义与历史记录高度相关。业务场景强调语义相似度例如知识库问答、推荐系统。团队已经有能力维护 Elasticsearch、Milvus、Chroma 或 pgvector 等服务。预算允许承担 Embedding 调用和向量数据库的资源开销。轻量方案的价值在于当规模还没到那个量级时先跑起来不浪费时间在基础设施上。4. 完整实战用 SQLite 给 AI 加一套轻量长期记忆下面我们来实现一套完整的“无向量数据库”AI 长期记忆系统。项目使用 Python 编写核心依赖只有标准库 sqlite3 和 openai用于调用大模型对话接口。代码重点演示记忆存储、记忆检索和 Prompt 注入三个核心环节。4.1 创建项目结构ai-memory-demo/ ├── main.py # 入口测试脚本 ├── memory_manager.py # 记忆系统核心模块 ├── llm_client.py # 大模型接口封装 ├── requirements.txt # 依赖清单 └── memories.db # SQLite 数据库文件运行时生成创建目录mkdir ai-memory-demo cd ai-memory-demo4.2 初始化数据库表结构记忆系统需要三张表用户表、对话记录表、记忆表。users存储用户基本信息。dialogues存储每一轮对话原文、时间、会话 ID。memories存储从对话中抽取的长期记忆。-- 文件路径ai-memory-demo/schema.sql CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_name TEXT UNIQUE NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS dialogues ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, session_id TEXT NOT NULL, role TEXT NOT NULL CHECK(role IN (user, assistant)), content TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, memory_type TEXT NOT NULL DEFAULT event, content TEXT NOT NULL, keywords TEXT, importance INTEGER DEFAULT 5, expires_at TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5( content, keywords, contentmemories, content_rowidid );这里用到了 FTS5 的外部内容表contentmemories这样全文索引和原表数据保持一致比较适合记忆内容会频繁更新的场景。4.3 编写记忆管理模块# 文件路径ai-memory-demo/memory_manager.py import json import sqlite3 from datetime import datetime, timedelta class MemoryManager: def __init__(self, db_pathmemories.db): self.db_path db_path self._init_db() def _get_connection(self): conn sqlite3.connect(self.db_path) conn.row_factory sqlite3.Row conn.execute(PRAGMA foreign_keys ON) return conn def _init_db(self): with self._get_connection() as conn: conn.executescript(SCHEMA_SQL) def get_or_create_user(self, user_name: str) - int: with self._get_connection() as conn: cur conn.execute( SELECT id FROM users WHERE user_name ?, (user_name,) ) row cur.fetchone() if row: return row[id] cur conn.execute( INSERT INTO users (user_name) VALUES (?), (user_name,) ) return cur.lastrowid def save_dialogue(self, user_id: int, session_id: str, role: str, content: str): with self._get_connection() as conn: conn.execute( INSERT INTO dialogues (user_id, session_id, role, content) VALUES (?, ?, ?, ?), (user_id, session_id, role, content), ) def save_memory( self, user_id: int, content: str, keywords: str , memory_type: str event, importance: int 5, expires_at: str None, ): with self._get_connection() as conn: cur conn.execute( INSERT INTO memories (user_id, content, keywords, memory_type, importance, expires_at) VALUES (?, ?, ?, ?, ?, ?) , (user_id, content, keywords, memory_type, importance, expires_at), ) memory_id cur.lastrowid # 同步到 FTS 索引 conn.execute( INSERT INTO memories_fts (rowid, content, keywords) VALUES (?, ?, ?), (memory_id, content, keywords), ) return memory_id def search_memory(self, user_id: int, query: str, limit: int 5): 基于关键词的全文检索返回相关记忆。 query_terms query.strip().split() if not query_terms: return [] fts_query AND .join(f{term} for term in query_terms) sql SELECT m.id, m.content, m.keywords, m.importance, m.created_at FROM memories m JOIN memories_fts f ON f.rowid m.id WHERE m.user_id ? AND memories_fts MATCH ? ORDER BY m.importance DESC, m.created_at DESC LIMIT ? with self._get_connection() as conn: rows conn.execute(sql, (user_id, fts_query, limit)).fetchall() return [dict(row) for row in rows] def get_recent_memories(self, user_id: int, days: int 7, limit: int 10): 获取近期记忆用于对话恢复或冷启动。 since (datetime.now() - timedelta(daysdays)).strftime(%Y-%m-%d %H:%M:%S) sql SELECT id, content, keywords, importance, created_at FROM memories WHERE user_id ? AND created_at ? ORDER BY importance DESC, created_at DESC LIMIT ? with self._get_connection() as conn: rows conn.execute(sql, (user_id, since, limit)).fetchall() return [dict(row) for row in rows] def build_memory_prompt(self, user_id: int, query: str, max_memories: int 5) - str: 根据当前问题检索记忆拼接成 Prompt 片段。 memories self.search_memory(user_id, query, limitmax_memories) if not memories: return 暂无相关记忆。 lines [] for mem in memories: importance_tag f[重要度{mem[importance]}] if mem[importance] 7 else lines.append(f- {mem[content]} (时间: {mem[created_at]}) {importance_tag}) return \n.join(lines)这里有几个设计点值得展开说明第一save_memory同时写原表和 FTS 索引保证两边数据一致。如果直接只建虚拟表后续更新字段会比较麻烦。第二search_memory使用了 FTS5 的 MATCH 语法关键词之间用AND连接要求命中的记忆同时包含所有关键词这样查出来的结果更精准。第三build_memory_prompt负责把检索结果转换成可直接写入 Prompt 的文本方便上层调用。4.4 封装大模型客户端# 文件路径ai-memory-demo/llm_client.py import os try: from openai import OpenAI except ImportError: OpenAI None class LLMClient: def __init__(self, api_keyNone, base_urlNone, modelgpt-4o-mini): if OpenAI is None: raise RuntimeError(请先安装 openai 库pip install openai) self.client OpenAI( api_keyapi_key or os.getenv(OPENAI_API_KEY), base_urlbase_url or os.getenv(OPENAI_BASE_URL), ) self.model model def chat(self, messages): resp self.client.chat.completions.create( modelself.model, messagesmessages, temperature0.7, ) return resp.choices[0].message.content def summarize_and_extract(self, dialogue_text: str) - dict: 使用大模型从对话中抽取长期记忆。 返回结构{content: ..., keywords: ...} prompt f 你是一个记忆抽取助手。请从下面的对话中提取值得长期记住的事实信息 包括用户偏好、关键事件、个人属性等。 对话内容 {dialogue_text} 请输出 JSON 格式字段包括 - content: 一句话描述值得记忆的内容 - keywords: 用逗号分隔的关键词方便后续检索 只输出 JSON不要输出其他文字。 try: result self.chat([ {role: system, content: 你是一个严格输出 JSON 的助手。}, {role: user, content: prompt}, ]) # 简单解析实际项目建议用 json.loads 加异常处理 import json, re match re.search(r\{.*\}, result, re.S) return json.loads(match.group()) if match else {content: result, keywords: } except Exception as e: # 抽取失败时降级处理截取对话前若干字符作为记忆 return { content: dialogue_text[:100], keywords: , }注意上面的 openai 库调用方式以较新的版本为例。如果你的环境比较旧接口可能是openai.ChatCompletion.create需要按实际版本调整。4.5 编写对话主流程# 文件路径ai-memory-demo/main.py from memory_manager import MemoryManager from llm_client import LLMClient def main(): memory_manager MemoryManager(memories.db) llm_client LLMClient() # 需要在环境变量中配置 OPENAI_API_KEY user_name 张三 session_id session-001 user_id memory_manager.get_or_create_user(user_name) print( AI 长期记忆 Demo无向量数据库版) print(输入 exit 退出输入 记忆 查看已知信息其他内容正常对话。\n) while True: user_input input(你).strip() if user_input.lower() exit: break # 1. 保存用户对话 memory_manager.save_dialogue(user_id, session_id, user, user_input) # 2. 检索相关记忆 memory_prompt memory_manager.build_memory_prompt(user_id, user_input) # 3. 构造带记忆的 Prompt system_prompt f你是一位贴心的 AI 助手。以下是一些关于当前用户的长期记忆 {memory_prompt} 请结合记忆回答用户问题如果记忆与当前问题无关可以参考但不要强行使用。 messages [ {role: system, content: system_prompt}, {role: user, content: user_input}, ] # 4. 调用大模型 reply llm_client.chat(messages) print(fAI{reply}\n) # 5. 保存回复 memory_manager.save_dialogue(user_id, session_id, assistant, reply) # 6. 从对话中抽取长期记忆 dialogue_text f用户{user_input}\nAI{reply} memory_info llm_client.summarize_and_extract(dialogue_text) if memory_info.get(content): memory_manager.save_memory( user_iduser_id, contentmemory_info[content], keywordsmemory_info.get(keywords, ), memory_typeevent, importance6, ) print(已记忆, memory_info[content], \n) if user_input 记忆: recent memory_manager.get_recent_memories(user_id, days30) print(\n 短期记忆概览 ) for mem in recent: print(-, mem[content]) print(\n) if __name__ __main__: main()4.6 安装依赖并运行pip install openai如果使用了国内大模型厂商的 OpenAI 兼容接口可以通过环境变量指定export OPENAI_API_KEY你的API Key export OPENAI_BASE_URLhttps://api.openai.com/v1 python main.py运行后可以测试这样一段对话流程第一轮用户说“我叫张三我喜欢喝美式咖啡住在杭州。”第二轮用户问“你还记得我喜欢喝什么吗”AI 应该能从记忆中检索到“喜欢喝美式咖啡”并给出个性化回复。这就是一个完整的“AI 长期记忆”闭环写入 → 检索 → 注入 → 生成 → 再写入。5. 增强方案让记忆更持久、更智能5.1 引入时间衰减机制长期记忆不必永久保留所有内容。用户一个月前的碎碎念对当前对话往往没什么价值。可以给每条记忆增加expires_at字段检索时自动过滤过期内容def get_valid_memories(self, user_id: int, limit: int 10): sql SELECT id, content, keywords, importance, created_at FROM memories WHERE user_id ? AND (expires_at IS NULL OR expires_at datetime(now)) ORDER BY importance DESC, created_at DESC LIMIT ? with self._get_connection() as conn: rows conn.execute(sql, (user_id, limit)).fetchall() return [dict(row) for row in rows]这种方式适合“有时效性”的记忆比如“用户下周三要去北京出差”“项目七月上线”。过期的记忆自动退场避免干扰。5.2 记忆冲突处理用户今天说“我不喝咖啡了改喝茶”但系统里已经有一条“喜欢喝美式咖啡”的记忆。这时应该如何处理推荐策略检测同主题的新旧记忆是否冲突。如果新记忆写入时发现旧记忆关键词高度重合可以降低旧记忆的importance或者标记为superseded。在 Prompt 中同时保留两条记忆让模型结合时间顺序判断。例如当模型看到“用户曾经喜欢美式咖啡3月1日”“用户近期改喝茶4月10日”时它能自然给出更合理的回答。5.3 用标签体系代替向量语义不引入向量数据库我们可以用标签来弥补语义检索能力。做法是在记忆写入阶段让大模型生成多个维度的标签例如“用户偏好 / 饮食习惯 / 生活习惯 / 工作信息”。检索时先用标签过滤再做关键词匹配能在一定程度上模拟“按语义分类查找”的效果。def search_by_tag(self, user_id: int, tag: str, limit: int 5): sql SELECT id, content, keywords, created_at FROM memories WHERE user_id ? AND keywords LIKE ? ORDER BY importance DESC, created_at DESC LIMIT ? with self._get_connection() as conn: rows conn.execute(sql, (user_id, f%{tag}%, limit)).fetchall() return [dict(row) for row in rows]5.4 记忆压缩和归档当单条记忆过大时可以设计一个后台任务定期把多条同主题的小记忆合并成一条摘要记忆原始明细归档到另一张表。这种“分层记忆”思路在成本上很有优势日常对话读取的是摘要层只有需要查明细时才访问原始记录。6. 常见问题与排查思路6.1 FTS5 不可用现象创建虚拟表时报错no such module: fts5。原因当前 SQLite 版本编译时未启用 FTS5 扩展。排查import sqlite3 conn sqlite3.connect(:memory:) print(conn.execute(SELECT sqlite_version()).fetchone())解决升级 Python 到 3.7 以上版本官方预编译版本通常已包含 FTS5。如果用的是系统自带旧版 SQLite考虑换用 Python 3.10 环境。实在不行可以回退到LIKE查询数据量小的时候性能差异不大。6.2 中文分词不理想现象输入“我喜欢喝美式咖啡”搜索“咖啡”能命中但搜索“美式”和“咖啡”同时出现时反而不命中。原因FTS5 默认按 Unicode61 分词对中文按整句连续字符切分不会自动分词成“美式”“咖啡”。解决写入时增加关键词字段由大模型生成检索时优先在关键词字段上匹配。使用支持中文分词的扩展例如simple分词器配合预分词策略。检索时拆成单个字进行 OR 匹配适当放宽条件后再做排序。实际项目中我建议用“关键词字段 FTS 双重匹配”的写法def search_memory_v2(self, user_id: int, query: str, limit: int 5): terms query.strip().split() if not terms: return [] fts_query OR .join(f{t} for t in terms) like_conditions OR .join([keywords LIKE ? for _ in terms]) params [] for t in terms: params.append(f%{t}%) sql f SELECT id, content, keywords, importance, created_at FROM memories WHERE user_id ? AND ({like_conditions}) ORDER BY importance DESC, created_at DESC LIMIT ? with self._get_connection() as conn: rows conn.execute(sql, (user_id, *params, limit)).fetchall() return [dict(row) for row in rows]6.3 记忆写入导致请求变慢现象每次对话后都要调用大模型做摘要抽取对话响应时间明显增加。原因摘要抽取是额外的推理请求耗时和成本都在叠加。解决摘要抽取改在后台异步执行不阻塞用户对话响应。降低抽取频率例如每 3 轮对话抽取一次摘要。简单场景使用规则抽取例如提取用户发言中的人名、食物名、地点等。6.4 检索结果噪声大现象记忆库里有几十条内容检索结果和当前问题相关性不高。原因关键词匹配太宽泛或者关键词字段提取质量差。解决提高importance权重让重要记忆排在前面。在 Prompt 中注明“只有记忆与问题明显相关时才参考”。定期清理低质量记忆或者让模型对已存记忆做一次质量评分。6.5 隐私与合规提醒长期记忆的本质是存储用户个人信息。上线前务必确认是否获得用户的存储和调用授权。是否提供“一键清空记忆”的功能。数据库文件是否加密或限制访问权限。是否对敏感信息做了脱敏处理。7. 最佳实践与工程建议结合几个真实落地过的项目经验给出一份避坑清单。7.1 先定义“需要记住什么”不要什么内容都往记忆库里塞。先梳理业务场景中真正需要长期保留的信息例如用户基本信息称呼、所在地、职业。用户偏好饮食、兴趣、风格。用户目标或计划在学什么、在准备什么。关键事件购买记录、投诉记录、参与活动。定义清楚后再决定哪些用字段存储、哪些用记忆表存储。7.2 记忆存档与检索引擎分离一张表既能写入、又能检索、还要频繁更新逻辑容易混乱。建议拆成memory_store原表保存完整记忆内容。memory_index索引表存储关键词、标签、时间信息。memory_fts全文检索表负责查询加速。写入时同步更新三处读时按需检索。这样做看起来稍微麻烦但后续扩展时很省心。7.3 定期做“记忆整理”长期记忆不是越多越好。建议每周或每月执行一次整理任务合并同类记忆把“用户喜欢猫”和“用户养了一只橘猫”合并成一条。删除过期记忆超过有效期且不再有参考价值的。调整重要度根据用户最近交互频率动态调整importance。纠正冲突信息用最新记忆覆盖或标记旧记忆。整理可以在低峰期用定时任务完成不需要实时处理。7.4 记录记忆召回率和准确率如果想让系统持续优化可以给每次检索加日志def log_retrieval(self, user_id, query, memory_ids, hit_clickedFalse): with self._get_connection() as conn: conn.execute( INSERT INTO retrieval_logs (user_id, query, memory_ids, hit_clicked, created_at) VALUES (?, ?, ?, ?, datetime(now)) , (user_id, query, json.dumps(memory_ids), hit_clicked), )累积一段时间后就能分析哪些关键词召回效果好、哪些记忆长期没被命中从而优化抽取策略。7.5 该上向量数据库时不要犹豫这套轻量方案适合数据量在数十万条以下、以“事实回忆”为主要场景的应用。如果出现以下情况建议升级用户提问很少包含具体关键词而是用同义词或模糊表达。对话记录量增长很快全文检索性能不再满足要求。知识库文档需要跨段落语义关联单纯关键词不够。团队希望实现“相似问题也不断被检索出来”的能力。升级路径并不复杂继续保留现有 SQLite 存储作为档案层在检索层加入向量数据库作为召回通道两者互补。8. 总结与下一步这篇文章拆解了一套不依赖向量数据库的 AI 长期记忆实现方案用 SQLite 存储结构化记忆用 FTS5 做关键词检索用大模型抽取关键信息再把记忆注入 Prompt 完成个性化回答。整个项目只有一个 Python 文件和一个小型数据库文件部署成本和维护成本都极低。与向量数据库方案相比它的优点是轻、快、省、可控缺点是语义检索能力弱、中文分词需要额外处理。实际项目中可以根据数据规模和业务场景灵活选择。如果你想动手实践建议按下面顺序推进先跑通当前代码体验完整的“写入 → 检索 → 注入”流程。加入标签体系和时间衰减机制观察检索效果变化。增加记忆整理和异步抽取优化线上体验。如果数据量变大再考虑接入 pgvector 或 Chroma先保持现有架构不变只替换检索层。AI 长期记忆并不是一个高不可攀的技术很多时候选择合适规模的方案比盲目追逐潮流更重要。希望这篇文章能帮你在自己的项目里少走一段弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价