当前位置:   article > 正文

零基础手把手教你如何使用Laf免费玩转Midjourney

零基础手把手教你如何使用Laf免费玩转Midjourney

一、什么是Laf?

Laf 是一个 Serverless 框架,提供开箱即用的云函数,云数据库,对象存储等能力,是一个非常干净清爽的开发平台,不仅入门简单,还能像写博客一样写代码!最重要的是,敲重点,三分钟即可上线 ChatGPT 应用

若想深入了解可点击 介绍 | laf 云开发https://doc.laf.run/guide/

二、入门Laf开发应用前的准备

在正式开发应用前需要先进行账号注册和新建应用,具体操作方法可访问Web IDE | laf 云开发https://doc.laf.run/guide/web-ide/

说明:下面讲解的Midjourney需要访问 laf.dev 域名进行注册

三、正式接入Midjourney

在应用列表点击「开发」按钮即可进入 Laf 应用开发 IDE

直接Web IDE在线开发,连安装软件的工作都省了,不得不说,十分亲民

 1.添加NPM依赖

点击左下角【NPM依赖】处的“+”按钮,在弹框中搜索“midjourney”,选中第一个后再点击“保存并重启”,等待3秒左右依赖会添加完成

 2.添加函数

点击左上角【函数列表】处的“+”按钮,在弹框中输入函数名(比如:mj-func),其他默认,完成后点击“确认”按钮,等待3秒左右函数会添加完成

408a392b3eda4244bc18f5f475cdbec2.png

说明:关于云函数的入门,可点击下面链接了解详情云函数入门 | laf 云开发https://doc.laf.run/guide/function/

3.初始化Midjourney

上面已经添加了midjourney的依赖,接着需要在刚刚添加的函数中引入midjourney,并进行初始化

  1. import { Midjourney, MidjourneyMessage } from 'midjourney'
  2. const SERVER_ID = '' // Midjourney 服务 ID
  3. const CHANNEL_ID = '' // Midjourney 频道 ID
  4. const SALAI_TOKEN = '' // Midjourney 服务 Token
  5. const Limit = 100
  6. const MaxWait = 3
  7. //初始化
  8. const client = new Midjourney({
  9. ServerId: SERVER_ID,
  10. ChannelId: CHANNEL_ID,
  11. SalaiToken: SALAI_TOKEN,
  12. Debug: true,
  13. SessionId: SALAI_TOKEN,
  14. Limit: Limit,
  15. MaxWait: MaxWait
  16. });

其中服务ID、频道ID和服务Token可在 人人都能接入 Midjourney,赚取自己的第一桶金 中找大佬单独获取

4.生成图片

“export default async function”可以理解成函数的主入口,在其方法内我们可以根据传入的参数去实现不同的业务逻辑

  1. //主入口
  2. export default async function (ctx: FunctionContext) {
  3. const { type, param } = ctx.body
  4. //参数校验
  5. if (!type) {
  6. return resultData(0, '参数type不能为空!')
  7. }
  8. switch (type) {
  9. case 'imagine':
  10. //生成图片
  11. return await imagine(param)
  12. }
  13. }

下面为【生成图片】的具体实现,需要question和msg_Id两个参数

question:其实就是prompt(提示),需要输入英文

msg_Id:可以理解成会话ID

  1. // 创建生图任务
  2. async function imagine(param) {
  3. console.log("imagine", param)
  4. const { question, msg_Id } = param
  5. //参数校验
  6. if (!question || !msg_Id) {
  7. return resultData(0, '参数question或msg_Id不能为空!')
  8. }
  9. try {
  10. const obj = await client.Imagine(
  11. `[${msg_Id}] ${question}`,
  12. (uri: string, progress: string) => {
  13. console.log("loading", uri, "progress", progress);
  14. }
  15. );
  16. console.log("imagine success ", obj)
  17. return resultData(200, 'imagine success', obj)
  18. }
  19. catch (e) {
  20. return resultData(0, '参数错误!', e)
  21. }
  22. }

 为了让前端在调用接口时接收到的数据风格统一化,故这里在返回数据时进行了统一的封装处理

  1. //返回结果数据
  2. async function resultData(code = 0, msg = '', data = null) {
  3. return { code, msg, data }
  4. }

以上完成后可点击上方的“发布”按钮,等待3秒左右,复制“发布”按钮旁的链接,即可在apipost或者前端中进行调用,如下图:

  1. {
  2. "type": "imagine",
  3. "param": {
  4. "question": "a beautiful girl",
  5. "msg_Id": "20230523_G001"
  6. }
  7. }

