AI自动生成文章标题,接入deepseek,AI

后台自动生成文章标题功能实现

最近给博客后台加了个小功能:写文章时,标题输入框旁边加个按钮,点击后根据文章内容自动生成标题。不用自己想标题了,AI 帮你搞定。

一、功能需求

  • 后台写文章时,标题输入框旁边有个「AI 生成标题」按钮
  • 点击后根据文章内容自动生成标题
  • 标题长度在 5-25 个字之间
  • 生成后自动填入标题输入框

二、技术选型

  • 后端:Spring Boot + OkHttp + DeepSeek/硅基流动 API
  • 前端:Vue 3 + TypeScript + Axios + Element Plus

三、后端实现

1. 添加依赖

如果你的项目还没有 OkHttp 依赖,需要在 pom.xml 中添加:

xml 复制代码
<!-- OkHttp (用于 AI 请求) -->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.12.0</version>
</dependency>

2. 配置文件

application-dev.yml 中添加 AI 配置(如果还没有):

yaml 复制代码
# AI 对话配置
ai:
  deepseek:
    api-key: ${AI_API_KEY:sk-你的API密钥}
    api-url: ${AI_API_URL:https://api.siliconflow.cn/v1/chat/completions}
    model: ${AI_MODEL:deepseek-ai/DeepSeek-V3-0324}
    system-prompt: ${AI_SYSTEM_PROMPT:你是一个博客智能助手,帮助用户解答技术问题。请用简洁、专业的中文回答,支持 Markdown 格式。}

3. AI 服务类

AiService.java 中添加生成标题方法:

java 复制代码
package com.ican.service;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ican.config.properties.DeepSeekProperties;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AiService {

    @Autowired
    private DeepSeekProperties deepSeekProperties;

    private final OkHttpClient httpClient = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(120, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .build();

    /**
     * 生成文章标题
     *
     * @param content 文章内容
     * @return 标题文本
     */
    public String generateTitle(String content) {
        // 截取前 3000 字,避免 token 过长
        String truncated = content.length() > 3000 ? content.substring(0, 3000) : content;

        JSONArray messagesArray = new JSONArray();

        // 系统提示词
        JSONObject systemMsg = new JSONObject();
        systemMsg.put("role", "system");
        systemMsg.put("content", "你是一个文章标题生成助手。请根据文章内容,生成一个简洁、有吸引力的中文标题。" +
                "要求:标题在5-25个字之间,不要加引号、书名号或其他标点包裹,直接输出标题文本。");
        messagesArray.add(systemMsg);

        // 用户消息
        JSONObject userMsg = new JSONObject();
        userMsg.put("role", "user");
        userMsg.put("content", "请为以下文章生成一个标题:\n\n" + truncated);
        messagesArray.add(userMsg);

        JSONObject requestBody = new JSONObject();
        requestBody.put("model", deepSeekProperties.getModel());
        requestBody.put("messages", messagesArray);
        requestBody.put("stream", false); // 非流式,同步返回
        requestBody.put("temperature", 0.7); // 提高创造性,标题更有吸引力
        requestBody.put("max_tokens", 60); // 限制输出长度,约 5-25 字

        Request request = new Request.Builder()
                .url(deepSeekProperties.getApiUrl())
                .addHeader("Authorization", "Bearer " + deepSeekProperties.getApiKey())
                .addHeader("Content-Type", "application/json")
                .post(RequestBody.create(requestBody.toJSONString(), MediaType.parse("application/json")))
                .build();

        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                String body = response.body() != null ? response.body().string() : "";
                log.error("AI 标题生成失败: {} {}", response.code(), body);
                throw new RuntimeException("AI 标题生成失败: " + response.code());
            }
            String responseBody = response.body().string();
            JSONObject jsonResponse = JSON.parseObject(responseBody);
            JSONArray choices = jsonResponse.getJSONArray("choices");
            if (choices != null && !choices.isEmpty()) {
                JSONObject message = choices.getJSONObject(0).getJSONObject("message");
                if (message != null) {
                    String result = message.getString("content");
                    // 清理可能的引号包裹
                    if (result != null) {
                        result = result.trim().replaceAll("^[\"'《「【]|[\"'》」】]$", "");
                    }
                    return result;
                }
            }
            return "";
        } catch (IOException e) {
            log.error("AI 标题生成异常", e);
            throw new RuntimeException("AI 标题生成异常: " + e.getMessage());
        }
    }
}

