Midjourney辅助绘画工具:基于Discord网关的批量任务调度实战 简介面向AI绘画工具开发与应用人群的《基于MIDJOURNEY的AI辅助绘画工具设计与实现》PDF文档针对MidJourney操作复杂、上手门槛高等痛点提出基于Spring Boot、Redis、MySQL与RabbitMQ的系统架构并引入SparkDesk与CRISPE框架优化Prompt适合中高级开发者、科研人员及对AIGC绘画平台搭建感兴趣的读者参考。资源为单文件PDF共945KB内容为完整论文版面包含摘要、关键技术与架构介绍、系统实现与应用、总结展望等章节图文结构清晰便于快速阅读与引用。已有149人学习/下载。通过该文档可获得AI辅助绘画平台的整体设计方案包括Spring Boot后端搭建思路、RedisMySQL数据存储策略、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 ReactDOM 结构随版本更新频繁变动你写死的选择器一升级就失效更重要的界面自动化无法稳定处理 Midjourney 图片生成完成后的消息回执。生成过程中可能出现中途图片进度图、文字说明、按钮组件回调结构复杂脚本会读错状态。这套工具的做法是另一条思路——直接对接 Discord 的内部网关协议。Discord 有标准的 WebSocket 网关用于收发消息、事件上报。Midjourney 机器人本质上是 Discord 用户它会以「消息」形式向频道推送结果图链接。辅助工具在本地模拟一个 Discord 客户端通过与网关建立长连接把/imagine指令作为真实用户消息发送出去同时监听目标频道的消息事件从中提取图片 URL。整个过程不触碰界面 DOM界面怎么改都与你无关。这里做决策时有三个考虑第一网关协议是 Discord 多年保留的稳定接口相对界面 DOM 变动小得多第二发送消息与接收消息都是结构化数据解析可靠第三所有操作本地化封装后续做任务队列、批量生成都只是数据处理问题。2.2 工具的整体架构与消息流转路径这套工具按「前端 UI 后端任务编排 网关适配层」三层组织。前端负责提示词编辑器、任务面板、图库预览后端维护一个并发受控的任务队列把用户发起的批次请求包装成任务对象限速后逐个投递给网关适配层适配层专门处理与 Midjourney 机器人交互的细节比如/imagine的完整参数拼接、验证码反馈事件、降级时间等待。消息流转路径我用一张简表说明阶段组件具体职责1提示词编辑器按预设模板组合用户输入的描述文本与参数如宽高比、模型版本2任务队列控制并发上限默认单并发等待前序完成再发下一个3网关适配层封装 Discord 网关登录状态、心跳、消息体构造与发送4结果监听器过滤频道内消息识别 Midjourney 机器人汇报的完成事件提取图片 URL5本地图库调用系统下载组件拉取原图生成缩略图记录生成参数供检索核心的封装实现在适配层用 Python 作为主力语言这里给出一个简化版的消息发送核心类方便理解这套工具的消息组织方式。import json import time import threading import websocket class GatewayClient: Discord 网关客户端负责维护连接与消息发送 def __init__(self, token, intents513): 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/?v9encodingjson) 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后会先回一条“已受理”的状态消息图片在几十秒后才到。结果监听器必须按消息引用的 tagMidjourney 用消息 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[i1].startswith(--): params[key] tokens[i1] 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_seconds20): 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(timeout1) 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(parentsTrue, exist_okTrue) resp requests.get(attachment_url, timeout30, streamTrue) file_ext Path(attachment_url).suffix or .png # URL 中往往带 hash 尾缀提取末尾 8 位作为临时标识 short_id attachment_url.split(/)[-1][:8] local_path Path(download_dir) / fmj_{short_id}{file_ext} with open(local_path, wb) as f: for chunk in resp.iter_content(chunk_size8192): 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 文件而不是硬编码在代码里。这样做的好处是二次部署时无需改动代码只需要复制配置模板并按环境修改。工具初始化时会校验所有配置项的存在性与取值范围配置缺失时给出警告并在默认值基础上运行避免配置错了导致整个工具无法启动。我自己的使用流程里每次跑新批次前会固定检查三样东西频道的当前活动状态、队列里的间隔设置、以及本地磁盘的剩余空间。只有这三项都确认没有问题才会点下批量生成的按钮。这套工具到后面越来越像一个生产流水线上的控制台你必须尊重它运行的每一个前提条件它才能稳定产出可用的图片。希望帮到你。本文还有配套的精品资源点击获取