说明:在返回的结果数据中code为200时表示请求成功,data为null不用理会,请求成功后大约等待30秒到一分钟左右的时间, Midjourney会自动在后台生成我们想要的图片,届时我们再调用【查询最近消息】的接口获取生成的图片。

另外,除了通过apipost或postman调用接口,我们还可以使用Web IDE自带的接口调试进行调用,不仅可以清晰的看到运行的结果,还能在控制台打印出我们想要的日志信息,十分的便利,如下图:

5.查询最近消息

在主入口方法中增加【查询最近消息】的业务逻辑

  1. //主入口
  2. export default async function (ctx: FunctionContext) {
  3. const { type, param } = ctx.body
  4. //参数校验
  5. if (!type) {
  6. return resultData(0, '参数type不能为空!')
  7. }
  8. switch (type) {
  9. case 'retrieveMessages':
  10. //查询最近消息
  11. return await retrieveMessages(param)
  12. case 'imagine':
  13. //生成图片
  14. return await imagine(param)
  15. }
  16. }

下面为【查询最近消息】的具体实现,由于默认查询的是所有用户最近50条的消息,所以这里根据msg_Id进行了数据筛选(若想查询所有的可以去掉这个限制)

msg_Id:会话ID(需与生成图片时传的msg_Id一致)

  1. // 查询消息(最近50条)
  2. async function retrieveMessages(param) {
  3. console.log("retrieveMessages", param)
  4. const { msg_Id } = param
  5. //参数校验
  6. if (!msg_Id) {
  7. return resultData(0, '参数msg_Id不能为空!')
  8. }
  9. try {
  10. const client = new MidjourneyMessage({
  11. ChannelId: CHANNEL_ID,
  12. SalaiToken: SALAI_TOKEN,
  13. });
  14. const obj = await client.RetrieveMessages();
  15. console.log("retrieveMessages success ", obj)
  16. //解析返回的list数据,获取图片信息
  17. if (obj) {
  18. //查找满足当前会话的消息
  19. let info = obj.find(x => x.content.indexOf(msg_Id) > -1);
  20. //校验是否完成了图片的生成,条件:attachments节点下的第一个子对象,且width大于512
  21. if (info && info.attachments && info.attachments.length > 0 && info.attachments[0].width > 512) {
  22. let d = info.attachments[0];
  23. //调用云函数获取网络图片并存储
  24. const res = await cloud.invoke('store-func', {
  25. body: {
  26. type: 'fetchImg',
  27. param: {
  28. fileName: d.filename,
  29. fileUrl: d.url,
  30. contentType: d.content_type
  31. }
  32. }
  33. });
  34. if (res.code == 200) {
  35. d.pic = res.data
  36. res.data = d
  37. }
  38. return res;
  39. }
  40. }
  41. return resultData(0, '未获取到数据!')
  42. }
  43. catch (e) {
  44. return resultData(0, '参数错误!', e)
  45. }
  46. }

