创作中心这个模块放在整个 AI 博客系统里单独拎出来讲,是因为它不只是几个 CRUD 页面,而是真正把 SpringBoot 后端、Vue3 前端和 AI 生成能力串起来的产品入口。用户在创作中心里完成写作、保存草稿、调用 AI 做标题润色、摘要提取、标签推荐,最后点发布进入文章库。从编码层面看,这部分要处理的不仅有文章表和接口,还有内容安全校验、异步 AI 调用、编辑器状态同步、统计面板,任何一个环节没对齐,创作流程就会断。
本文基于 SpringBoot + Vue 前后端分离架构,把“创作中心”的设计思路和开发骨架完整过一遍。代码默认按普通 Spring Boot Web 项目和 Vue3 + Vite 项目组织,具体包名、组件地址以实际项目为准。看完你会得到一套可以照抄的完工流程:从建表、写接口、做 AI 服务隔离,再到前端搭建编辑器页面,最后按验证清单把功能跑通。
1. 创作中心核心能力速览
| 能力项 | 说明 |
|---|---|
| 后端技术栈 | SpringBoot,标准 Web + 参数校验 + 全局异常处理 |
| 前端技术栈 | Vue3 + Vite,可选 Element Plus 或 Naive UI 做后台样式 |
| 前后端通信 | REST API + JSON,前端通过 axios 封装请求 |
| 内容编辑器 | Markdown 编辑器(如 md-editor-v3)或富文本编辑器按需选择 |
| 核心能力 | 草稿管理、文章发布、AI 写摘要、AI 优化标题、AI 提取标签 |
| AI 接入方式 | 后端统一封装 AI 服务接口,不把供应商 SDK 散落在业务代码中 |
| 内容安全校验 | 发布前执行应用层敏感词过滤或调用内容安全服务 |
| 数据库 | MySQL,文章内容使用长文本字段存储 |
| 版权边界 | AI 生成内容需保留服务端调用记录,创作者必须对最终发布内容负责 |
从这张表可以看到,创作中心的定位是“生产内容的后台出口”。与普通博客系统相比,多出来的不是花哨页面,而是 AI 能力如何与原有编辑、保存、发布链路平稳融合。真正影响开发节奏的是三个点:编辑器选型、AI 服务抽象、状态流转设计。
2. 创作中心功能拆解与使用边界
2.1 功能模块拆分
| 模块 | 对应功能 | 开发优先级 |
|---|---|---|
| 编辑页 | 标题输入、正文编辑、封面选择、摘要编辑 | 高 |
| 草稿箱逻辑 | 自动保存草稿、草稿列表、恢复最近编辑 | 高 |
| AI 辅助区 | 一键生成摘要、优化标题、提取标签、续写段落 | 中 |
| 发布流程 | 内容校验、状态变更、发布后跳转文章详情 | 高 |
| 统计面板 | 今日发布数、草稿总数、累计字数、AI 调用次数 | 低 |
模块拆分看起来简单,但实际操作时要特别注意“AI 辅助区”的任务边界。建议把 AI 辅助定位成“帮助作者产出初稿和可选文案”,而不是“绕过人工审核直接输出内容”。这个定位决定后端调用逻辑和内容安全校验必须存在。
2.2 使用边界和合规提醒
创作中心的用户是通过自己账号登录的创作者,所以系统需要具备基本的权限控制。涉及 AI 的能力,不应默认用户会自行判断内容是否合规,服务端在任何发布、保存、导出操作前都要做同一道检查。任何 AI 生成文本都应标记来源,比如在数据库字段中记录 ai_count 或 ai_used,方便后续审计。
如果后续把创作中心部署到公网,还要注意这些界面不能暴露敏感参数。AI 模型的 API Key 只能存在于后端配置环境变量中,不能出现在 Vue 工程里。上传素材时,需要限定文件类型和大小,避免恶意文件进入静态资源目录。
3. 数据库设计与状态流转
3.1 文章主表设计
创作中心最核心的是 article 表,它既要支持草稿状态,也要支撑已发布文章展示。建议在一张表中通过 status 字段区分草稿、发布、下架状态,而不是单独建一张草稿表,否则保存和发布的同步逻辑会很麻烦。
下面给出通用建表 SQL,你可以按项目需要增删字段:
CREATE TABLE article ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', user_id BIGINT NOT NULL COMMENT '作者ID', category_id BIGINT DEFAULT NULL COMMENT '分类ID', title VARCHAR(200) NOT NULL COMMENT '标题', summary VARCHAR(500) DEFAULT NULL COMMENT '摘要', cover_url VARCHAR(500) DEFAULT NULL COMMENT '封面图地址', content LONGTEXT COMMENT '富文本或HTML内容', markdown_content LONGTEXT COMMENT 'Markdown原始内容', status TINYINT NOT NULL DEFAULT 0 COMMENT '0草稿 1已发布 2下架', view_count INT NOT NULL DEFAULT 0 COMMENT '浏览量', ai_used TINYINT NOT NULL DEFAULT 0 COMMENT '是否使用AI辅助 0否 1是', create_time DATETIME NOT NULL COMMENT '创建时间', update_time DATETIME NOT NULL COMMENT '更新时间', KEY idx_user_status (user_id, status), KEY idx_create_time (create_time) ) COMMENT '文章表';创作中心通常会同时提供 Markdown 编辑器和纯文本内容预览,所以我同时保留 content 和 markdown_content 两个字段。实际展示页面读取哪个字段,取决于文章详情接口的实现。窄表结构更利于按用户、按状态分页查询,后续要增加定时发布功能时,扩展一个 publish_time 字段即可。
3.2 状态流转设计
文章状态的流转是创作中心最容易写乱的地方,尤其是草稿和发布混合在一个接口里时。我的做法是强制规定状态只允许按以下顺序变化:编辑中的草稿可以多次保存,但只有用户显式点击“发布”按钮才算提交发布;已经发布的文章再次编辑时可以重新变成草稿,也可以直接更新并保持发布状态。
| 当前状态 | 操作 | 下一状态 | 接口行为 |
|---|---|---|---|
| 草稿 | 保存草稿 | 草稿 | 更新记录,不对外展示 |
| 草稿 | 发布 | 已发布 | 更新状态,可被前台查询 |
| 已发布 | 编辑保存 | 已发布 | 更新内容,保留浏览量等统计 |
| 已发布 | 下架 | 下架 | 前台不再返回 |
需要注意的是,保存草稿和发布的校验力度不应该一样。保存草稿时只需要保证标题和正文可以写进 MySQL,发布时必须经过 AI 内容安全校验和必要字段完整性检查。否则用户在编辑器里只输入半句话,也会因为误点发布产生一条前台可见的空文章。
4. SpringBoot 后端接口实现
4.1 统一返回结构
创作中心接口众多,如果每个接口都返回不同的 Map 结构,前端联调会非常痛苦。建议先统一后端返回格式:
public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "success"; r.data = data; return r; } public static <T> Result<T> fail(Integer code, String message) { Result<T> r = new Result<>(); r.code = code; r.message = message; return r; } }统一返回结构以后,前端 axios 拦截器可以根据 result.code 统一弹出错误提示,不用每个页面重复处理超时和业务异常。业务异常建议抛出自定义 BizException,全局异常处理器会把它转换为 Result.fail。
4.2 保存草稿与发布文章接口
创作中心的接口可以设计成独立的 WriterController,不与前台博客展示接口混在一起。下面这段代码展示最核心的保存草稿接口,Controller 只负责接收参数、识别当前登录用户、调用 Service:
@RestController @RequestMapping("/api/writer") public class WriterController { @Resource private ArticleService articleService; @PostMapping("/draft") public Result<Long> saveDraft(@RequestBody @Valid ArticleDraftDTO dto) { Long userId = UserContext.getUserId(); return Result.ok(articleService.saveDraft(userId, dto)); } @PostMapping("/publish") public Result<Long> publish(@RequestBody @Valid ArticlePublishDTO dto) { Long userId = UserContext.getUserId(); Article article = articleService.buildArticle(userId, dto); SafeFilter.check(article); return Result.ok(articleService.doPublish(article)); } }从接口设计上能看出两个区别:draftDTO 里的字段校验相对宽松,publishDTO 会强制要求标题非空、正文非空、分类存在。在 publish 流程里,SafeFilter.check 是被特别调用的,它会在文章进入数据库前执行内容安全校验。
4.3 内容安全过滤实现
AI 博客系统不能只关心好不好用,还要保证发布内容和 AI 辅助生成内容符合平台要求。用最简单的实现,SafeFilter 可以接第三方内容安全服务,也可以先做本地关键词检查。下面是一个通用模板:
@Component public class SafeFilter { public void check(Article article) { String checkText = article.getTitle() + "\n" + article.getSummary() + "\n" + article.getContent(); if (checkText == null || checkText.length() > 100000) { throw new BizException("内容长度异常,请检查后重新提交"); } if (SensitiveWordChecker.contains(checkText)) { throw new BizException("内容包含不恰当信息,请修改后发布"); } } }SensitiveWordChecker 的具体实现可以是 DFA 敏感词库,也可以是对接云厂商文本审核服务的封装。实际项目中建议两步都做:本机 DFA 做快速拦截,云服务做深度语义审核。这样既不会因为外网服务抖动导致整个发布接口不可用,也能覆盖更复杂的新词变体。
4.4 分页查询草稿和已发布列表
创作中心另一个高频接口是文章列表查询。草稿区只需要拿当前登录用户自己的数据,已发布区域要考虑按分类、按状态筛选。
@Service public class ArticleServiceImpl implements ArticleService { @Resource private ArticleMapper articleMapper; @Override public PageResult<ArticleVO> pageDrafts(Long userId, PageQuery query) { IPage<Article> page = new Page<>(query.getPageNum(), query.getPageSize()); LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Article::getUserId, userId) .eq(Article::getStatus, ArticleStatus.DRAFT) .orderByDesc(Article::getUpdateTime); IPage<Article> result = articleMapper.selectPage(page, wrapper); return PageResult.convert(result, this::toVO); } }这种写法适合使用 MyBatis Plus 的项目。若项目直接使用 MyBatis XML,只要把分页 SQL 写成对 user_id、status 两个字段的等值查询也会得到同样效果。真正的重点是索引设计:user_id + status 联合索引是这张表最常用的查询路径。
4.5 前后端分离跨域与鉴权
前后端分离开发时,后端接口大概率在 8080,前端在 5173 或 80,跨域问题无法回避。SpringBoot 侧可以写一个 WebMvcConfigurer 配置允许的跨域来源,前后端联调完毕后按部署域名收紧。登录态推荐使用 JWT,前端每次请求在 Authorization 头携带 token,后端拦截器统一解析并写入 UserContext。
@Component public class UserContext { private static final ThreadLocal<Long> USER_ID_HOLDER = new ThreadLocal<>(); public static void setUserId(Long userId) { USER_ID_HOLDER.set(userId); } public static Long getUserId() { return USER_ID_HOLDER.get(); } public static void clear() { USER_ID_HOLDER.remove(); } }使用 ThreadLocal 存当前用户时一定要注意在请求结束后 clear,否则 Tomcat 线程复用会导致后续请求拿到上一个用户的数据。这个问题在登录功能和创作中心都容易发生,建议在拦截器的 afterCompletion 方法里统一清理。
5. AI 辅助写作能力接入与服务隔离
5.1 为什么要把 AI 服务单独抽象出来
接入 AI 写作能力时最常见的错误是直接在 ArticleService 里调用某一个供应商的 SDK。一旦要更换模型供应商、切换 prompt 模板或者调整超时时间,就得去翻业务代码。正确做法是定义 AiWriterService 接口,把“根据标题、正文生成摘要”“根据正文提取标签”等能力变成一组方法,然后在 impl 中封装具体供应商调用。
public interface AiWriterService { String generateSummary(String title, String content); List<String> extractTags(String content); String polishTitle(String originalTitle, String content); }在 Spring 项目中,接口注入可以方便替换实现类。开发阶段可以先写一个 LocalMockAiWriterService 返回固定文本,确保创作中心整体流程不会因为 AI 服务不稳定而中断;联调阶段再切换到真正的大模型调用实现类。这样前端开发不会被后端 AI 供应商参数阻塞。
5.2 供应商参数与提示词模板管理
真实调用 AI 时,需要维护模型名称、token 上限、温度、超时时间等参数。我不建议把这些参数散在 Java 常量类里,更稳妥的方式是放到 application.yml:
ai: provider: openai-compatible api-key: ${AI_API_KEY} base-url: ${AI_BASE_URL} model: gpt-4o-mini temperature: 0.7 timeout: 30s通过环境变量注入 API Key,能避免把敏感信息提交到 Git 仓库。提示词模板同样不要写在代码字符串拼接里,可以在 resources 目录下放 prompt 模板文件,服务启动时加载,后续编辑提示词不需要重新编译 Java 代码。
一段生成摘要的提示词可以设计为:
你是博客写作助手。请根据以下文章标题和正文生成一句中文摘要,长度控制在50字以内。 要求:不编造正文不存在的观点,不重复标题关键词。 标题:{title} 正文:{content}在实际调用前,服务层会把 {title}、{content} 占位符替换为当前文章内容,再拼上系统提示词发送给模型。这样做的价值是 prompt 与代码逻辑分离,当模型供应商更新后,只需要调整模板文本或模型名称,不需要改动 Controller 层。
5.3 超时与结果格式问题
AI 辅助写作接口不适合前端长时间阻塞等待。生成摘要的耗时通常在 1 到 10 秒之间,如果文章很长,甚至可能超过 30 秒。因此,我建议对 AI 调用设置单独超时时间,不要复用普通 HTTP 请求的默认连接超时。前端调用后以 loading 状态提示用户等待即可。
如果要求更高,可以考虑把 AI 任务异步化:前端提交后立即返回任务 ID,后端通过线程池或消息队列处理,完成后前端轮询结果。这个方案在批量生成标签、批量润色旧文章时尤其有用。内容生成结果必须做长度校验,避免模型返回带 Markdown 片段或英文重复文本污染正文。
6. Vue3 前端创作中心页面搭建
6.1 路由和页面布局
Vue3 项目中,创作中心建议做成独立视图,而不是嵌入在博客首页的弹窗中。路由可以设置成 /writer 目录下的子路由,包括编辑器页、草稿箱列表、统计页。下面是编辑器路由的通用写法:
import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/writer', component: () => import('@/layout/WriterLayout.vue'), meta: { requiresAuth: true }, children: [ { path: 'editor', name: 'ArticleEditor', component: () => import('@/views/writer/EditorView.vue') }, { path: 'drafts', name: 'DraftList', component: () => import('@/views/writer/DraftListView.vue') } ] } ] })requiresAuth 元信息用于路由守卫判断用户是否登录。没有 token 时,跳转到登录页并记录 redirect 查询参数,登录完成后回到原页面。创作中心的数据与用户绑定,路由守卫是不可省的一步。
6.2 文章编辑器组件
Markdown 编辑器我用 md-editor-v3 做演示,因为它在 Vue3 工程里安装最方便,支持预览、工具栏定制、暗色主题。先安装依赖:
npm install md-editor-v3然后写一个核心的 EditorView.vue,包含标题输入、AI 功能按钮区、Markdown 编辑器和发布/保存按钮:
<script setup> import { ref, reactive } from 'vue' import { ElMessage } from 'element-plus' import { MdEditor } from 'md-editor-v3' import { saveDraft, publishArticle, aiApi } from '@/api/writer' const form = reactive({ id: null, title: '', summary: '', content: '' }) const saving = ref(false) const publishing = ref(false) const mdEditorRef = ref() async function handleSaveDraft() { if (saving.value) return saving.value = true try { form.id = await saveDraft(form) ElMessage.success('草稿已保存') } finally { saving.value = false } } async function handlePublish() { if (!form.title.trim()) { ElMessage.warning('请填写标题') return } if (!form.content.trim()) { ElMessage.warning('请填写正文内容') return } publishing.value = true try { await publishArticle(form) ElMessage.success('发布成功') } finally { publishing.value = false } } async function handleSummary() { const summary = await aiApi.generateSummary(form) form.summary = summary } </script> <template> <div class="editor-wrap"> <div class="editor-header"> <input v-model="form.title" class="title-input" placeholder="请输入文章标题" /> <div class="btn-group"> <el-button @click="handleSaveDraft" :disabled="saving">保存草稿</el-button> <el-button type="primary" @click="handlePublish" :disabled="publishing">发布文章</el-button> </div> </div> <div class="ai-toolbar"> <el-button size="small" @click="handleSummary">AI 生成摘要</el-button> <el-button size="small">AI 优化标题</el-button> <el-button size="small">AI 提取标签</el-button> </div> <MdEditor v-model="form.content" :on-upload-img="uploadImage" /> </div> </template>这段代码把内容回填逻辑写得比较直接:调用 AI 接口后,把返回结果写入 form.summary 或 form.title。你要根据实际 API 和字段结构调整方法签名,但是整体链路是可复用的。上传图片的 onUploadImg 方法需要把文件先传到后端,拿到 CDN 或静态资源 URL 后回填到编辑器,避免把图片存成 base64 撑爆数据库。
6.3 前端请求封装
创作中心几乎所有页面都依赖登录 token,因此 axios 实例需要统一配置请求头,并在响应拦截器里处理 401 和业务错误码。下面是一个通用封装:
import axios from 'axios' const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 30000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { return Promise.reject(new Error(res.message || '请求失败')) } return res.data }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(error) } ) export const saveDraft = (data) => service.post('/writer/draft', data) export const publishArticle = (data) => service.post('/writer/publish', data) export const getDraftPage = (params) => service.get('/writer/drafts', { params })这里把 backend 返回的 res.data 直接暴露给业务层,后续在 Vue 组件中调用 saveDraft 时,拿到的就是返回的 id 字段,不用再写 res.data.data。超时时间设置 30 秒是因为草稿内容比较长时不排除上传或 AI 调用耗时增加。
7. AI 写作面板交互与功能验证
7.1 交互流程
AI 写作面板的交互流程可以分成四步。第一步,用户写正文,编辑器通过 v-model 实时更新 form.content。第二步,用户点击“AI 生成摘要”,前端把 title、content 发送给后端接口。第三步,后端调用模型服务,返回摘要结果,前端将结果写入 form.summary。第四步,发布接口携带 summary 字段入库。
这套流程前端实现简单,问题是用户点击 AI 按钮后,页面处在等待状态。如果模型供应商返回慢,接口超时会导致页面白屏,所以请求期间必须有 loading。更稳的做法是给 AI 接口单独设置较长的超时时间,比如 60 秒,并在代码中给按钮 disable。
7.2 AI 生成结果回填编辑器
AI 续写或扩写的结果通常要插入到正文光标位置。不同编辑器暴露的 API 名称差异很大,建议以编辑器官方文档为准,核心思路是拿到编辑器的文本选区或光标位置,执行插入操作。为演示方便,可以直接拼接在正文后面:
function appendTextToContent(text: string) { if (!text) return form.content += `\n${text}\n` }实际项目中千万不要生硬地在 content 后追加,否则用户光标在文章中间时,AI 生成内容会跑到末尾。建议把编辑器实例暴露给 AI 面板,统一走编辑器 API 完成内容插入。如果使用 md-editor-v3,可以调用编辑器暴露的 insert 方法,具体参数以你安装版本的类型声明为准。
7.3 功能验证清单
创作中心做完后,不要急着继续开发下一个模块,先把以下清单跑一遍。这张表是我建议的最小验证集,能覆盖 80% 的常见回归问题:
| 测试功能 | 操作步骤 | 预期结果 |
|---|---|---|
| 保存草稿 | 填写标题和正文,点击保存草稿 | 页码不刷新,提示保存成功,重新加载后数据还在 |
| 自动防抖保存 | 连续输入标题,等待 2 秒 | 网络面板出现一次保存请求,不是每个字符都请求一次 |
| 发布文章 | 填写完整字段后点击发布 | status 变为已发布,前台列表出现文章 |
| AI 生成摘要 | 正文超过 200 字时点击 AI 生成摘要 | AI 区域出现摘要,摘要长度合理且没有明显错别字 |
| AI 内容安全拦截 | 输入明显不恰当的测试文本后提交发布 | 接口返回提示,文章未入库 |
| 上传封面图 | 选择 JPG/PNG 文件上传 | 返回 URL,封面区域预览正常 |
| 编辑已发布文章 | 从创作中心选择一篇已发布文章,修改后保存 | 前台详情页内容更新,浏览量不清零 |
执行这份清单时,最好使用真实后端环境而非 Mock 数据。如果 AI 接口或内容安全服务未接入,可以先临时返回成功结构,但要明确知道哪些环节是模拟的,不能把模拟结果当作正式结论。
8. 草稿自动保存、发布前校验与统计面板
8.1 防抖自动保存
创作中心和普通表单最大的区别,是用户可能在编辑器里停留很久。如果让用户手动保存草稿,遇到浏览器崩溃或手滑刷新就会丢失内容。建议前端增加自动保存机制。简单实现可以在 watch 里加防抖函数:
import { debounce } from 'lodash-es' const autoSave = debounce(() => { if (!form.title && !form.content) return saveDraft(form).then(id => { form.id = id }) }, 2000) watch(() => [form.title, form.content], autoSave)防抖时间通常设置 1 到 3 秒。太短会频繁请求,太长则容易丢数据。自动保存失败时应该做兜底:把失败内容写入 localStorage,下次打开编辑器时提示用户恢复本地草稿。这样可以覆盖接口临时不可用的场景。
8.2 发布前校验
发布动作不能只依赖前端校验,后端必须再次校验字段。尤其是空标题或空正文,不能让脏数据落到前台文章列表。后端校验可以用 Spring Validation,在 DTO 上添加注解:
public class ArticlePublishDTO { @NotBlank(message = "标题不能为空") @Size(max = 200, message = "标题长度不能超过200") private String title; @NotBlank(message = "正文内容不能为空") private String content; private Long categoryId; private String summary; }正文长度不建议直接限制得特别死,因为 Markdown 原文可能很长。真正要做的是限制正文超过合理阈值时返回友好提示。发布成功后,可以异步更新文章分词索引或通知搜索引擎更新,避免影响发布接口响应速度。
8.3 统计面板数据来源
创作中心的统计面板可以复用 article 表。下面两条 SQL 给出常用统计口径:
-- 当前用户草稿总数 SELECT COUNT(*) FROM article WHERE user_id = 1 AND status = 0; -- 当前用户累计发布文章字数 SELECT IFNULL(SUM(CHAR_LENGTH(markdown_content)), 0) FROM article WHERE user_id = 1 AND status = 1;如果你的项目已经拆了统计服务,可以改为主从库查询或定时汇总。统计面板不要实时全表扫,文章量上来后开销很大。更合理的做法是每天在业务低峰期汇总一次,缓存到 Redis,页面打开时直接读缓存。
9. 创作中心常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 前端请求接口 404 | 后端没有配置/api/writer前缀或前端 baseURL 不一致 | 查看 Network 请求 URL | 统一接口前缀,调整 nginx 或网关转发规则 |
| 保存草稿时提示无权限 | 请求头缺少 token 或 token 已过期 | 检查 localStorage 是否有 token | 重新登录,刷新 token |
| 发布文章后前台看不到 | status 没有改为 1,或前台接口按状态过滤 | 查数据库该记录 status | 检查 publish 逻辑,确认状态枚举值一致 |
| AI 摘要接口超时 | 模型响应慢或网络不通 | 看后端日志确认 AI 供应商返回时间 | 单独加长超时时间,或者改异步任务 |
| 编辑器图片上传 404 | 后端文件上传接口不存在或路径不对 | 查看浏览器控制台上传 URL | 实现上传接口并配置静态资源映射 |
| 用户 A 看到用户 B 草稿 | 查询列表没按 user_id 过滤 | 检查 SQL 条件 | 所有创作中心查询强制加 user_id 条件 |
| 文章内容出现 HTML 乱码 | Markdown 与富文本字段混用 | 对比 content 和 markdown_content | 统一编辑器类型,切换编辑器时做格式转换 |
| AI 生成结果包含不可控内容 | 缺内容安全校验 | 观察发布接口记录 | 接入应用层敏感词过滤和模型侧系统提示词 |
这些问题的根因大多是前后端边界不清晰。排查时不要一上来就看代码,先确定请求到达了后端哪一层。例如保存草稿失败,先看 Controller 是否收到参数;如果 Controller 收到了但数据库没写入,再看 Service 异常日志;如果本地能写入但线上失败,再排查数据库连接和事务问题。
10. 最佳实践与安全合规建议
10.1 工程化实践建议
开发创作中心时先做最小可用版本,再逐步叠加 AI 功能。最小版本至少要有编辑器、保存草稿、发布、列表查询四个能力,因为 AI 功能需要基于已保存的文章本体才能检验效果。先把本文数据库和接口骨架搭好,再去调整 AI 提示词,会少走很多弯路。
代码层面要注意几件事:文章正文属于大字段,分页查询时不要默认把 content 带回列表视图;列表接口只返回 id、title、summary、status、updateTime 这些轻量字段。详情编辑时才读取完整正文。另一个建议是给所有创作中心接口统一加日志,记录 userId、接口耗时、操作类型,方便定位线上问题。
10.2 AI 内容与版权安全
这里再强调一次,AI 辅助写作功能必须把安全合规放在功能效果之前。所有由模型生成的摘要、标题、标签、续写文本,在进入发布流程前都应经过后端校验。创作中心也应当在前台展示时区分 AI 生成内容与作者原创内容,例如在文章详情的元信息区显示 AI 辅助标记。这样读者能了解内容的生成方式。
素材版权方面,上传的封面图和正文图片应检查是否具备授权。用户导入已有文章时,用户对内容版权负责;运营方在平台规则中要明确禁止盗用他人作品。对涉及个人肖像的图片,应用层默认拒绝未授权上传,避免后续纠纷。AI 生成内容若引用了真实人物、品牌或未公开资料,发布前也应当由人类作者做最终审核。
10.3 权限、隐私和数据安全建议
创作中心涉及用户未发布的私密内容,数据库连接串、Redis 密码、AI API Key 都要通过环境变量或配置中心管理,不能硬编码在源码中。对外提供接口时,建议做接口维度的限流。例如用户每分钟最多调用 30 次保存接口、10 次 AI 生成接口,防止批量刷接口和消耗 AI 配额。
如果文章内容需要进入训练语料或数据仓库进行二次分析,必须先脱敏或取得用户同意。未发布的草稿不应该被任何统计任务读取并进入日志检索范围。生产环境应设置完善的备份策略,内容类数据一旦误删恢复成本很高。
11. 小结与下一步开发方向
这一讲的创作中心骨架已经从数据库、后端接口、AI 能力、Vue3 页面四个层面完整过了一遍。核心结论很简单:先把编辑和保存链路跑通,再把 AI 能力抽象成后端服务,最后把内容安全校验嵌入发布入口。这个顺序不要反,否则你会发现很多问题既不在 AI 供应商,也不在前端,而是在最基础的表结构上没有给内容状态留扩展余地。
对于一个系列教程来说,创作中心完成后,最值得继续开发的方向是定时发布、社交分享封面自动生成、多作者协作和 AI 生成内容的批量改写。你可以先验证自动保存草稿和发布后详情页的链路,再给文章列表和统计面板补充查询条件,逐步把创作中心做成可以直接上线的产品模块。建议把这篇文章收藏备用,后续开发草稿箱或 AI 功能模块时可以直接对照这里的表结构和接口设计。