GEO优化软件从零开发指南:数据模型与API设计详解
这套GEO软件的核心流程实际上并不复杂,完全可以拆解为四个关键步骤:首先是批量拓词,建立选题池;然后将企业介绍、服务说明、FAQ、案例等资料存入知识库;接着基于关键词和知识库生成文章;最后就是发布与分发。听起来像是一个AI写作工具,但真正落到系统开发时你会发现,重点反而不是AI本身,而是数据如何沉淀、如何复用。

因此,本篇专门聚焦数据模型与API。GEO系统的数据模型,核心思路是围绕「内容资产」来设计,而不是围绕「AI调用」来设计。真正需要长期保存和反复使用的,是关键词、知识库、文章、发布记录、账号、投放资源、配额和任务这些实体。按这个方向拆解模块,系统做出来会更像一个内容运营平台,而不是一个单点的写作小工具。
1. 应用作用域
在 frontend/src/api/backend.js 中有一个非常关键的设计:GEO相关的接口会自动挂载到 /apps/geo_app 路径下。被纳入这个作用域的资源包括以下这些:
/keyword-groups/keywords/articles/knowledge-bases/conversion-targets/article-styles/platforms/distribute/social-accounts/site-publish/auto-operations/soft-articles/ai-quotas/ai-generation-tasks
这样设计的优势在于,后端可以用同一套路由和权限框架同时服务多个产品。只需要通过 X-App-Code 或URL前缀来区分业务域即可。这里我们使用 geo_app 作为应用标识,未来如果扩展其他垂直产品,也不需要推倒整套后端重新开发。
前端的请求拦截器可以这样编写,此后所有业务页面都无需重复处理token、客户端模式和应用标识等繁琐细节:
backendApi.interceptors.request.use(config => {
const token = storage.get('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
} config.headers['X-App-Code'] = APP_CODE
config.headers['X-Client-Mode'] =
(window.require || window.electron) ? 'electron' : 'browser_web'
config.url = withAppPrefix(config.url) return config
})
2. 关键词模型
应用作用域搭建完成后,接下来看第一类核心资产:关键词。关键词是整个GEO内容生产的入口,其重要性不言而喻。建议至少设计两个表来搞定它。
首先是 keyword_groups 这个分组表:
iduser_idapp_codenamedescriptionenabledsortcreated_atupdated_at
然后是 keywords 这个关键词明细表:
idgroup_iduser_idapp_codewordsourcecreated_atupdated_at
在关键词页面上,这些操作已经集成为日常运营的工具箱:分组创建、关键词列表展示、AI拓词、批量添加、批量删除、移动分组等。API层对应的就是 getKeywordGroups、createKeywordGroup、getKeywords、generateKeywords、batchCreateKeywords、moveKeywords 这些方法。
后端建表时,先保持这种粒度即可,不需要一开始就把关键词表设计成复杂的SEO大表:
CREATE TABLE keyword_groups (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
app_code VARCHAR(64) NOT NULL,
name VARCHAR(120) NOT NULL,
description TEXT NULL,
enabled TINYINT DEFAULT 1,
sort INT DEFAULT 0,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);CREATE TABLE keywords (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
group_id BIGINT NOT NULL,
app_code VARCHAR(64) NOT NULL,
word VARCHAR(180) NOT NULL,
source VARCHAR(40) DEFAULT 'manual',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_group_id (group_id),
INDEX idx_user_app (user_id, app_code)
);
3. 知识库模型
光有关键词还不够。关键词告诉系统要写什么方向,而知识库要解决的是另一个问题:内容真实可信。知识库的作用,就是保证生成的文章不是空泛的套话,而是能围绕真实的产品、服务和案例展开。建议如下设计:
knowledge_bases 知识库主表:
iduser_idapp_codenamedescriptionenabledcreated_atupdated_at
knowledge_files 知识文件表:
idknowledge_base_idnamefile_typefile_pathfile_sizecontentmetadatacreated_atupdated_at
项目里关于知识库的API已经比较完备了,包括上传文件、添加文本、获取文件内容、更新内容、移动文件、删除文件等能力。开发时要注意一个细节:文本和图片的处理方式完全不同。文本可以直接组装进提示词,但图片更适合作为素材引用,让模型在文章里输出一个占位符即可。
4. 转化目标和文章风格
转化目标回答的是「文章最终要为谁服务」这个问题。而文章风格回答的是「内容具体该怎么写」这个问题。这两个概念千万不要混在一个prompt字段里,它们应当各司其职。
conversion_targets 转化目标表:
namecompany_nameofficial_websiteindustrydescription
article_styles 文章风格表:
nametypepromptenabled
在 article-creation.vue 中,已经把正文风格和标题风格分开使用,还支持标题改写。这个设计很有必要,因为标题负责吸引点击和匹配搜索,正文负责承载可信度和信息密度,两者目标不同,不能混为一谈。
5. 文章模型
关键词、知识库、风格、转化目标,最终都会汇聚到文章这个载体上。文章是整个系统的中心资产,建议字段设计如下:
iduser_idapp_codetitlecontentstatussourceword_countkeyword_idsknowledge_base_idsstyle_idtitle_style_idconversion_target_iderror_messagecreated_atupdated_at
文章的状态,建议至少包含这几个:
createdmanualaigeneratingfailed
目前文章列表已经支持状态筛选、失败重试、发布、投放、导出Word、查看发布记录等功能。后端在设计时,要把文章生成过程做成任务化的,避免前端页面长时间等待大模型响应,那样用户体验会很差。
6. AI 配额和任务
数据资产之外,还需要单独考虑AI任务和额度管理。项目里的 keywords.vue 和 article-creation.vue 都会展示AI额度信息。关键词拓词调用后会轮询 getAiGenerationTask,这说明生成任务必须是异步的。
建议这样设计:
ai_generation_tasks 任务表:
iduser_idapp_codetask_typeusage_typestatusrequest_payloadresult_payloaderror_messagestarted_atfinished_at
ai_quota_usages 配额使用记录表:
iduser_idtask_idusage_typeamountperiod_keystatus
这里状态要区分 reserved、used、released。当任务失败或取消时,要释放额度;任务完成时,再确认扣减。这个设计能有效避免高并发下额度被重复消耗的问题。
任务和额度建议采用"预占 -> 确认 -> 释放"的状态机来管理,伪代码逻辑如下:
DB::transaction(function () use ($user, $payload) {
$quota = AiQuota::lockForUpdate()
->where('user_id', $user->id)
->where('usage_type', 'article_generation')
->firstOrFail(); if ($quota->remaining <= 0) {
throw new RuntimeException('AI额度不足');
} $task = AiGenerationTask::create([
'user_id' => $user->id,
'app_code' => 'geo_app',
'task_type' => 'article',
'usage_type' => 'article_generation',
'status' => 'pending',
'request_payload' => $payload,
]); AiQuotaUsage::create([
'user_id' => $user->id,
'task_id' => $task->id,
'usage_type' => 'article_generation',
'amount' => 1,
'status' => 'reserved',
]);
});
7. 发布和投放模型
发布方面要分两类来看:账号发布和站点发布。
账号发布依赖的是 /social-accounts 和 /distribute/records 这两组接口。站点发布则依赖 /site-publish 和 /site-publish/publish。自动运营服务会在文章生成后,自动创建分发记录,再调用RPA或站点发布接口完成最终的推送。
8. API 响应规范
最后,是API响应格式的问题。项目里的axios拦截器,默认假设Laravel的统一响应格式是这样的:
{
"code": 0,
"message": "success",
"data": {}
}
如果code返回非0,前端会直接当成错误处理。如果返回401,前端就会清理token并跳转到登录页。后端在实现时,必须保持这个格式稳定不变,否则前端页面出问题的时候,排查起来会非常棘手。数据和接口的基础打好之后,下一步才适合深入AI生成链路——否则,生成出来的内容连系统都承接不住,那就更谈不上优化了。