其中,这里“调用云函数获取网络图片并存储”是写在另一个云函数中,所以需要另外再创建一个云函数“store-func”,具体实现如下:

  1. import cloud from "@lafjs/cloud";
  2. import { GetObjectCommand, S3 } from "@aws-sdk/client-s3";
  3. import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
  4. //初始化
  5. const s3Client = new S3({
  6. endpoint: process.env.OSS_EXTERNAL_ENDPOINT,
  7. region: process.env.OSS_REGION,
  8. credentials: {
  9. accessKeyId: process.env.OSS_ACCESS_KEY,
  10. secretAccessKey: process.env.OSS_ACCESS_SECRET
  11. },
  12. forcePathStyle: true,
  13. })
  14. //存储空间名称,不带 Laf 应用 appid
  15. const bucketName = 'store-file'
  16. //主入口
  17. export default async function (ctx: FunctionContext) {
  18. const { type, param } = ctx.body
  19. //参数校验
  20. if (!type) {
  21. return resultData(0, '参数type不能为空!')
  22. }
  23. switch (type) {
  24. case 'fetchImg':
  25. //存储网络图片
  26. return await fetchImgToStore(param)
  27. }
  28. }
  29. //获取网络图片并存储
  30. async function fetchImgToStore(param) {
  31. console.log("fetchImgToStore", param);
  32. const { fileName, fileUrl, contentType } = param
  33. //参数校验
  34. if (!fileName || !fileUrl || !contentType) {
  35. return resultData(0, '参数fileName、fileUrl或contentType不能为空!')
  36. }
  37. try {
  38. //下载url图片二进制数据
  39. const ret = await cloud.fetch({
  40. url: fileUrl,
  41. method: "get",
  42. responseType: "arraybuffer"
  43. });
  44. // console.log(ret.data);
  45. if (ret.data) {
  46. //图片存储
  47. const res = await uploadAppFile(fileName, ret.data, contentType);
  48. console.log('文件存储结果:', res)
  49. if (res && res.$metadata && res.$metadata.httpStatusCode == 200) {
  50. //获取图片存储的绝对路径
  51. const fileUrl = await getAppFileUrl(fileName);
  52. return resultData(200, '成功', fileUrl)
  53. }
  54. return resultData(0, '文件存储失败!')
  55. }
  56. return resultData(0, '获取网络图片失败!')
  57. }
  58. catch (e) {
  59. return resultData(0, '出现异常!', e)
  60. }
  61. }
  62. //拼接文件桶名字
  63. function getInternalBucketName() {
  64. const appid = process.env.APP_ID;
  65. return `${appid}-${bucketName}`;
  66. }
  67. //上传文件
  68. async function uploadAppFile(key, body, contentType) {
  69. const bucket = getInternalBucketName();
  70. const res = await s3Client
  71. .putObject({
  72. Bucket: bucket,
  73. Key: key,
  74. ContentType: contentType,
  75. Body: body,
  76. })
  77. return res;
  78. }
  79. //获取文件 url
  80. async function getAppFileUrl(key) {
  81. const bucket = getInternalBucketName();
  82. const res = await getSignedUrl(s3Client, new GetObjectCommand({
  83. Bucket: bucket,
  84. Key: key,
  85. }));
  86. return res;
  87. }
  88. //返回结果数据
  89. async function resultData(code = 0, msg = '', data = null) {
  90. return { code, msg, data }
  91. }

这里涉及到了云函数的调用,以及云存储,详细资料可在下面链接中进行查阅云存储简介 | laf 云开发https://doc.laf.run/guide/oss/

以上完成后可点击上方的“发布”按钮,等待3秒左右,复制“发布”按钮旁的链接,即可在apipost或者前端中进行调用,如下图:

  1. {
  2. "type": "retrieveMessages",
  3. "param": {
  4. "msg_Id": "20230523_G001"
  5. }
  6. }

 返回的data中pic的链接即为生成的图片

 

 6.放大图片

在主入口方法中增加【放大图片】的业务逻辑

  1. //主入口
  2. export default async function (ctx: FunctionContext) {
  3. const { type, param } = ctx.body
  4. //参数校验
  5. if (!type) {
  6. return resultData(0, '参数type不能为空!')
  7. }
  8. switch (type) {
  9. case 'retrieveMessages':
  10. //查询最近消息
  11. return await retrieveMessages(param)
  12. case 'imagine':
  13. //生成图片
  14. return await imagine(param)
  15. case 'upscale':
  16. //放大图片
  17. return await upscale(param)
  18. }
  19. }

下面为【放大图片】的具体实现,需要question、index、id和url四个参数

question:与生成图片时的参数,需要输入英文

index:你想要的第几张图(默认生成的4张图,按1234的顺序)

id:查询最近消息接口返回的数据体中的id

url:查询最近消息接口返回的数据体中attachments节点下的url

  1. // upscale 放大图片
  2. async function upscale(param) {
  3. console.log("upscale", param)
  4. const { question, index, id, url } = param
  5. //参数校验
  6. if (!question || !index || !id || !url) {
  7. return resultData(0, '参数question、index、id或url不能为空!')
  8. }
  9. try {
  10. const hash = url.split("_").pop()?.split(".")[0] ?? ""
  11. const obj = await client.Upscale(
  12. question,
  13. index,
  14. id,
  15. hash,
  16. (uri: string, progress: string) => {
  17. console.log("loading", uri, "progress", progress);
  18. }
  19. );
  20. console.log("upscale success ", obj)
  21. return resultData(200, 'upscale success', obj)
  22. }
  23. catch (e) {
  24. return resultData(0, '参数错误!', e)
  25. }
  26. }

