基于 Django+Vue3 的 AI 海报生成平台开发(海报模块专项)

发布于:2025-09-08 ⋅ 阅读:(20) ⋅ 点赞:(0)

1、项目介绍

1.1、需求分析

1.1.1、功能性需求

从业务场景出发,AI 海报生成平台的核心需求围绕 “便捷、智能、可定制” 的海报创作全流程展开,具体包括:

  • AI 辅助创作能力提供提示词助手(多轮对话式 AI),帮助用户生成专业海报描述,降低创作门槛;支持通过 Coze 智能体豆包视觉大模型实现 “文本→海报”“海报→优化海报” 的 AI 驱动创作。
  • 海报生成功能:支持模板选择(热门、小红书、商业、节日等分类)、关键词推荐(如 “科技感未来城市”“简约风格插画”)、图片比例自定义(1:1、2:3、4:3 等),满足不同场景(社交分享、商业宣传、节日活动)的海报尺寸需求。
  • 海报优化功能:支持海报内容修改(基于豆包视觉大模型)、画面清晰化、自定义添加 Logo 和二维码;允许上传本地海报进行二次编辑,覆盖 “生成→修改→完善” 的全链路需求。
  • 数据持久化与隔离:用户生成的海报记录、对话会话需长期存储,且严格按用户隔离(通过用户 ID 关联数据),支持记录查询、筛选(按海报类型)、删除操作。
  • 交互体验优化:提示词助手支持流式对话(实时返回 AI 回复),海报生成 / 修改过程显示加载状态,响应式布局适配 PC、平板、手机等设备。

1.1.2、非功能性需求

  • 性能需求:AI 接口响应时间≤5s(流式对话支持增量返回,降低等待感知);图片上传大小限制≤5MB,避免占用过多带宽;页面加载时间≤2s。
  • 兼容性需求:前端支持 Chrome、Edge、Safari 等主流浏览器;响应式适配PC端。
  • 安全性需求:图片上传需过滤非图片格式文件;第三方 API 密钥(Coze、豆包)需通过环境变量或配置文件管理,避免硬编码;用户数据(海报记录、对话)需按用户 ID 隔离,防止越权访问。
  • 可用性需求:操作流程直观(如模板 “一键使用”、关键词 “点击添加”);错误提示明确(如 “图片 URL 不能为空”“生成失败:网络错误”);支持本地图片拖拽上传,降低操作成本。

1.2、技术栈

围绕海报模块的开发需求,技术栈分为后端前端两层,重点集成 AI 能力与前后端交互能力:

层面 技术 / 工具 用途说明
后端核心 Django 5.2.5 Web 框架,提供请求路由、数据模型定义、中间件支持(如 CORS 跨域)
Django REST Framework (DRF) 构建 RESTful API,处理海报生成、对话等接口逻辑
MySQL 8.0 存储用户海报记录(poster_records)、聊天会话(chat_sessions)等数据
AI 能力集成 Coze API 提供工作流(海报生成)、智能体(提示词助手)能力
豆包视觉大模型(doubao-seededit) 实现海报修改、画面优化的视觉生成能力
前端核心 Vue 3 (Composition API + TS) 构建组件化、类型安全的前端界面
Element Plus 提供模板选择、对话界面、表单等 UI 组件,支持响应式
Pinia 状态管理,存储海报模板、生成记录、对话历史等全局数据
辅助工具 Axios 前端与后端 API 交互,处理流式响应、请求拦截(如添加 Token)
Saaa 实现响应式样式、渐变动画、自定义滚动条等视觉效果
FreeImage.host + 代理服务器 处理本地图片上传,将本地文件转换为可访问的网络 URL

1.3 项目运行效果图

(注:以下为功能页面描述,建议实际运行项目后截取对应截图插入)

  1. 海报生成页

  1. 左侧为模板选择区(Tab 切换 “热门模板”“小红书海报” 等分类,模板卡片 hover 显示 “使用模板” 按钮),右侧为输入区(热门关键词标签、提示词文本框、比例选择器、“生成海报” 按钮),顶部为渐变风格标题栏(含 “智能生成”“多种模板” 等功能标识)。

  2. 提示词助手页

  1. 左侧为会话列表(显示会话标题、最后一条消息预览、时间),右侧为聊天区(欢迎消息→用户输入→AI 流式回复,底部为输入框和发送按钮),支持会话新建、删除、切换。

3.修改海报页

  1. 三栏布局 —— 左侧 “画面调整” 面板(修改描述输入、生成记录管理)、中间 “海报预览” 区(支持图片缩放、下载、取消预览)、右侧 “元素添加” 面板(Logo / 二维码上传、位置选择),支持本地海报拖拽上传。

1.4 项目目录结构

原项目目录已具备基础结构,但为提升可维护性,对海报相关目录进行优化(补充分层、明确职责),优化后结构如下:

1.4.1 后端目录(Django)


1.4.2 前端目录(Vue3)

2. 项目架构分析

2.1 架构图

2.2 架构图内容解析

