ImageService.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. namespace app\service;
  3. use app\service\AIGatewayService;
  4. use think\Db;
  5. use think\Exception;
  6. use think\Queue;
  7. /**
  8. * ImageService 图像任务服务
  9. * 负责图生文、文生文、文生图、图生图等 AI 任务的队列派发与状态管理
  10. */
  11. class ImageService
  12. {
  13. /** 队列名称 */
  14. private const QUEUE_ARRIMAGE = 'arrimage';
  15. /** Redis 任务状态 TTL(秒) */
  16. private const TASK_TTL = 300;
  17. /** Redis key 前缀 */
  18. private const KEY_TEXT_TO_IMAGE = 'text_to_image_task:';
  19. private const KEY_IMG_TO_IMG = 'img_to_img_task:';
  20. /**
  21. * 图生文:提交到队列
  22. * @param array $params 请求参数
  23. * @return bool
  24. */
  25. public function handleImgToText(array $params): bool
  26. {
  27. Queue::push('app\job\ImageArrJob', $params, self::QUEUE_ARRIMAGE);
  28. return true;
  29. }
  30. /**
  31. * 文生文:直接调用 API 并返回结果
  32. * @param string $prompt 提示词
  33. * @param string $model 模型名称
  34. * @return array ['success'=>bool, 'message'=>string, 'data'=>string]
  35. */
  36. public function handleTextToText($status_val, string $prompt, string $model): array
  37. {
  38. $ai = new AIGatewayService();
  39. $gptRes = $ai->buildRequestData($status_val, $model, $prompt);
  40. $gptText = '';
  41. if (isset($gptRes['candidates'][0]['content']['parts'][0]['text'])) {
  42. $gptText = $gptRes['candidates'][0]['content']['parts'][0]['text'];
  43. } elseif (isset($gptRes['choices'][0]['message']['content'])) {
  44. $gptText = trim($gptRes['choices'][0]['message']['content']);
  45. }
  46. if (isset($gptRes['error']) || isset($gptRes['code']) && $gptRes['code'] !== 0) {
  47. return [
  48. 'success' => false,
  49. 'message' => $gptRes['msg'] ?? $gptRes['error']['message'] ?? '生成失败',
  50. 'data' => ''
  51. ];
  52. }
  53. return [
  54. 'success' => true,
  55. 'message' => '生成成功',
  56. 'data' => $gptText
  57. ];
  58. }
  59. /**
  60. * 文生图:创建任务并推送到队列
  61. * @param array $params 含 id、model 等
  62. * @return array ['success'=>bool, 'message'=>string, 'task_id'=>string]
  63. */
  64. public function handleTextToImg(array $params): array
  65. {
  66. return $this->submitTaskToQueue(
  67. $params,
  68. self::KEY_TEXT_TO_IMAGE,
  69. '正在生成图片中,请稍等.....'
  70. );
  71. }
  72. /**
  73. * 图生图:创建任务并推送到队列(产品图+模板图)
  74. * @param array $params 含 id、product_img、template_img、prompt、model 等
  75. * @return array ['success'=>bool, 'message'=>string, 'task_id'=>string]
  76. */
  77. public function handleImgToImg(array $params): array
  78. {
  79. return $this->submitTaskToQueue(
  80. $params,
  81. self::KEY_IMG_TO_IMG,
  82. '正在生成图片中,请稍等.....'
  83. );
  84. }
  85. /**
  86. * 通用:创建任务 ID、写入 Redis、推送到队列
  87. */
  88. private function submitTaskToQueue(array $params, string $redisKeyPrefix, string $message): array
  89. {
  90. $taskId = ($params['id'] ?? '0') . '-' . date('YmdHis') . '-' . mt_rand(1000, 9999);
  91. $params['task_id'] = $taskId;
  92. $redis = getTaskRedis();
  93. $redis->set($redisKeyPrefix . $taskId, json_encode([
  94. 'status' => 'pending',
  95. 'created_at' => date('Y-m-d H:i:s')
  96. ]), ['EX' => self::TASK_TTL]);
  97. Queue::push('app\job\ImageArrJob', $params, self::QUEUE_ARRIMAGE);
  98. return [
  99. 'success' => true,
  100. 'message' => $message,
  101. 'task_id' => $taskId
  102. ];
  103. }
  104. /**
  105. * 批量图像任务:支持链式任务和单类型任务
  106. * @param array $params 含 batch、num、type、old_image_file 等
  107. * @return bool
  108. */
  109. public function handleImage(array $params): bool
  110. {
  111. if (!isset($params['batch']) || !is_array($params['batch'])) {
  112. return false;
  113. }
  114. $arr = $this->buildBatchItems($params);
  115. $insertData = $this->buildQueueLogData($params, count($arr));
  116. if (empty($params['type'])) {
  117. $this->dispatchFullChainTask($arr, $insertData);
  118. } else {
  119. $result = $this->dispatchSingleTypeTask($arr, $params, $insertData);
  120. if (!$result) {
  121. return false;
  122. }
  123. }
  124. return true;
  125. }
  126. /** 构建批量任务项 */
  127. private function buildBatchItems(array $params): array
  128. {
  129. $arr = [];
  130. foreach ($params['batch'] as $v) {
  131. $baseItem = [
  132. 'sourceDir' => $this->sourceDir($v, 1),
  133. 'outputDir' => $this->sourceDir($v, 2),
  134. 'file_name' => $this->sourceDir($v, 3),
  135. 'type' => $params['type'] ?? '',
  136. 'selectedOption' => $params['selectedOption'] ?? '',
  137. 'txttotxt_selectedOption' => $params['txttotxt_selectedOption'] ?? '',
  138. 'imgtotxt_selectedOption' => $params['imgtotxt_selectedOption'] ?? '',
  139. 'prompt' => '',
  140. 'width' => $params['width'] ?? 0,
  141. 'height' => $params['height'] ?? 0,
  142. 'executeKeywords' => $params['executeKeywords'] ?? '',
  143. 'sys_id' => $params['sys_id'] ?? ''
  144. ];
  145. $num = (int)($params['num'] ?? 1);
  146. $arr = array_merge($arr, array_fill(0, max(1, $num), $baseItem));
  147. }
  148. return $arr;
  149. }
  150. /** 构建队列日志插入数据 */
  151. private function buildQueueLogData(array $params, int $imageCount): array
  152. {
  153. return [
  154. 'create_time' => date('Y-m-d H:i:s'),
  155. 'old_image_file' => $params['old_image_file'] ?? '',
  156. 'status' => '等待中',
  157. 'image_count' => $imageCount,
  158. 'params' => json_encode($params, JSON_UNESCAPED_UNICODE)
  159. ];
  160. }
  161. /** 派发一键链式任务:图生文→文生文→文生图→图生图→高清放大 */
  162. private function dispatchFullChainTask(array $arr, array $insertData): void
  163. {
  164. $params = json_decode($insertData['params'], true) ?: [];
  165. $insertData['model'] = 'gpt-4-vision-preview,gpt-4,' . ($params['selectedOption'] ?? '');
  166. $insertData['model_name'] = '文生图';
  167. $taskId = Db::name('queue_logs')->insertGetId($insertData);
  168. $arr = array_map(function ($item) use ($taskId) {
  169. $item['type'] = '图生文';
  170. $item['chain_next'] = ['文生文', '文生图', '图生图', '高清放大'];
  171. $item['task_id'] = $taskId;
  172. return $item;
  173. }, $arr);
  174. Queue::push('app\job\ImageArrJob', ['task_id' => $taskId, 'data' => $arr], self::QUEUE_ARRIMAGE);
  175. }
  176. /** 派发单类型任务 */
  177. private function dispatchSingleTypeTask(array $arr, array $params, array $insertData): bool
  178. {
  179. $typeConfig = $this->getTypeConfig($params['type'], $params);
  180. if (!$typeConfig) {
  181. return false;
  182. }
  183. $insertData['model'] = $typeConfig['model'];
  184. $insertData['model_name'] = $typeConfig['model_name'];
  185. $taskId = Db::name('queue_logs')->insertGetId($insertData);
  186. $arr = array_map(function ($item) use ($params, $taskId) {
  187. $item['type'] = $params['type'];
  188. $item['task_id'] = $taskId;
  189. return $item;
  190. }, $arr);
  191. Queue::push('app\job\ImageArrJob', ['task_id' => $taskId, 'data' => $arr], self::QUEUE_ARRIMAGE);
  192. return true;
  193. }
  194. /** 任务类型对应的 model 配置 */
  195. private function getTypeConfig(string $type, array $params = []): ?array
  196. {
  197. $configs = [
  198. '图生文' => ['model' => 'gpt-4-vision-preview', 'model_name' => '图生文'],
  199. '文生文' => ['model_key' => 'txttotxt_selectedOption', 'model_name' => '文生文'],
  200. '文生图' => ['model_key' => 'selectedOption', 'model_name' => '文生图'],
  201. '图生图' => ['model' => 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]', 'model_name' => '图生图'],
  202. '高清放大' => ['model' => '高清放大', 'model_name' => '高清放大']
  203. ];
  204. $cfg = $configs[$type] ?? null;
  205. if (!$cfg) {
  206. return null;
  207. }
  208. if (isset($cfg['model_key'])) {
  209. $cfg['model'] = $params[$cfg['model_key']] ?? '';
  210. unset($cfg['model_key']);
  211. }
  212. return $cfg;
  213. }
  214. /**
  215. * 解析图像路径
  216. * @param string $filePath 如 uploads/operate/ai/Preview/20240610/xxx.png
  217. * @param int $type 1=源目录 2=输出目录 3=文件名
  218. * @return string|null
  219. */
  220. public function sourceDir(string $filePath, int $type): ?string
  221. {
  222. $pathParts = explode('/', $filePath);
  223. $filename = array_pop($pathParts);
  224. $baseParts = $pathParts;
  225. $date = '';
  226. foreach ($pathParts as $index => $part) {
  227. if (preg_match('/^\d{8}$/', $part)) {
  228. $date = $part;
  229. unset($baseParts[$index]);
  230. break;
  231. }
  232. }
  233. $basePath = implode('/', $baseParts);
  234. switch ($type) {
  235. case 1:
  236. return $basePath;
  237. case 2:
  238. return '/' . str_replace('/Preview/', '/dall-e/', $basePath) . $date;
  239. case 3:
  240. return $filename;
  241. default:
  242. return null;
  243. }
  244. }
  245. }