简介:面向AI绘画工具开发与应用人群的《基于MIDJOURNEY的AI辅助绘画工具设计与实现》PDF文档,针对MidJourney操作复杂、上手门槛高等痛点,提出基于Spring Boot、Redis、MySQL与RabbitMQ的系统架构,并引入SparkDesk与CRISPE框架优化Prompt,适合中高级开发者、科研人员及对AIGC绘画平台搭建感兴趣的读者参考。资源为单文件PDF,共945KB,内容为完整论文版面,包含摘要、关键技术与架构介绍、系统实现与应用、总结展望等章节,图文结构清晰,便于快速阅读与引用。已有149人学习/下载。通过该文档可获得AI辅助绘画平台的整体设计方案,包括Spring Boot后端搭建思路、Redis+MySQL数据存储策略、RabbitMQ异步任务解耦方法,以及CRISPE框架提升提示词质量的实践细节,可作为课程设计、毕业设计或项目落地的技术参考资料。
1. 从 Midjourney 到完整工作流:为什么你需要一个辅助工具
天天泡在各类 AI 绘画平台里的人应该都有同感:Midjourney 单独用并不难——发个/imagine,等几十秒,出图。但当你真正拿它投入生产,比如给一套电商详情页配 20 张风格统一的图,或想拿同一组提示词在不同风格间做对比,麻烦就来了:提示词散落在聊天记录里,参数全靠手动输,批次生成全靠一张张点,图片管理更是灾难。我这次拆的资源,就是一套「基于 Midjourney 的 AI 辅助绘画工具」的完整设计与实现方案。它不属于某一套代码, 而是工程实践产物:一个用于 Midjourney 可视化管理、提示词生成、批量任务调度以及生成结果筛选回流的本地辅助工具。工具本身基于跨平台桌面框架开发,集成 Discord 网关协议与 Midjourney 任务处理逻辑,为频繁使用 Midjourney 的从业者省去重复劳动。适合独立开发者、设计工作室和 AI 绘画重度用户拿去改造,或者作为侧载工具嵌入现有的内容创作管线中。
2. 辅助工具的本质:把 Midjourney 的对话式交互改造成任务队列
2.1 为什么用「网关协议」而不是界面自动化
很多开发者听我说 Midjourney 没有开放 API,第一反应是用 pyautogui 做屏幕自动化:模拟鼠标点击、键盘输入,调用 Discord 界面上的输入框。这方案有人做过,翻车概率极高。原因有两点:Discord 前端是 Electron + React,DOM 结构随版本更新频繁变动,你写死的选择器一升级就失效;更重要的,界面自动化无法稳定处理 Midjourney 图片生成完成后的消息回执。生成过程中可能出现中途图片(进度图)、文字说明、按钮组件,回调结构复杂,脚本会读错状态。
这套工具的做法是另一条思路——直接对接 Discord 的内部网关协议。Discord 有标准的 WebSocket 网关,用于收发消息、事件上报。Midjourney 机器人本质上是 Discord 用户,它会以「消息」形式向频道推送结果图链接。辅助工具在本地模拟一个 Discord 客户端,通过与网关建立长连接,把/imagine指令作为真实用户消息发送出去,同时监听目标频道的消息事件,从中提取图片 URL。整个过程不触碰界面 DOM,界面怎么改都与你无关。
这里做决策时有三个考虑:第一,网关协议是 Discord 多年保留的稳定接口,相对界面 DOM 变动小得多;第二,发送消息与接收消息都是结构化数据,解析可靠;第三,所有操作本地化封装,后续做任务队列、批量生成都只是数据处理问题。
2.2 工具的整体架构与消息流转路径
这套工具按「前端 UI + 后端任务编排 + 网关适配层」三层组织。前端负责提示词编辑器、任务面板、图库预览;后端维护一个并发受控的任务队列,把用户发起的批次请求包装成任务对象,限速后逐个投递给网关适配层;适配层专门处理与 Midjourney 机器人交互的细节,比如/imagine的完整参数拼接、验证码反馈事件、降级时间等待。消息流转路径我用一张简表说明:
| 阶段 | 组件 | 具体职责 |
|---|---|---|
| 1 | 提示词编辑器 | 按预设模板组合用户输入的描述文本与参数(如宽高比、模型版本) |
| 2 | 任务队列 | 控制并发上限(默认单并发,等待前序完成再发下一个) |
| 3 | 网关适配层 | 封装 Discord 网关登录状态、心跳、消息体构造与发送 |
| 4 | 结果监听器 | 过滤频道内消息,识别 Midjourney 机器人汇报的完成事件,提取图片 URL |
| 5 | 本地图库 | 调用系统下载组件拉取原图,生成缩略图,记录生成参数供检索 |
核心的封装实现在适配层,用 Python 作为主力语言,这里给出一个简化版的消息发送核心类,方便理解这套工具的消息组织方式。
import json import time import threading import websocket class GatewayClient: """Discord 网关客户端,负责维护连接与消息发送""" def __init__(self, token, intents=513): self.token = token self.intents = intents self.seq = None # 事件序列号,断线重连时用 self.session_id = None # 会话 ID,断线重连时用 self.ws = None self.event_handlers = {} def connect(self): """建立 WebSocket 连接并完成 Identify 握手""" ws = websocket.WebSocket() ws.connect("wss://gateway.discord.gg/?v=9&encoding=json") self.ws = ws # 先接收 Hello,拿到心跳间隔 hello = json.loads(ws.recv()) heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000 self._start_heartbeat(heartbeat_interval) # 发送 Identify auth_payload = { "op": 2, "d": { "token": self.token, "intents": self.intents, "properties": { "os": "linux", "browser": "chrome", "device": "chrome" } } } ws.send(json.dumps(auth_payload)) # 等 Ready 事件并拿到 session_id while self.session_id is None: msg = json.loads(ws.recv()) if msg.get("t") == "READY": self.session_id = msg["d"]["session_id"] self.seq = msg["s"] break def send_message(self, channel_id, content): """向指定频道发送文本消息""" payload = { "op": 1, "d": { "channel_id": channel_id, "content": content } } self.ws.send(json.dumps(payload))这里有两个参数值得注意:intents 值513是必要的事件订阅集合(GUILD_MESSAGES + DIRECT_MESSAGES),少了就收不到频道消息事件;心跳必须持续运行,默认 41.5 秒一次,如果漏发会被服务端判定离线,连接直接断开。很多初学者在接入网关时反复掉线,多半是心跳线程没起对。
发送消息成功不代表任务已经受理。Midjourney 机器人收到/imagine后会先回一条“已受理”的状态消息,图片在几十秒后才到。结果监听器必须按消息引用的 tag(Midjourney 用消息 ID 记录任务归属)来匹配结果,而不是按顺序,否则并发请求时结果会对不上号。
3. 提示词组织与参数解析:让生成结果可控
3.1 从自由文本到结构化模板的改造
Midjourney 的提示词本身承载两类信息:语义描述与参数指令。大多数人日常使用是手写整段英文再带几个--ar、--v之类的参数,这没问题,但工具化之后必须结构化。原因是如果你想做批量生成——把同一张商品图生成 3 个配色版本,或在 5 组提示词之间做网格测试——手写文本完全无法管理变量。
这套工具的内部做法是把提示词改造成「模板 + 变量槽」的结构。比如商品模特类提示词设计成如下模板:
PROMPT_TEMPLATE = { "ecommerce_fashion": { "subject": "a fashion model wearing {apparel}", "environment": "in {studio_type}, {lighting_setup}", "camera": "shot on {camera_model}, {focal_length}mm lens", "style": "editorial photography style, {color_tone} color grading", "quality": "--quality 2 --style raw --v 6.1" } }用户在 UI 里填的每一个下拉框、输入框的值,都会在提交前拼接到模板中。这样做的好处很明显:承诺一致性场景下,背景和镜头参数不变,只换服装类别,AI 产出的图片风格漂移会大幅降低;出图结果可直接反查出全套参数,方便复盘。
3.2 参数黑匣子:Midjourney 参数解析器的实现
拼接完的提示词看起来已经完整,但要真正在工具里做「自动补全」「参数校验」,你需要一个能从中提取和改写参数的解析器。Midjourney 常用参数包括--aspect、--chaos、--no、--seed、--stylize、--quality,它们的表达形式略有差异——有的是键值对,有的只是单独词条跟在--后。解析器需要兼容各类形式,我这里的实现思路是:拆分 token,找到所有--开头的片段,往前找相邻参数值。
import shlex def parse_mj_params(prompt): """从提示词文本中解析出 Midjourney 参数对""" tokens = shlex.split(prompt) params = {} i = 0 while i < len(tokens): tok = tokens[i] if tok.startswith("--"): key = tok[2:] # 有些参数带值且值已在下一 token,如 --seed 12345 if i + 1 < len(tokens) and not tokens[i+1].startswith("--"): params[key] = tokens[i+1] i += 2 else: # 纯开关类,如 --raw, --niji params[key] = True i += 1 else: i += 1 return params解析器拿到参数后,工具会做三层校验:值域检查(seed 必须整数有界、chaos 必须在 0 到 100 区间)、冲突检查(--quality与--tile不能同用)、修正建议(宽高比与二次元风格不匹配时提示推荐值)。这些校验规则本身来自官方文档汇总和社区使用共识,不算深奥,但放在工具里能明显减少废图率。
这里的参数解析还有个隐蔽细节:--no参数的表达一直很暧昧,官方文档说是排除词,但实际使用中它在某些模型版本下可能与--style产生交互,导致排除不完全。我的做法是在解析器里记录--no对应的内容,并在模板生成时把它转为弱化表达(poorly composed之类),而不是直接塞进--no,至少在 V6 模型下这么做效果更稳定。
4. 批量任务调度与并发控制:从单张出图到批量流水线
4.1 任务队列的核心逻辑与限速策略
Midjourney 官方对并发请求的容忍度很低,同一频道内频繁短时间发多个任务,大概率触发机器人限流或验证码校验。所以这套工具的重要模块是任务队列——你可以在工具里一次排 30 张图,队列负责按安全速率逐张投递。
队列底层设计了一个生产者-消费者模型。用户端的每组提示词会被分解成一个Task对象,包含状态字段:pending、sending、waiting_result、completed、failed。 消费者线程从队列取出待发任务,调用刚才的网关客户端发消息,然后进入等待结果阶段。收到机器人的完成事件后才标记下一轮可发送。
import queue import time class TaskQueue: """带间隔控制的任务队列,确保 Midjourney 请求频率不超限""" def __init__(self, interval_seconds=20): self.queue = queue.Queue() self.interval = interval_seconds self.last_send_time = 0 self.running = False def submit(self, task): self.queue.put(task) def _wait_for_interval(self): """强制等待上一任务发出后所需的最小间隔""" elapsed = time.time() - self.last_send_time if elapsed < self.interval: time.sleep(self.interval - elapsed) def start(self): self.running = True while self.running: try: task = self.queue.get(timeout=1) except queue.Empty: continue self._wait_for_interval() self._send_task(task) self.last_send_time = time.time() self.queue.task_done() def _send_task(self, task): # 发送任务到网关,并注册监听回调 gateway_client.send_message(task.channel_id, task.prompt_text) listener.register(task, self.on_finished) def on_finished(self, task, image_urls): if task.callback: task.callback(image_urls)间隔时间的默认值 20 秒是我在多个频道环境下实测后取的较保守值。实际的限制取决于你所在计划的类型,也受频道内其他用户的影响——如果频道内同时有多个人在发图,你依然贴着间隔发任务,仍然可能触发验证码。建议的做法是:保留队列的间隔配置项,在深夜跑大批量时调低到 15 秒,在白天多人混用频道时调到 30 秒或更高。
4.2 结果匹配与图片下载的回流设计
队列发送完成只是开始,更重要的工作在结果回流。Midjourney 的生成结果会经历两个阶段:第一阶段返回 4 宫格预览图(grid image),第二阶段可选做放大或变体。工具默认只抓第一期结果,得到四宫格图后先下载到本地缓存目录,等待用户在 UI 中挑选。
这里有个必须理解的细节:Midjourney 机器人返回的附件 URL 有时效性,默认在几小时后失效,且只能被 Discord 的 CDN 放行。因此工具在收到结果事件时应第一时间下载原图到本地,而不是只保存链接。下载时机晚于 10 分钟都可能导致原图不可访问。
from pathlib import Path import requests def download_mj_attachment(attachment_url, download_dir="./mj_output"): """下载 Midjourney 返回的图片附件,附加解析出的种子值到文件名""" Path(download_dir).mkdir(parents=True, exist_ok=True) resp = requests.get(attachment_url, timeout=30, stream=True) file_ext = Path(attachment_url).suffix or ".png" # URL 中往往带 hash 尾缀,提取末尾 8 位作为临时标识 short_id = attachment_url.split("/")[-1][:8] local_path = Path(download_dir) / f"mj_{short_id}{file_ext}" with open(local_path, "wb") as f: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) return local_path单 worker 下载对四宫格图够用,但一些图像数量大的场景需要并发下载。工具的改进是引入线程池,限制并发 3 个下载任务,防止大量图片齐头并进导致本地网络打满,代理节点的连接数爆掉也是常见的坑。
5. 避坑与常见问题排查:网关掉线、验证码与图片过期
整个工具跑起来后,你大概率会在某个时间点遇到以下几类问题。我按自己调试过程中踩过的顺序,整理成「现象 → 原因 → 解决」三段式,方便你直接照着对照。
5.1 现象:网关频繁掉线,WebSocket 连接总是几十分钟后断开
原因:当时我对 Discord 网关的心跳机制理解不到位,心跳只发送了两次就不再继续,服务端判定客户端离线,强制断开连接。后来查官方文档才意识到,网关要求每个 interval(约 41 秒)内发送一次心跳,且必须在收到Heartbeat ACK后继续下次计时。我的线程写成了定时器固定 41 秒发一次,与 ACK 无关,一旦网络抖动导致 ACK 迟到,下次心跳就会撞上重连逻辑,产生「连环掉线」。
解决:重写心跳逻辑——每次发完心跳后等待 ACK,收到 ACK 后重新计时。如果超过 5 秒未收到 ACK,主动断开并走重连流程。重连时带上 session_id 与 seq 值,可以恢复事件流,避免丢失结果回调。
5.2 现象:任务被发送,但 Midjourney 机器人一直未响应,队列卡在 waiting_result 状态
原因:当时在自建频道里用机器人测试,忽略了 Midjourney 机器人所在的频道与用户发送频道必须一致。另外,部分频道启用了慢速模式(slow mode),机器人也会拒绝响应。这类问题日志里不会报错,只会表现为「发出去了但杳无音信」。
解决:发消息前先校验频道是否存在、Midjourney 机器人是否在该频道具有读取和响应权限。工具里提供了/info自检命令,发一条消息验证频道活跃状态,再进入正式队列。
5.3 现象:生成结果图片只保存了 URL,几天后再次访问 404
原因:这是一个思路性错误。测试期间以为图片已经落到 Discord CDN 就会长期保留。实际上 Midjourney 机器人附件链接有时效限制,从生成完成时刻开始计算有效期,过期后链接返回 404。
解决:把附件下载时机前置到收到完成事件的瞬间。若工具因故宕机错过了下载窗口,只能回到 Midjourney 频道手动查看历史记录,从记录中复制图片重新下载。在那之后,我的工具里多了一条策略:收到完成事件立即下载,不等用户挑选。
5.4 现象:批量任务跑了一部分,突然收到验证码请求,队列被迫全部中断
原因:Midjourney 对短时间提交频率实行弹性限制。不同套餐的具体阈值不透明,经验值是单账号每 30 秒多于 1 个任务、或者持续一段时间高频提交,就会见到验证码。频道内其他人同时操作也会叠加计数。
解决:为队列增加「拥塞暂停」机制——检测到验证码事件后就地暂停队列,改为每 40 秒发送一次低频探活消息,等待可恢复后再切换回正常间隔。这个过程要避免自动刷屏探测,而是在探活时监听机器人是否有校验消息回传,用回调状态来判断是否解除限制。
5.5 现象:本地图库里图片文件名全是一长串无意义哈希,检索形同虚设
原因:最初设计只是拿末尾短 ID 做了文件名,没考虑提示词与参数的关联。三天后想找出「之前生成过某品牌风格的背景图」时,完全没有检索入口,只能一张张看缩略图碰运气。
解决:重新设计本地图库的元数据存储。每次生成任务都会写一条 JSON 记录,包含完整提示词、参数对、时间戳、图片 URL、种子值。文件名仍是短 ID 前缀,但查询时走 JSON 索引,按关键词、参数组合过滤,效率提升明显。
6. 验证工具质量的有效方法:回归测一套,把历史失败参数当测试集
工具写完后,验证它能不能扛住真实使用是重要环节。常见做法是拿一套历史有效提示词跑一遍流程,但这样只能验证正常路径,作用有限。我验证时会专门收集「曾经翻过车的参数组合」,做成回归测试集:比如--ar 21:9搭配--style raw的某些组合、--chaos 90配合低--stylize值等。把这些参数组合输入队列工具,逐项确认工具能正确解析、发送,并能识别结果或失败状态。有一点可以设计为自动化:本地搭一套 Discord 测试服务器,用机器人模拟 Midjourney 回执事件,构造各种正常与异常的响应消息,再验证工具的事件解析逻辑能输出预期的结构化数据。
验证过程中,参数解析器和提示词模板是重点关注区。建议每完成一轮代码改动就运行一次已有的回归集,确认不会破坏之前能通过的组合。如果你对这套辅助工具的设计实现有进一步改造的意图,可以把网关适配层拆出来当一个独立 Python 包,UI 与队列都通过接口调用,后续即使换前端技术栈也不需要动协议层。
在连续性使用中,我发现最稳定、最耐用的实现习惯是:把整个工具的参数与配置项全部外部化到 YAML 文件,而不是硬编码在代码里。这样做的好处是二次部署时无需改动代码,只需要复制配置模板并按环境修改。工具初始化时会校验所有配置项的存在性与取值范围,配置缺失时给出警告并在默认值基础上运行,避免配置错了导致整个工具无法启动。我自己的使用流程里,每次跑新批次前会固定检查三样东西:频道的当前活动状态、队列里的间隔设置、以及本地磁盘的剩余空间。只有这三项都确认没有问题,才会点下批量生成的按钮。这套工具到后面越来越像一个生产流水线上的控制台,你必须尊重它运行的每一个前提条件,它才能稳定产出可用的图片。希望帮到你。
本文还有配套的精品资源,点击获取