(1)数据流向逻辑

  1. 用户操作→前端:用户在 “生成海报页” 选择模板、输入提示词,前端通过posterApi.ts将参数(promptpropotion)发送给后端。
  2. 前端→后端:后端路由(/api/posters/posters/)将请求转发至PostersView视图,视图调用CozeServicerun_coze_workflow()方法,传入参数调用 Coze 工作流 API。
  3. 后端→第三方 AI:Coze 工作流生成海报 URL 后返回后端,后端将 URL 存入poster_records表(关联当前用户 ID),并将结果返回前端。
  4. 前端渲染:前端 Pinia 存储海报 URL,跳转至 “修改海报页” 预览,用户可进一步添加 Logo、二维码或优化画面。

(2)核心分层职责

  • 前端层:负责交互与视觉渲染,通过 Pinia 管理全局状态(如模板列表、当前海报 URL),通过 Axios 处理请求(含流式响应,如提示词助手的实时回复)。

  • 后端层:负责业务逻辑与数据管理,视图层处理请求参数校验,服务层封装 AI 接口调用,数据模型层确保用户数据隔离(如PosterRecorduser外键关联SysUser)。
  • 第三方服务层:提供 AI 能力(Coze 生成海报、豆包优化画面)和图片存储(FreeImage.host),代理服务器解决图片上传的跨域问题。

3. 开发的主要功能分析(含代码、注释、效果图)

3.1 提示词助手

提示词助手是降低用户创作门槛的核心功能,基于 Coze 智能体实现多轮对话,支持会话记录持久化,核心解决 “用户不会写提示词” 的问题。

3.1.1 扣子(Coze)智能体的设计

核心逻辑:通过 Coze API 创建聊天机器人实例,支持文本交互与流式回复,封装CozeChatBot类统一管理对话逻辑。

关键代码(后端services/coze_service.py

from cozepy import Coze, TokenAuth, Message, ChatEventType
from typing import List, Optional
import logging

# 配置日志
logger = logging.getLogger(__name__)

# Coze API配置(建议从环境变量读取,此处为示例)
COZE_API_TOKEN = 'pat_JfvLHMeGpuTHvi7tVNsFIrQs3xo4mbGQ45JCmNPHvR4Ziin7wwmUYul6jdINZg9m'
COZE_BASE_URL = 'https://api.coze.cn/v3'
BOT_ID = '7543072474888929280'  # 预配置的海报提示词助手智能体ID

class CozeChatBot:
    def __init__(self, bot_id: str = BOT_ID, user_id: str = "default_user", conversation_history: List[Message] = None):
        """
        初始化Coze聊天机器人
        :param bot_id: 智能体ID(Coze平台创建)
        :param user_id: 用户ID(用于会话隔离)
        :param conversation_history: 历史对话记录
        """
        # 初始化Coze客户端
        self.coze_client = Coze(
            auth=TokenAuth(token=COZE_API_TOKEN),
            base_url=COZE_BASE_URL
        )
        self.bot_id = bot_id
        self.user_id = user_id
        # 对话历史(默认空列表)
        self.conversation_history: List[Message] = conversation_history or []

    def chat_stream(self, user_input: str):
        """
        流式对话(核心!实时返回AI回复)
        :param user_input: 用户输入的提示词需求(如“帮我写一个奶茶海报的提示词”)
        :yield: AI回复的增量内容
        """
        try:
            # 1. 构建用户消息(Coze要求的格式)
            user_message = Message.build_user_question_text(user_input)
            self.conversation_history.append(user_message)

            # 2. 调用Coze流式API
            stream = self.coze_client.chat.stream(
                bot_id=self.bot_id,
                user_id=self.user_id,
                additional_messages=self.conversation_history  # 携带历史对话
            )

            # 3. 处理流式响应
            bot_response = ""
            for event in stream:
                # 增量消息事件(实时返回每一段回复)
                if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA:
                    if event.message and event.message.content:
                        bot_response += event.message.content
                        yield event.message.content  # 增量返回给前端
                # 对话完成事件(记录Token使用)
                elif event.event == ChatEventType.CONVERSATION_CHAT_COMPLETED:
                    logger.info(f"Token使用量: {event.chat.usage.token_count}")

            # 4. 保存AI回复到历史记录
            bot_message = Message.build_assistant_answer(bot_response)
            self.conversation_history.append(bot_message)

        except Exception as e:
            logger.error(f"流式对话失败: {str(e)}")
            yield f"错误: {str(e)}"

设计说明

  • 封装CozeChatBot类,屏蔽 Coze API 的细节,视图层只需调用chat_stream()即可实现流式对话。
  • 通过user_id参数关联当前登录用户,确保不同用户的对话历史隔离;conversation_history参数支持多轮对话上下文关联。

3.1.2 多轮对话的实现

核心逻辑:前端通过流式 HTTP 响应接收 AI 增量回复,实时更新界面;后端通过ChatSession(会话)和ChatMessage(消息)模型管理多轮对话。

关键代码(前端views/poster/PosterChat.vue

// 发送流式消息
const sendStreamMessage = async (message: string) => {
  searchDataStore.isStreaming = true;
  searchDataStore.streamingMessage = "";

  try {
    // 调用后端流式接口
    const response = await fetch("/api/posters/chat/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${localStorage.getItem("token")}`
      },
      body: JSON.stringify({
        session_id: currentSessionId.value, // 会话ID(为空则新建)
        message: message,
        stream: true
      })
    });

    if (!response.ok) throw new Error("请求失败");

    // 处理流式响应
    const reader = response.body?.getReader();
    const decoder = new TextDecoder();
    let buffer = "";

    while (true) {
      const { done, value } = await reader!.read();
      if (done) break;

      // 解码二进制流
      const chunk = decoder.decode(value, { stream: true });
      buffer += chunk;
      const lines = buffer.split("\n");
      buffer = lines.pop() || "";

      // 解析每一行数据(SSE格式:data: {...})
      for (const line of lines) {
        if (line.startsWith("data: ")) {
          const data = JSON.parse(line.slice(6));
          if (data.type === "chunk") {
            // 增量更新AI回复
            searchDataStore.streamingMessage += data.content;
            await scrollToBottom(); // 滚动到底部
          } else if (data.type === "end") {
            // 对话完成,保存AI消息到本地状态
            searchDataStore.currentChatSession?.messages.push({
              id: data.message_id,
              message_type: "assistant",
              content: searchDataStore.streamingMessage,
              create_time: new Date().toISOString()
            });
          }
        }
      }
    }
  } catch (error: any) {
    ElMessage.error(`发送失败: ${error.message}`);
  } finally {
    searchDataStore.isStreaming = false;
  }
};