以上完成后可点击上方的“发布”按钮,等待3秒左右,复制“发布”按钮旁的链接,即可在apipost或者前端中进行调用,如下图:

  1. {
  2. "type": "upscale",
  3. "param": {
  4. "question": "a beautiful girl",
  5. "index": 2,
  6. "id": "1110429562023723048",
  7. "url": "https://cdn.discordapp.com/attachments/1110206663958466611/1110429561428135976/johnsonmaureen_20230523_G001_a_beautiful_girl_1925266c-7937-4b8d-8150-c16f013d0dca.png"
  8. }
  9. }

成功后,大约等待30秒到一分钟,重新再调用一下【查询最近消息】的接口,获取可访问的放大后的图片

或者,也可以在【放大图片】的接口中将新生成的图片进行云存储后再返回给前端,两种方式都可以

7.变换图片

在主入口方法中增加【变换图片】的业务逻辑

  1. //主入口
  2. export default async function (ctx: FunctionContext) {
  3. const { type, param } = ctx.body
  4. //参数校验
  5. if (!type) {
  6. return resultData(0, '参数type不能为空!')
  7. }
  8. switch (type) {
  9. case 'retrieveMessages':
  10. //查询最近消息
  11. return await retrieveMessages(param)
  12. case 'imagine':
  13. //生成图片
  14. return await imagine(param)
  15. case 'upscale':
  16. //放大图片
  17. return await upscale(param)
  18. case 'variation':
  19. //变换图片
  20. return await variation(param)
  21. }
  22. }

下面为【变换图片】的具体实现,需要question、index、id和url四个参数

question:与生成图片时的参数,需要输入英文

index:你想要的第几张图(默认生成的4张图,按1234的顺序)

id:查询最近消息接口返回的数据体中的id

url:查询最近消息接口返回的数据体中attachments节点下的url

  1. // variation 变换图片
  2. async function variation(param) {
  3. console.log("variation", param)
  4. const { question, index, id, url } = param
  5. //参数校验
  6. if (!question || !index || !id || !url) {
  7. return resultData(0, '参数question、index、id或url不能为空!')
  8. }
  9. try {
  10. const hash = url.split("_").pop()?.split(".")[0] ?? ""
  11. const obj = await client.Variation(
  12. question,
  13. index,
  14. id,
  15. hash,
  16. (uri: string, progress: string) => {
  17. console.log("loading", uri, "progress", progress);
  18. }
  19. );
  20. console.log("variation success ", obj)
  21. return resultData(200, 'variation success', obj)
  22. }
  23. catch (e) {
  24. return resultData(0, '参数错误!', e)
  25. }
  26. }

以上完成后可点击上方的“发布”按钮,等待3秒左右,复制“发布”按钮旁的链接,即可在apipost或者前端中进行调用,如下图:

  1. {
  2. "type": "variation",
  3. "param": {
  4. "question": "a beautiful girl",
  5. "index": 3,
  6. "id": "1110429562023723048",
  7. "url": "https://cdn.discordapp.com/attachments/1110206663958466611/1110429561428135976/johnsonmaureen_20230523_G001_a_beautiful_girl_1925266c-7937-4b8d-8150-c16f013d0dca.png"
  8. }
  9. }

 说明:在返回的结果数据中code为200时表示请求成功,data为null不用理会,请求成功后大约等待30秒到一分钟左右的时间, Midjourney会自动在后台重新生成我们想要的图片,届时我们再调用【查询最近消息】的接口获取变换后的图片。

8.关于云存储

如使用云存储,需在云存储中新建Bucket,如下图:

d6b83fef597d4fd48986461d61b24a41.png