关键点说明

  • stream: false:非流式调用,同步返回结果
  • temperature: 0.7:提高创造性,标题更有吸引力
  • max_tokens: 60:限制输出长度,约 5-25 字
  • 截取前 3000 字:避免文章太长导致 token 超限
  • 系统提示词明确要求:标题长度、不要标点包裹
  • 后端清理:去除可能的引号、书名号等包裹

4. 控制器

AiController.java 中添加接口:

java 复制代码
package com.ican.controller;

import com.ican.model.vo.Result;
import com.ican.service.AiService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * AI 对话控制器
 *
 * @author ican
 */
@Api(tags = "AI 模块")
@RestController
public class AiController {

    @Autowired
    private AiService aiService;

    /**
     * AI 生成文章标题
     *
     * @param body 请求体,包含 content 字段(文章内容)
     * @return 标题文本
     */
    @ApiOperation(value = "AI 生成文章标题")
    @PostMapping("/admin/ai/title")
    public Result<String> generateTitle(@RequestBody Map<String, String> body) {
        String content = body.get("content");
        if (content == null || content.trim().isEmpty()) {
            return Result.fail("文章内容不能为空");
        }
        return Result.success(aiService.generateTitle(content));
    }
}

接口放在 /admin/ 路径下,需要登录才能访问(后台管理功能)。

四、前端实现

1. API 函数

api/article/index.ts 中添加:

typescript 复制代码
import { Result } from "@/model";
import request from "@/utils/request";
import { AxiosPromise } from "axios";

/**
 * AI 生成文章标题
 * @param content 文章内容
 * @returns 标题文本
 */
export function generateAiTitle(content: string): AxiosPromise<Result<string>> {
  return request({
    url: "/admin/ai/title",
    method: "post",
    timeout: 60000, // AI 调用可能需要较长时间,设置 60 秒超时
    data: { content },
  });
}

注意设置了 timeout: 60000(60 秒),因为 AI 调用可能需要几秒到十几秒。

2. 页面添加按钮

views/blog/article/write.vue 中,找到标题输入框,修改为:

vue 复制代码
<template>
    <div class="app-container">
        <!-- 文章标题 -->
        <div class="operation-container">
            <el-input v-model="articleForm.articleTitle" placeholder="请输入文章标题">
                <template #append>
                    <el-button :loading="aiTitleLoading" @click="handleAiTitle" title="AI 生成标题">
                        <el-icon><MagicStick /></el-icon>
                    </el-button>
                </template>
            </el-input>
            <el-button type="danger" style="margin-left: 10px" @click="openModel">发布文章</el-button>
        </div>

        <!-- 文章内容编辑器 -->
        <md-editor
            ref="editorRef"
            v-model="articleForm.articleContent"
            :theme="isDark ? 'dark' : 'light'"
            class="md-container"
            :toolbars="toolbars"
            @on-upload-img="uploadImg"
            placeholder="请输入文章内容..."
        >
            <template #defToolbars>
                <emoji-extension :on-insert="insert" />
            </template>
        </md-editor>

        <!-- 发布文章对话框 -->
        <!-- ... 原有发布对话框 ... -->
    </div>
</template>

<script setup lang="ts">
import { generateAiTitle } from "@/api/article";
import { MagicStick } from '@element-plus/icons-vue';
import { ElMessage } from "element-plus";
import { useDark } from "@vueuse/core";

const isDark = useDark();

// 文章表单数据
const articleForm = ref({
    articleTitle: "",
    articleContent: "",
    // ... 其他字段
});

// AI 生成标题
const aiTitleLoading = ref(false);

