ImageJob.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <?php
  2. namespace app\job;
  3. use think\Db;
  4. use think\queue\Job;
  5. use think\Queue;
  6. class ImageJob{
  7. protected $config = [
  8. 'gpt' => [
  9. 'api_key' => 'sk-Bhos1lXTRpZiAAmN06624a219a874eCd91Dc068b902a3e73',
  10. 'api_url' => 'https://one.opengptgod.com/v1/chat/completions'
  11. ],
  12. 'dalle' => [
  13. 'api_key' => 'sk-e0JuPjMntkbgi1BoMjrqyyzMKzAxILkQzyGMSy3xiMupuoWY',
  14. 'api_url' => 'https://niubi.zeabur.app/v1/images/generations'
  15. ]
  16. ];
  17. /**
  18. * 图生文
  19. */
  20. public function fire(Job $job, $data)
  21. {
  22. // echo "<pre>";
  23. // print_r($data);
  24. // echo "<pre>";die;
  25. $logId = $data['log_id'] ?? null;
  26. try {
  27. echo date('Y-m-d H:i:s')."图生文开始\n";
  28. if ($logId) {
  29. Db::name('image_task_log')->where('id', $logId)->update([
  30. 'status' => 1,
  31. 'log' => '图生文处理中',
  32. 'update_time' => date('Y-m-d H:i:s')
  33. ]);
  34. }
  35. $result = $this->processImage($data);
  36. echo $result;
  37. if ($logId) {
  38. Db::name('image_task_log')->where('id', $logId)->update([
  39. 'status' => 2,
  40. 'log' => '图生文处理成功',
  41. 'update_time' => date('Y-m-d H:i:s')
  42. ]);
  43. }
  44. echo date('Y-m-d H:i:s')."图生文结束\n";
  45. $job->delete();
  46. } catch (\Exception $e) {
  47. //异常处理,记录失败日志
  48. echo "错误信息: " . $e->getMessage() . "\n";
  49. echo "文件: " . $e->getFile() . "\n";
  50. echo "行号: " . $e->getLine() . "\n";
  51. if ($logId) {
  52. Db::name('image_task_log')->where('id', $logId)->update([
  53. 'status' => 99,
  54. 'log' => '图生文失败:' . $e->getMessage(),
  55. 'update_time' => date('Y-m-d H:i:s')
  56. ]);
  57. }
  58. $job->delete();
  59. }
  60. }
  61. /**
  62. * 任务失败时的处理
  63. */
  64. public function failed($data)
  65. {
  66. // 记录失败日志或发送通知
  67. echo "ImageJob failed: " . json_encode($data);
  68. }
  69. /**
  70. * 处理图片的具体逻辑
  71. */
  72. public function processImage($data)
  73. {
  74. // 根据传入的数据处理图片
  75. $res = $this->imageToText($data["sourceDir"],$data["file_name"],$data["prompt"],$data);
  76. echo $res;
  77. }
  78. /**
  79. * 图生文接口
  80. */
  81. public function imageToText($sourceDirRaw, $fileName, $prompt, $call_data)
  82. {
  83. // 自动拆分文件名
  84. if (!$fileName && preg_match('/([^\/]+\.(jpg|jpeg|png))$/i', $sourceDirRaw, $matches)) {
  85. $fileName = $matches[1];
  86. $sourceDirRaw = preg_replace('/\/' . preg_quote($fileName, '/') . '$/', '', $sourceDirRaw);
  87. }
  88. // 参数校验
  89. if ($sourceDirRaw === '' || $fileName === '') {
  90. return '参数错误:原图路径 或 图片名称 不能为空';
  91. }
  92. // 构建路径
  93. $rootPath = str_replace('\\', '/', ROOT_PATH);
  94. $sourceDir = rtrim($rootPath . 'public/' . $sourceDirRaw, '/') . '/';
  95. $filePath = $sourceDir . $fileName;
  96. $relativePath = $sourceDirRaw . '/' . $fileName;
  97. // 文件检查
  98. if (!is_dir($sourceDir)) {
  99. return '源目录不存在:' . $sourceDir;
  100. }
  101. if (!is_file($filePath)) {
  102. return '文件不存在:' . $filePath;
  103. }
  104. // 获取图片信息
  105. $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
  106. $mime = ($ext === 'jpg' || $ext === 'jpeg') ? 'jpeg' : $ext;
  107. list($width, $height) = getimagesize($filePath);
  108. $imageData = base64_encode(file_get_contents($filePath));
  109. if (!$imageData || strlen($imageData) < 1000) {
  110. throw new \Exception('图片内容读取失败');
  111. }
  112. $imageUrl = "data:image/{$mime};base64,{$imageData}";
  113. // 记录提示词日志
  114. $logDir = $rootPath . 'runtime/logs/';
  115. if (!is_dir($logDir)) mkdir($logDir, 0755, true);
  116. // 调用图生文
  117. $gptRes = $this->callGptApi($imageUrl, $prompt);
  118. $gptText = trim($gptRes['choices'][0]['message']['content'] ?? '');
  119. // 提取英文描述
  120. $patternEnglish = '/^([\s\S]+?)---json json---/';
  121. preg_match($patternEnglish, $gptText, $matchEn);
  122. $englishDesc = isset($matchEn[1]) ? trim($matchEn[1]) : '';
  123. // 提取中文描述
  124. $patternChinese = '/---json json---\s*([\x{4e00}-\x{9fa5}][\s\S]+?)---json json---/u';
  125. preg_match($patternChinese, $gptText, $matchZh);
  126. $chineseDesc = isset($matchZh[1]) ? trim($matchZh[1]) : '';
  127. // 提取图片名(可能是中文短句,也可能是关键词)
  128. $patternName = '/---json json---\s*(.+)$/s';
  129. preg_match($patternName, $gptText, $matchName);
  130. $rawName = isset($matchName[1]) ? trim($matchName[1]) : '';
  131. $img_name = preg_replace('/[^\x{4e00}-\x{9fa5}A-Za-z0-9_\- ]/u', '', $rawName);
  132. // 验证 GPT 返回格式
  133. if (strpos($gptText, '---json json---') === false) {
  134. return 'GPT 返回格式不正确,缺少分隔符';
  135. }
  136. // 以 ---json json--- 分割
  137. $parts = array_map('trim', explode('---json json---', $gptText));
  138. // 清理“第一段”、“第二段”等标签前缀
  139. $cleanPrefix = function ($text) {
  140. return preg_replace('/^第[一二三四五六七八九十]+段[::]?\s*/u', '', $text);
  141. };
  142. // 防止越界,逐个安全提取
  143. $englishDesc = isset($parts[0]) ? $cleanPrefix(trim($parts[0])) : '';
  144. $chineseDesc = isset($parts[1]) ? $cleanPrefix(trim($parts[1])) : '';
  145. $part2 = isset($parts[2]) ? $cleanPrefix(trim($parts[2])) : '';
  146. // 只保留中英文、数字、下划线、短横线、空格
  147. $img_name = preg_replace('/[^\x{4e00}-\x{9fa5}A-Za-z0-9_\- ]/u', '', $part2);
  148. // 成功后的图生文提示词日志
  149. // file_put_contents(
  150. // $logDir . 'img_name_success.txt',
  151. // "\n======== " . date('Y-m-d H:i:s') . " ========\n" .
  152. // $englishDesc . "\n---json json---\n" .
  153. // $chineseDesc . "\n---json json---\n" .
  154. // $img_name . "\n\n",
  155. // FILE_APPEND
  156. // );
  157. // 成功写入数据库
  158. $this->logToDatabase([
  159. 'img_name' => $img_name,
  160. 'old_image_url' => $relativePath,
  161. 'chinese_description' => $chineseDesc,
  162. 'english_description' => $englishDesc,
  163. 'size' => "",
  164. 'status' => 0
  165. ]);
  166. return ;
  167. }
  168. /**
  169. * 文生文接口
  170. */
  171. public function textToTxt($prompt,$id)
  172. {
  173. // 查询数据库记录
  174. $record = Db::name('text_to_image')
  175. ->field('id,english_description')
  176. ->where('id',$id)
  177. ->order('id desc')
  178. ->find();
  179. if (!$record) {
  180. return '没有找到匹配的图像记录';
  181. }
  182. // 调用文生文
  183. $gptRes = $this->TxtGptApi($prompt.$record['english_description']);
  184. $gptText = trim($gptRes['choices'][0]['message']['content'] ?? '');
  185. // 更新数据库记录
  186. Db::name('text_to_image')->where('id', $record['id'])->update([
  187. 'english_description' => $gptText
  188. ]);
  189. return 0;
  190. }
  191. public function logToDatabase($data)
  192. {
  193. $record = [
  194. 'old_image_url' => $data['old_image_url'] ?? '',
  195. 'new_image_url' => $data['new_image_url'] ?? '',
  196. 'custom_image_url' => $data['custom_image_url'] ?? '',
  197. 'img_name' => $data['img_name'],
  198. 'model' => '',
  199. 'size' => isset($data['image_width'], $data['image_height']) ? $data['image_width'] . 'x' . $data['image_height'] : '',
  200. 'chinese_description' => $data['chinese_description'] ?? '',
  201. 'english_description' => $data['english_description'] ?? '',
  202. 'status' => $data['status'] ?? 0,
  203. 'error_msg' => $data['error_msg'] ?? '',
  204. 'created_time' => date('Y-m-d H:i:s'),
  205. 'updated_time' => date('Y-m-d H:i:s')
  206. ];
  207. if (isset($data['id'])) {
  208. Db::name('text_to_image')->where('id', $data['id'])->update($record);
  209. } else {
  210. Db::name('text_to_image')->insert($record);
  211. }
  212. }
  213. /**
  214. * 图升文模型
  215. */
  216. public function callGptApi($imageUrl, $prompt)
  217. {
  218. $data = [
  219. "model" => "gpt-4-vision-preview",
  220. "messages" => [[
  221. "role" => "user",
  222. "content" => [
  223. ["type" => "text", "text" => $prompt],
  224. ["type" => "image_url", "image_url" => [
  225. "url" => $imageUrl,
  226. "detail" => "auto" // ✅ 显式添加 detail 字段,兼容 vision API
  227. ]]
  228. ]
  229. ]],
  230. "max_tokens" => 1000
  231. ];
  232. return $this->callApi($this->config['gpt']['api_url'], $this->config['gpt']['api_key'], $data);
  233. }
  234. /**
  235. * 文升文模型
  236. */
  237. public function TxtGptApi($prompt)
  238. {
  239. $data = [
  240. 'prompt' => $prompt,
  241. 'model' => 'gpt-4',
  242. 'session_id' => null,
  243. 'context_reset' => true
  244. ];
  245. return $this->callApi($this->config['gpt']['api_url'],$this->config['gpt']['api_key'],$data);
  246. }
  247. /**
  248. * 通用API调用方法
  249. */
  250. public function callApi($url, $apiKey, $data)
  251. {
  252. $maxRetries = 2;
  253. $attempt = 0;
  254. $lastError = '';
  255. while ($attempt <= $maxRetries) {
  256. $ch = curl_init();
  257. curl_setopt_array($ch, [
  258. CURLOPT_URL => $url,
  259. CURLOPT_RETURNTRANSFER => true,
  260. CURLOPT_POST => true,
  261. CURLOPT_POSTFIELDS => json_encode($data),
  262. CURLOPT_HTTPHEADER => [
  263. 'Content-Type: application/json',
  264. 'Authorization: Bearer ' . $apiKey
  265. ],
  266. CURLOPT_TIMEOUT => 120,
  267. CURLOPT_SSL_VERIFYPEER => false,
  268. CURLOPT_SSL_VERIFYHOST => 0,
  269. CURLOPT_TCP_KEEPALIVE => 1,
  270. CURLOPT_FORBID_REUSE => false
  271. ]);
  272. $response = curl_exec($ch);
  273. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  274. $curlError = curl_error($ch);
  275. curl_close($ch);
  276. if ($response !== false && $httpCode === 200) {
  277. $result = json_decode($response, true);
  278. return $result;
  279. }
  280. $lastError = $curlError ?: "HTTP错误:{$httpCode}";
  281. $attempt++;
  282. sleep(1);
  283. }
  284. throw new \Exception("请求失败(重试{$maxRetries}次):{$lastError}");
  285. }
  286. }