下面为云函数mj-func的完整代码:

  1. import cloud from '@lafjs/cloud'
  2. import { Midjourney, MidjourneyMessage } from 'midjourney'
  3. const SERVER_ID = '' // Midjourney 服务 ID
  4. const CHANNEL_ID = '' // Midjourney 频道 ID
  5. const SALAI_TOKEN = '' // Midjourney 服务 Token
  6. const Limit = 100
  7. const MaxWait = 3
  8. //初始化
  9. const client = new Midjourney({
  10. ServerId: SERVER_ID,
  11. ChannelId: CHANNEL_ID,
  12. SalaiToken: SALAI_TOKEN,
  13. Debug: true,
  14. SessionId: SALAI_TOKEN,
  15. Limit: Limit,
  16. MaxWait: MaxWait
  17. });
  18. //主入口
  19. export default async function (ctx: FunctionContext) {
  20. const { type, param } = ctx.body
  21. //参数校验
  22. if (!type) {
  23. return resultData(0, '参数type不能为空!')
  24. }
  25. switch (type) {
  26. case 'retrieveMessages':
  27. //查询最近消息
  28. return await retrieveMessages(param)
  29. case 'imagine':
  30. //生成图片
  31. return await imagine(param)
  32. case 'upscale':
  33. //放大图片
  34. return await upscale(param)
  35. case 'variation':
  36. //变换图片
  37. return await variation(param)
  38. }
  39. }
  40. // 查询消息(最近50条)
  41. async function retrieveMessages(param) {
  42. console.log("retrieveMessages", param)
  43. const { msg_Id } = param
  44. //参数校验
  45. if (!msg_Id) {
  46. return resultData(0, '参数msg_Id不能为空!')
  47. }
  48. try {
  49. const client = new MidjourneyMessage({
  50. ChannelId: CHANNEL_ID,
  51. SalaiToken: SALAI_TOKEN,
  52. });
  53. const obj = await client.RetrieveMessages();
  54. console.log("retrieveMessages success ", obj)
  55. //解析返回的list数据,获取图片信息
  56. if (obj) {
  57. //查找满足当前会话的消息
  58. let info = obj.find(x => x.content.indexOf(msg_Id) > -1);
  59. //校验是否完成了图片的生成,条件:attachments节点下的第一个子对象,且width大于512
  60. if (info && info.attachments && info.attachments.length > 0 && info.attachments[0].width > 512) {
  61. let d = info.attachments[0];
  62. //调用云函数获取网络图片并存储
  63. const res = await cloud.invoke('store-func', {
  64. body: {
  65. type: 'fetchImg',
  66. param: {
  67. fileName: d.filename,
  68. fileUrl: d.url,
  69. contentType: d.content_type
  70. }
  71. }
  72. });
  73. if (res.code == 200) {
  74. info.pic = res.data
  75. res.data = info
  76. }
  77. return res;
  78. }
  79. }
  80. return resultData(0, '未获取到数据!')
  81. }
  82. catch (e) {
  83. return resultData(0, '参数错误!', e)
  84. }
  85. }
  86. // 创建生图任务
  87. async function imagine(param) {
  88. console.log("imagine", param)
  89. const { question, msg_Id } = param
  90. //参数校验
  91. if (!question || !msg_Id) {
  92. return resultData(0, '参数question或msg_Id不能为空!')
  93. }
  94. try {
  95. const obj = await client.Imagine(
  96. `[${msg_Id}] ${question}`,
  97. (uri: string, progress: string) => {
  98. console.log("loading", uri, "progress", progress);
  99. }
  100. );
  101. console.log("imagine success ", obj)
  102. return resultData(200, 'imagine success', obj)
  103. }
  104. catch (e) {
  105. return resultData(0, '参数错误!', e)
  106. }
  107. }
  108. // upscale 放大图片
  109. async function upscale(param) {
  110. console.log("upscale", param)
  111. const { question, index, id, url } = param
  112. //参数校验
  113. if (!question || !index || !id || !url) {
  114. return resultData(0, '参数question、index、id或url不能为空!')
  115. }
  116. try {
  117. const hash = url.split("_").pop()?.split(".")[0] ?? ""
  118. const obj = await client.Upscale(
  119. question,
  120. index,
  121. id,
  122. hash,
  123. (uri: string, progress: string) => {
  124. console.log("loading", uri, "progress", progress);
  125. }
  126. );
  127. console.log("upscale success ", obj)
  128. return resultData(200, 'upscale success', obj)
  129. }
  130. catch (e) {
  131. return resultData(0, '参数错误!', e)
  132. }
  133. }
  134. // variation 变换图片
  135. async function variation(param) {
  136. console.log("variation", param)
  137. const { question, index, id, url } = param
  138. //参数校验
  139. if (!question || !index || !id || !url) {
  140. return resultData(0, '参数question、index、id或url不能为空!')
  141. }
  142. try {
  143. const hash = url.split("_").pop()?.split(".")[0] ?? ""
  144. const obj = await client.Variation(
  145. question,
  146. index,
  147. id,
  148. hash,
  149. (uri: string, progress: string) => {
  150. console.log("loading", uri, "progress", progress);
  151. }
  152. );
  153. console.log("variation success ", obj)
  154. return resultData(200, 'variation success', obj)
  155. }
  156. catch (e) {
  157. return resultData(0, '参数错误!', e)
  158. }
  159. }
  160. //返回结果数据
  161. async function resultData(code = 0, msg = '', data = null) {
  162. return { code, msg, data }
  163. }

 以上即为使用Laf接入Midjourney的完整演示,如有疑问,欢迎提出!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号