效果图描述
用户输入 “帮我写一个咖啡店夏季海报的提示词” 后,输入框右侧显示 “发送中...”,AI 回复以 “打字机” 效果实时显示(如 “咖啡店夏季海报提示词建议:1. 风格:清新日系风,背景用浅薄荷绿... ”),无需等待完整回复,提升交互体验。

3.1.3 会话记录的长久储存与用户隔离

核心逻辑:通过ChatSession(会话)和ChatMessage(消息)模型关联用户 ID,实现 “用户→会话→消息” 的层级隔离;后端接口查询时过滤当前用户的会话,确保数据安全。

关键代码(后端models.py

from django.db import models
from user.models import SysUser
import uuid

class ChatSession(models.Model):
    """聊天会话模型(用户→多会话)"""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(
        SysUser,
        on_delete=models.CASCADE,
        related_name="chat_sessions",  # 反向查询:用户.chat_sessions.all()
        verbose_name="关联用户"
    )
    title = models.CharField(max_length=200, default="新对话", verbose_name="会话标题")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    update_time = models.DateTimeField(auto_now=True, verbose_name="最后更新时间")
    is_active = models.BooleanField(default=True, verbose_name="是否活跃")

    class Meta:
        db_table = "chat_sessions"
        ordering = ["-update_time"]  # 按更新时间倒序(最新会话在前)

class ChatMessage(models.Model):
    """聊天消息模型(会话→多消息)"""
    MESSAGE_TYPE_CHOICES = [("user", "用户消息"), ("assistant", "AI回复")]
    id = models.AutoField(primary_key=True)
    session = models.ForeignKey(
        ChatSession,
        on_delete=models.CASCADE,
        related_name="messages",  # 反向查询:会话.messages.all()
        verbose_name="所属会话"
    )
    message_type = models.CharField(max_length=20, choices=MESSAGE_TYPE_CHOICES)
    content = models.TextField(verbose_name="消息内容")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name="发送时间")

    class Meta:
        db_table = "chat_messages"
        ordering = ["create_time"]  # 按发送时间正序(历史消息顺序)