const handleAiTitle = async () => {
    const content = articleForm.value.articleContent;
    if (!content || content.trim() === "") {
        ElMessage.warning("请先编写文章内容");
        return;
    }
    aiTitleLoading.value = true;
    try {
        const { data } = await generateAiTitle(content);
        if (data.flag && data.data) {
            articleForm.value.articleTitle = data.data;
            ElMessage.success("AI 标题生成成功");
        } else {
            ElMessage.error(data.msg || "AI 标题生成失败");
        }
    } catch {
        ElMessage.error("AI 标题生成失败,请稍后重试");
    } finally {
        aiTitleLoading.value = false;
    }
};
</script>

<style scoped>
.operation-container {
    display: flex;
    align-items: center;
    margin-bottom: 1.25rem;
}

.md-container {
    min-height: 300px;
    height: calc(100vh - 200px);
}
</style>

按钮使用了 Element Plus 的 MagicStick 图标(魔法棒),放在输入框的 append 插槽中。

五、使用效果

  1. 在后台写文章页面,先写好文章内容
  2. 点击标题输入框右侧的魔法棒按钮
  3. 等待几秒(按钮显示 loading)
  4. AI 自动生成标题并填入输入框
  5. 可以手动修改后再发布

六、遇到的问题

1. AI 返回格式问题

问题 :AI 可能返回带引号或书名号的标题(如 "文章标题"《文章标题》)。

解决 :在系统提示词中明确要求「不要加引号、书名号或其他标点包裹」。后端再用正则表达式清理:replaceAll("^[\"'《「【]|[\"'》」】]$", "")

2. 标题长度控制

问题:AI 可能生成过短或过长的标题。

解决 :在系统提示词中明确要求「标题在5-25个字之间」。设置 max_tokens: 60,限制输出长度。

3. 文章内容为空

问题:用户没有写文章内容就点击生成标题。

解决:前端检查文章内容是否为空,为空时提示「请先编写文章内容」。

七、总结

这个功能实现起来不算复杂:

  1. 后端:新增一个同步调用的 AI 接口,专门用于生成标题
  2. 前端:加个按钮,调用 API,把结果填入输入框

关键是:

  • 系统提示词要明确(标题长度、不要标点包裹)
  • 设置合适的 temperature(提高创造性)和 max_tokens(限制长度)
  • 前端设置足够的超时时间
  • 处理错误情况,给用户友好提示
  • 后端清理可能的标点包裹

现在写文章时,标题可以一键生成,省了不少时间。如果生成的不满意,还可以手动修改。

八、完整代码文件清单

后端

  • pom.xml - 添加 OkHttp 依赖
  • application-dev.yml - 添加 AI 配置
  • AiService.java - 添加 generateTitle 方法
  • AiController.java - 添加 /admin/ai/title 接口

前端

  • api/article/index.ts - 添加 generateAiTitle 函数
  • views/blog/article/write.vue - 添加标题生成按钮和逻辑

按照上面的步骤,就可以在自己的项目中实现这个功能了。

相关推荐
九.九7 小时前
ops-transformer:AI 处理器上的高性能 Transformer 算子库
人工智能·深度学习·transformer
春日见7 小时前
拉取与合并:如何让个人分支既包含你昨天的修改,也包含 develop 最新更新
大数据·人工智能·深度学习·elasticsearch·搜索引擎
恋猫de小郭7 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
deephub7 小时前
Agent Lightning:微软开源的框架无关 Agent 训练方案,LangChain/AutoGen 都能用
人工智能·microsoft·langchain·大语言模型·agent·强化学习
大模型RAG和Agent技术实践7 小时前
从零构建本地AI合同审查系统:架构设计与流式交互实战(完整源代码)
人工智能·交互·智能合同审核
老邋遢7 小时前
第三章-AI知识扫盲看这一篇就够了
人工智能
互联网江湖7 小时前
Seedance2.0炸场:长短视频们“修坝”十年,不如AI放水一天?
人工智能
青云计划7 小时前
知光项目知文发布模块
java·后端·spring·mybatis
PythonPioneer7 小时前
在AI技术迅猛发展的今天,传统职业该如何“踏浪前行”?
人工智能
赶路人儿8 小时前
Jsoniter(java版本)使用介绍
java·开发语言