查询逻辑(后端views.py

class ChatSessionDetailView(APIView):
    permission_classes = [IsAuthenticated]  # 仅登录用户可访问

    def get(self, request, session_id):
        """获取指定会话的消息(用户隔离)"""
        try:
            # 1. 过滤:仅查询当前用户的活跃会话
            session = ChatSession.objects.get(
                id=session_id,
                user=request.user,  # 关键!用户隔离
                is_active=True
            )
            # 2. 获取会话下的所有消息
            messages = ChatMessage.objects.filter(session=session).order_by("create_time")
            # 3. 格式化返回
            message_list = [{
                "id": msg.id,
                "message_type": msg.message_type,
                "content": msg.content,
                "create_time": msg.create_time.strftime("%Y-%m-%d %H:%M:%S")
            } for msg in messages]
            return Response({
                "code": 200,
                "data": {"session_id": str(session.id), "messages": message_list}
            })
        except ChatSession.DoesNotExist:
            return Response({"code": 404, "message": "会话不存在"})

3.2 生成海报

3.2.1 扣子(Coze)工作流的设计

核心逻辑:通过 Coze 工作流串联 “参数接收→AI 生成→结果返回” 的流程,支持动态传入模板、提示词、比例等参数,无需后端编写复杂 AI 逻辑。

关键代码(后端services/coze_service.py

def run_coze_workflow(parameters: Dict[str, str]) -> Optional[str]:
    """
    调用Coze工作流生成海报
    :param parameters: 工作流参数(img、prompt、propotion等)
    :return: 生成的海报URL
    """
    try:
        logger.info(f"执行Coze工作流,参数: {parameters}")
        # 1. 启动Coze工作流流式运行
        stream = coze_client.workflows.runs.stream(
            workflow_id="7541234832299802667",  # Coze工作流ID
            parameters=parameters
        )
        # 2. 处理工作流事件流,提取海报URL
        return _handle_workflow_stream(stream)
    except Exception as e:
        logger.error(f"工作流执行失败: {str(e)}")
        raise Exception(f"生成海报失败: {str(e)}")

def _handle_workflow_stream(stream: Stream[WorkflowEvent]) -> Optional[str]:
    """处理工作流事件流,提取图片URL"""
    for event in stream:
        # 消息事件:工作流返回结果(含海报URL)
        if event.event == WorkflowEventType.MESSAGE:
            if event.message and event.message.content:
                try:
                    # Coze工作流返回格式:{"output": "https://xxx.png"}
                    content = json.loads(event.message.content)
                    if "output" in content:
                        return content["output"]  # 返回海报URL
                except json.JSONDecodeError:
                    logger.warning("解析工作流结果失败")
        # 错误事件:抛出异常
        elif event.event == WorkflowEventType.ERROR:
            error_msg = event.error.message if event.error else "未知错误"
            raise Exception(f"工作流出错: {error_msg}")
    # 未获取到URL
    raise Exception("工作流未返回海报URL")

Coze 工作流设计建议(补充优化):
在 Coze 平台创建工作流时,建议按以下节点设计:

  1. 参数接收节点:定义img(模板 URL)、prompt(提示词)、propotion(比例)等输入参数。
  2. AI 生成节点:调用 Coze 的 “图像生成” 能力,将参数注入生成 prompt(如 “根据模板 {img},按比例 {propotion},生成 {prompt} 风格的海报”)。
  3. 结果返回节点:将生成的图片 URL 按{"output": "xxx"}格式返回,便于后端解析。

3.2.2 模板和关键词

核心逻辑:前端通过 Pinia 存储模板数据和热门关键词,用户选择模板后自动填充对应的提示词,关键词支持 “点击添加”,降低输入成本。

关键代码(前端stores/posters.ts

// 热门关键词(优化后:按场景分类)
const hotKeywords = ref([
  { label: "科技感", category: "风格" },
  { label: "未来城市", category: "场景" },
  { label: "简约插画", category: "风格" },
  { label: "商业宣传", category: "用途" },
  // ... 更多关键词
]);

// 海报模板数据(按类型分类)
const posterTemplates = ref({
  hot: [  // 热门模板
    { id: "hot1", url: "/assets/images/posters/hot/hot1.png", prompt: "科技感未来城市海报,霓虹灯效果" },
    { id: "hot2", url: "/assets/images/posters/hot/hot2.png", prompt: "简约风格产品宣传海报,清新色调" },
  ],
  xiaohongshu: [  // 小红书模板
    { id: "xh1", url: "/assets/images/posters/xiaohongshu/xh1.png", prompt: "小红书美食探店海报,暖色调,突出食物细节" },
  ]
});

// 选择模板:自动填充提示词
const selectTemplate = (template: PosterTemplate) => {
  searchValue.value = template.prompt;  // 填充提示词
  currentTemplate.value = template;    // 记录当前模板
};

// 选择关键词:追加到提示词
const selectKeyword = (keyword: string) => {
  if (searchValue.value) {
    searchValue.value += ` ${keyword}`;
  } else {
    searchValue.value = keyword;
  }
};

效果图描述

  • 模板区域:以网格布局展示模板卡片,hover 时显示 “使用模板” 按钮,点击后右侧输入框自动填充该模板的默认提示词(如 “科技感未来城市海报,霓虹灯效果”)。
  • 关键词区域:以标签云形式展示关键词,点击 “科技感” 后,输入框内容追加 “科技感”(如原内容为 “未来城市”,变为 “未来城市 科技感”)。

3.2.3 比例的选择

核心逻辑:支持 5 种常用海报比例(1:1、2:3、4:3、9:16、16:9),用户选择后将比例值传入后端,Coze 工作流按比例生成海报。

关键代码(前端components/poster/RatioSelector.vue

<template>
  <el-select
    v-model="selectedRatio"
    placeholder="选择海报比例"
    @change="handleRatioChange"
  >
    <el-option
      v-for="(item, key) in ratioOptions"
      :key="key"
      :label="`${item.label}(${item.value})`"
      :value="key"
    >
      <!-- 比例预览:小方块展示比例 -->
      <div class="ratio-preview" :style="{ width: item.width, height: item.height }"></div>
    </el-option>
  </el-select>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useSearchDataStore } from "@/stores/posters.ts";

const searchDataStore = useSearchDataStore();
const selectedRatio = ref("2");  // 默认2:3(竖版,适合小红书)

// 比例配置(优化后:含预览尺寸)
const ratioOptions = ref({
  "1": { label: "正方形", value: "1:1", width: "40px", height: "40px" },
  "2": { label: "竖版照片", value: "2:3", width: "40px", height: "60px" },
  "3": { label: "标准照片", value: "4:3", width: "53px", height: "40px" },
  "4": { label: "长屏", value: "9:16", width: "22.5px", height: "40px" },
  "5": { label: "宽屏", value: "16:9", width: "71px", height: "40px" }
});

// 比例变化:同步到Pinia
const handleRatioChange = (value: string) => {
  searchDataStore.setImageRatio(value, ratioOptions.value[value].value);
};
</script>

<style scoped>
.ratio-preview {
  display: inline-block;
  margin-right: 8px;
  border: 2px solid #667eea;
  border-radius: 4px;
}
</style>

3.3 修改海报

3.3.1 修改海报(豆包视觉大模型)

核心逻辑:用户上传原始海报 URL 和修改提示词(如 “将背景改为森林”),后端调用豆包视觉大模型(doubao-seededit-3-0-i2i-250628)实现 “图生图” 优化。

关键代码(后端services/doubao_service.py

from volcenginesdkarkruntime import Ark
import os
import random
from dotenv import load_dotenv

# 加载环境变量(优化:避免密钥硬编码)
load_dotenv()
ARK_API_KEY = os.environ.get("ARK_API_KEY")

# 初始化豆包客户端
doubao_client = Ark(
    base_url="https://ark.cn-beijing.volces.com/api/v3",
    api_key=ARK_API_KEY
)

def modify_poster(image_url: str, prompt: str) -> str:
    """
    调用豆包视觉大模型修改海报
    :param image_url: 原始海报URL
    :param prompt: 修改提示词
    :return: 修改后的海报URL
    """
    try:
        # 调用豆包图生图API
        response = doubao_client.images.generate(
            model="doubao-seededit-3-0-i2i-250628",  # 豆包视觉大模型ID
            prompt=prompt,
            image=image_url,  # 原始图片(图生图)
            seed=random.randint(1, 10000),  # 随机种子(避免重复)
            guidance_scale=8.5,  # 引导力度(越高越贴近prompt)
            size="adaptive",  # 自适应原始图片尺寸
            watermark=False  # 不添加水印
        )
        # 提取结果
        if response.data and len(response.data) > 0:
            return response.data[0].url
        raise Exception("豆包未返回修改后的图片")
    except Exception as e:
        raise Exception(f"修改海报失败: {str(e)}")

3.3.2 清晰化

核心逻辑:用户点击 “变清晰” 按钮时,后端不传入修改提示词,仅将原始海报 URL 传入 Coze 工作流,通过 AI 提升画面分辨率和清晰度。

关键代码(后端views.py

class PostersView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 获取参数
        img = request.data.get("img")  # 原始海报URL
        prompt = request.data.get("prompt", "")  # 默认为空(清晰化)
        # 1. 确定海报类型(清晰化:无prompt且有img)
        if img and not prompt:
            poster_type = "enhance"  # 清晰化类型
            # 构造参数(仅传img,不传递prompt)
            parameters = {"img": img, "prompt": "提升画面清晰度,优化细节", "propotion": ""}
        else:
            # 其他类型(生成/修改)
            poster_type = "modify" if img else "generate"
            parameters = {"img": img, "prompt": prompt, "propotion": request.data.get("propotion")}
        
        # 2. 调用Coze工作流(清晰化)
        try:
            image_url = run_coze_workflow(parameters)
            # 3. 保存记录
            PosterRecord.objects.create(
                user=request.user,
                image_url=image_url,
                prompt="画面清晰化",
                poster_type=poster_type
            )
            return Response({"code": 200, "data": {"image_url": image_url}})
        except Exception as e:
            return Response({"code": 500, "message": f"清晰化失败: {str(e)}"})

3.3.3 添加 logo 和二维码

核心逻辑:用户上传 Logo / 二维码图片(本地或 URL),选择位置(左上、右上、右下等 9 个位置),后端将参数传入 Coze 工作流,实现元素叠加。

关键代码(前端components/poster/ElementAdd.vue

<template>
  <!-- Logo上传 -->
  <div class="element-upload">
    <h3>添加Logo</h3>
    <el-upload
      action="/api/upload"  # 后端上传接口(或代理服务器)
      :on-success="handleLogoUpload"
      :before-upload="checkImageSize"
    >
      <div class="upload-area">点击上传Logo</div>
    </el-upload>
    <!-- 位置选择 -->
    <el-select v-model="logoPosition" placeholder="选择Logo位置">
      <el-option label="左上" value="nw"></el-option>
      <el-option label="右上" value="ne"></el-option>
      <el-option label="右下" value="se"></el-option>
      <!-- 其他位置 -->
    </el-select>
  </div>

  <!-- 二维码上传(逻辑同上) -->
  <div class="element-upload">
    <h3>添加二维码</h3>
    <!-- 略... -->
  </div>

  <!-- 确认添加按钮 -->
  <el-button @click="addElements">添加到海报</el-button>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useSearchDataStore } from "@/stores/posters.ts";
import { posterApi } from "@/api/posterApi.ts";

const searchDataStore = useSearchDataStore();
const logoPosition = ref("nw");  // 默认左上
const qrPosition = ref("se");   // 默认右下
const logoUrl = ref("");
const qrUrl = ref("");

// 检查图片大小(≤5MB)
const checkImageSize = (file: File) => {
  const isLt5M = file.size / 1024 / 1024 < 5;
  if (!isLt5M) {
    ElMessage.error("图片大小不能超过5MB");
  }
  return isLt5M;
};

// Logo上传成功:保存URL
const handleLogoUpload = (response: any) => {
  logoUrl.value = response.data.image_url;
};

// 添加元素到海报
const addElements = async () => {
  try {
    const result = await posterApi.addElements({
      img: searchDataStore.img_uml,  // 当前海报URL
      logo_img: logoUrl.value,
      logo_place: logoPosition.value,
      qr_img: qrUrl.value,
      qr_place: qrPosition.value
    });
    // 更新海报URL
    searchDataStore.img_uml = result.data.image_url;
    ElMessage.success("元素添加成功");
  } catch (error) {
    ElMessage.error("添加失败");
  }
};
</script>

3.3.4 上传本地海报修改

核心逻辑:支持本地图片拖拽 / 点击上传,通过代理服务器(proxy-server.js)上传到 FreeImage.host,转换为可访问的网络 URL,再进行修改。

关键代码(前端utils/imageUtils.ts

import { ElMessage } from "element-plus";

export const uploadLocalImage = async (file: File): Promise<string> => {
  try {
    // 1. 检查文件类型
    if (!file.type.startsWith("image/")) {
      throw new Error("请上传图片文件");
    }
    // 2. 检查文件大小
    if (file.size > 5 * 1024 * 1024) {
      throw new Error("图片大小不能超过5MB");
    }
    // 3. 构造FormData(FreeImage.host要求的格式)
    const formData = new FormData();
    formData.append("key", "6d207e02198a847aa98d0a2a901485a5");  // FreeImage API Key
    formData.append("source", file);
    formData.append("format", "json");

    // 4. 调用代理服务器(解决跨域)
    const response = await fetch("http://localhost:3000/upload", {
      method: "POST",
      body: formData
    });
    const data = await response.json();

    // 5. 提取网络URL
    if (data.status_code === 200 && data.image?.url) {
      return data.image.url;
    }
    throw new Error("上传失败: " + data.error?.message);
  } catch (error: any) {
    ElMessage.error(error.message);
    throw error;
  }
};

代理服务器作用
FreeImage.host 的 API 存在跨域限制(前端无法直接调用),proxy-server.js作为中间层,接收前端请求后转发至 FreeImage.host,再将结果返回前端,解决跨域问题。

3.3.5 生成记录的长久储存和用户隔离

核心逻辑:通过PosterRecord模型关联用户 ID,存储海报 URL、提示词、类型等信息;前端支持按类型筛选(生成 / 修改 / 清晰化),用户仅能查看自己的记录。

关键代码(后端views.py

class PosterRecordsView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        """获取用户的海报记录(支持筛选)"""
        # 1. 获取筛选参数
        poster_type = request.GET.get("poster_type")  # 类型筛选
        search = request.GET.get("search")            # 关键词搜索(提示词)
        
        # 2. 基础查询:当前用户的未删除记录
        queryset = PosterRecord.objects.filter(
            user=request.user,
            is_deleted=False
        )
        
        # 3. 应用筛选条件
        if poster_type:
            queryset = queryset.filter(poster_type=poster_type)
        if search:
            queryset = queryset.filter(prompt__icontains=search)  # 模糊搜索
        
        # 4. 格式化返回
        records = [{
            "id": record.id,
            "image_url": record.image_url,
            "prompt": record.prompt,
            "poster_type": record.poster_type,
            "poster_type_display": record.get_poster_type_display(),  # 中文显示(如“生成海报”)
            "create_time": record.create_time.strftime("%Y-%m-%d %H:%M:%S")
        } for record in queryset.order_by("-create_time")]
        
        return Response({
            "code": 200,
            "data": {
                "records": records,
                "total_count": len(records)
            }
        })

3.3.6 本地上传图片转换图片地址

核心逻辑:通过 FreeImage.host 的 API 将本地图片转换为网络 URL,前端调用代理服务器上传,后端存储转换后的 URL 用于后续修改。

关键代码(后端utils/image_utils.py

import requests

def convert_local_image_to_url(file_path: str) -> str:
    """
    本地图片文件转换为网络URL(后端备用方案,前端优先用代理)
    :param file_path: 本地图片路径
    :return: 网络URL
    """
    url = "https://freeimage.host/api/1/upload"
    data = {
        "key": "6d207e02198a847aa98d0a2a901485a5",
        "format": "json"
    }
    files = {"source": open(file_path, "rb")}
    
    response = requests.post(url, data=data, files=files)
    response_data = response.json()
    
    if response_data["status_code"] == 200:
        return response_data["image"]["url"]
    raise Exception(f"图片转换失败: {response_data['error']['message']}")

4. 项目总结

4.1 核心功能实现

功能模块 实现要点
提示词助手 基于 Coze 智能体的流式多轮对话、会话记录用户隔离、持久化存储
海报生成 Coze 工作流驱动、模板 / 关键词辅助、多比例支持
海报修改 豆包视觉大模型(图生图)、画面清晰化、Logo / 二维码添加、本地图片上传转换
记录管理 海报记录 / 会话记录的用户隔离、筛选(类型 / 关键词)、删除

4.2 技术亮点

  1. 多 AI 服务协同:集成 Coze(工作流 + 智能体)和豆包(视觉大模型),分别负责 “海报生成 / 对话” 和 “海报修改”,优势互补。
  2. 流式交互体验:提示词助手采用流式响应,AI 回复实时增量显示,降低用户等待感知;海报生成 / 修改过程显示加载状态,提升交互透明度。
  3. 响应式设计:通过 SCSS 的@media查询适配多设备(PC / 平板 / 手机),模板卡片、输入区布局自动调整,确保移动端可用性。
  4. 数据安全隔离:所有用户数据(海报记录、对话)通过外键关联用户 ID,接口层强制过滤当前用户数据,防止越权访问。

4.3 存在的挑战和解决方案

问题一:图片资源访问限制与解决方案

一、问题背景

在使用“扣子工作流”处理图片时,系统对图片资源的访问存在明确限制:仅支持通过网络地址(URL)访问图片资源。然而,在开发环境中,由于开发者通常未部署正式服务器,上传的图片地址多为本地路径(如本地文件路径或本地服务地址),这些地址不具备公网访问能力,因此无法被“扣子工作流”识别和使用,从而影响业务流程的正常运行。

二、解决方案

为解决上述问题,可采取以下两种方案:

(一)临时过渡方案:使用第三方图片托管服务

在开发阶段,可临时采用第三方图片托管服务作为解决方案。例如,使用 freeimage.host 等服务,通过其提供的 API 接口,将本地图片上传至第三方服务器,由其托管并生成可公网访问的图片 URL。此方式可快速解决图片资源无法被“扣子工作流”识别的问题,保障开发与测试阶段的流程顺畅。

(二)项目化正式方案

根据项目规模和需求,可选择以下长期解决方案,以确保图片资源的稳定存储、高效访问及业务扩展性:

小型项目
采用 静态文件服务 + Nginx 方案,适合小型项目,满足基本的图片存储与访问需求。

中型项目
采用 云存储服务,如 AWS S3 或 阿里云 OSS,提供稳定、高效的存储与访问能力。

大型项目
采用 云存储 + CDN 加速方案,结合内容分发网络(CDN)提升访问速度与稳定性,适用于大规模项目。

问题二

  • 问题背景:
    前端需要上传图片到第三方服务(freeimage.host),但直接调用会遇到浏览器CORS跨域限制,严重影响用户体验。

  • 解决方案

①架构流程:

前端 ---> 本地代理服务器 ---> 第三方图片托管服务(freeimage.host) ---> 返回公网URL

②原理解释:

浏览器直接发送请求到freeimage存在跨域问题,改用浏览器发送请求到代理服务器,代理服务器和freeimage直接实现端对端连接,解决了跨越问题。

问题三

一、问题背景:

生成海报的记录以及聊天会话记录的用户隔离和长久保存,需要支持多轮对话,保持上下文信息,同时要高效管理大量的会话数据。

二、解决方案

①数据模型设计:

User和ChatSession以及ChatMessage的关联设计,UUID确保会话唯一性,软删除和数据归档策略。

问题四

  • 问题背景:

需要同时集成Coze智能体和Coze工作流和豆包两个不同的AI平台,它们的API调用方式、参数格式、响应结构都完全不同。

  • 解决方案

①模块化设计架构

为每个AI服务创建独立工具类,CozeTool.py:封装Coze工作流和聊天机器人,doubaoTool.py:封装豆包图像编辑API。

问题五

  • 问题背景:

AI生成海报和聊天对话都需要实时反馈,传统的请求-响应模式无法满足用户体验要求。

二、解决方案

①流式响应架构:

使用StreamingHttpResponse实现流式输出,Server-Sent Events (SSE) 数据格式。

②连接管理优化:

连接超时和心跳机制,客户端断线检测,资源自动清理。

问题一:图片资源访问限制与解决方案

一、问题背景

在使用“扣子工作流”处理图片时,系统对图片资源的访问存在明确限制:仅支持通过网络地址(URL)访问图片资源。然而,在开发环境中,由于开发者通常未部署正式服务器,上传的图片地址多为本地路径(如本地文件路径或本地服务地址),这些地址不具备公网访问能力,因此无法被“扣子工作流”识别和使用,从而影响业务流程的正常运行。

二、解决方案

为解决上述问题,可采取以下两种方案:

(一)临时过渡方案:使用第三方图片托管服务

在开发阶段,可临时采用第三方图片托管服务作为解决方案。例如,使用 freeimage.host 等服务,通过其提供的 API 接口,将本地图片上传至第三方服务器,由其托管并生成可公网访问的图片 URL。此方式可快速解决图片资源无法被“扣子工作流”识别的问题,保障开发与测试阶段的流程顺畅。

(二)项目化正式方案

根据项目规模和需求,可选择以下长期解决方案,以确保图片资源的稳定存储、高效访问及业务扩展性:

小型项目
采用 静态文件服务 + Nginx 方案,适合小型项目,满足基本的图片存储与访问需求。

中型项目
采用 云存储服务,如 AWS S3 或 阿里云 OSS,提供稳定、高效的存储与访问能力。

大型项目
采用 云存储 + CDN 加速方案,结合内容分发网络(CDN)提升访问速度与稳定性,适用于大规模项目。

问题二

  • 问题背景:
    前端需要上传图片到第三方服务(freeimage.host),但直接调用会遇到浏览器CORS跨域限制,严重影响用户体验。

  • 解决方案

①架构流程:

前端 ---> 本地代理服务器 ---> 第三方图片托管服务(freeimage.host) ---> 返回公网URL

②原理解释:

浏览器直接发送请求到freeimage存在跨域问题,改用浏览器发送请求到代理服务器,代理服务器和freeimage直接实现端对端连接,解决了跨越问题。

问题三

一、问题背景:

生成海报的记录以及聊天会话记录的用户隔离和长久保存,需要支持多轮对话,保持上下文信息,同时要高效管理大量的会话数据。

二、解决方案

①数据模型设计:

User和ChatSession以及ChatMessage的关联设计,UUID确保会话唯一性,软删除和数据归档策略。

问题四

  • 问题背景:

需要同时集成Coze智能体和Coze工作流和豆包两个不同的AI平台,它们的API调用方式、参数格式、响应结构都完全不同。

  • 解决方案

①模块化设计架构

为每个AI服务创建独立工具类,CozeTool.py:封装Coze工作流和聊天机器人,doubaoTool.py:封装豆包图像编辑API。

问题五

  • 问题背景:

AI生成海报和聊天对话都需要实时反馈,传统的请求-响应模式无法满足用户体验要求。

二、解决方案

①流式响应架构:

使用StreamingHttpResponse实现流式输出,Server-Sent Events (SSE) 数据格式。

②连接管理优化:

连接超时和心跳机制,客户端断线检测,资源自动清理。

5. 未来展望

5.1 功能扩展

  1. AI 风格推荐:基于用户历史生成记录,推荐相似风格的模板或提示词(如 “您之前喜欢科技感风格,推荐以下模板”)。
  2. 海报协作:支持多用户共同编辑一张海报(如设计师生成初稿,运营添加 Logo),通过 WebSocket 实现实时同步。
  3. 风格迁移:新增 “风格迁移” 功能(如 “将海报转换为梵高画风”),集成更多视觉大模型(如 Stable Diffusion)。
  4. 批量生成:支持批量上传提示词,一次性生成多张海报(如 “生成 10 张不同节日的促销海报”),提升效率。

5.2 性能与体验优化

  1. 缓存策略:对热门模板、用户常用比例的生成结果进行缓存(如 Redis),减少 AI 接口调用次数,提升响应速度。
  2. 图片优化:生成的海报自动压缩(如使用pillow库),降低图片加载时间;支持 WebP 格式,平衡画质与大小。
  3. 离线体验:通过 PWA 技术实现部分离线功能(如查看历史记录、编辑提示词),提升弱网环境可用性。
  4. 操作引导:新增新手引导流程(如 “点击模板→输入提示词→生成海报”),降低新用户学习成本。

5.3 生态和商业化

  1. 模板市场:推出付费模板(如设计师定制模板),支持模板创作者入驻,平台抽成,形成生态闭环。
  2. 会员体系:免费用户限制生成次数,会员用户解锁高清下载、批量生成、高级 AI 模型(如豆包 Pro)等特权。
  3. 企业版功能:针对企业用户推出 “品牌模板库”(统一企业 VI 风格)、“团队协作”、“海报数据统计”(如曝光量、下载量)。
  4. API 开放:将海报生成 / 修改能力封装为开放 API,供第三方平台调用(如电商平台集成海报生成功能),实现商业化变现。

5.4 技术探索

  1. 端侧 AI 集成:探索在前端集成轻量化 AI 模型(如 TensorFlow.js),实现简单的海报风格预览(如 “本地预览科技感风格”),减少服务器依赖。

  2. 多模态输入:支持语音输入提示词(集成科大讯飞语音识别)、手绘草图生成海报(如用户手绘布局,AI 生成完整海报),扩展输入方式。
  3. AI 自动化:实现 “一键生成全套海报”,用户输入活动主题(如 “618 促销”),AI 自动生成不同平台(小红书、朋友圈、电商)的海报,无需人工调整。

6. 总结和收获

6.1 总结

本项目基于 Django+Vue3 构建了 “AI 驱动的海报创作平台”,聚焦海报 “生成→修改→管理” 的全流程需求,通过 Coze 和豆包 AI 服务降低创作门槛,通过响应式设计和流式交互提升用户体验,通过数据隔离确保用户数据安全。项目不仅实现了核心功能,还通过分层设计(如后端services、前端api)提升了代码可维护性,为后续扩展奠定基础。

6.2 收获

  1. 前后端协作能力:深入理解前后端分离架构下的数据流向(如前端请求→后端视图→AI 服务→数据存储→前端渲染),掌握 Axios 请求封装、DRF 视图设计的最佳实践。
  2. AI API 集成经验:熟悉 Coze、豆包等 AI 平台的 API 调用方式,理解工作流、智能体、视觉大模型的应用场景,学会处理 API 异常和跨域问题。
  3. 用户体验设计意识:从 “流式响应”“模板辅助”“错误提示” 等细节出发,体会 “技术服务于体验” 的理念,提升产品化思维。
  4. 问题解决能力:面对跨域、流式响应、图片上传等挑战,通过查阅文档(Django/Coze API)、调试代码(如流式响应的 SSE 格式),培养独立解决技术问题的能力。

通过本项目,不仅掌握了 AI 驱动的 Web 应用开发流程,还意识到 “功能实现” 与 “用户体验”“可扩展性” 的平衡,为后续复杂项目开发积累了宝贵经验。


网站公告

今日签到

点亮在社区的每一天
去签到