xiaoai/php_server/app/api/logic/HdLogic.php

592 lines
19 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\api\logic;
use app\api\logic\service\TokenLogService;
use app\common\enum\user\AccountLogEnum;
use app\common\logic\AccountLogLogic;
use app\common\model\hd\HdImage;
use app\common\model\hd\HdLog;
use app\common\model\user\User;
use app\common\service\FileService;
use app\common\model\hd\HdImageCases;
use think\facade\Log;
class HdLogic extends ApiLogic
{
const HD_TXT2IMG = 'hd_txt_to_img'; //文生图
const HD_TXT2IMG_STATUS = 'hd_txt_to_img_status'; //文生图状态
const HD_IMG2IMG = 'hd_img_to_img'; //图生图
const HD_IMG2IMG_STATUS = 'hd_img_to_img_status'; //图生图状态
const HD_TEMPLATE_LISTS = 'hd_template_lists'; //模板列表
const HD_SHOP_IMG2IMG = 'hd_shop_img_to_img'; //商品图生图状态
const HD_SHOP_IMG2IMG_STATUS = 'hd_shop_img_to_img_status'; //商品图生图状态
const HD_SHOP_IMG_CUT = 'hd_shop_img_cut'; //商品图生图抠图
const HD_AI_TRY = 'hd_ai_try'; //AI试衣生图
const HD_AI_TRY_STATUS = 'hd_ai_try_status'; //AI试衣生图状态
/**
* @desc 删除图片
* @param array $data
* @return true
* @date 2024/7/6 15:59
* @author dagouzi
*/
public static function deleteImage(array $data)
{
try {
if (is_string($data['log_id'])) {
HdImage::destroy(['log_id' => $data['log_id']]);
HdLog::destroy(['id' => $data['log_id']]);
} else {
HdImage::whereIn('log_id', $data['log_id'])->select()->delete();
HdLog::whereIn('id', $data['log_id'])->select()->delete();
}
return true;
} catch (\Exception $exception) {
self::setError($exception->getMessage());
return false;
}
}
/**
* @desc 保存日志
* @param $type
* @param $request_id
* @param $params
* @param $task_id
* @param $sub_task_id
* @return true
* @date 2024/7/6 15:57
* @author dagouzi
*/
public static function saveLog($type, $request_id, $params, $task_id, $sub_task_id)
{
$data = [
'user_id' => self::$uid,
'request_id' => $request_id,
'type' => $type,
'params' => json_encode($params, JSON_UNESCAPED_UNICODE),
'task_id' => $task_id,
'sub_task_ids' => json_encode($sub_task_id, JSON_UNESCAPED_UNICODE)
];
$log = HdLog::create($data);
foreach ($sub_task_id as $id) {
$imageData = [
'log_id' => $log->id,
'image' => '',
'sub_task_id' => $id,
'task_status' => 0,
'task_completion' => 0,
];
HdImage::create($imageData);
}
return true;
}
/**
* @desc 获取模板
* @param $params
* @return true
* @date 2024/7/5 10:10
* @throws \GuzzleHttp\Exception\GuzzleException
* @author dagouzi
*/
public static function templates($params)
{
$page_index = $params['page'] ?? 1;
$page_size = max($params['page_size'] ?? 20, 100);
$response = self::requestUrl([
'page_index' => $page_index,
'page_size' => $page_size,
], self::HD_TEMPLATE_LISTS, self::$uid);
self::$returnData = ['result' => $response];
return true;
}
/**
* @desc 提交商品图图生图任务
* @param $params
* @return bool
* @date 2024/7/5 14:48
* @throws \Exception
* @author dagouzi
*/
public static function segmentImage($params)
{
$request = [];
if (!empty($params['prompt'])) {
// 默认位置
$defaultLocation = [
'horizontal_direction' => 'AlignLeft',
'horizontal_proportion' => 0.5,
'vertical_direction' => 'AlignTop',
'vertical_proportion' => 0.5
];
// 提示词
$request['prompt'] = $params['prompt'];
$request['location_desc'] = !empty($params['location_desc']) ? $params['location_desc'] : $defaultLocation;
} else {
$request['template_category'] = $params['template_category'];
$request['template_name'] = $params['template_name'];
$request['custom_template'] = $params['custom_template'] ?? true;
}
foreach ($params as $key => $value) {
if (in_array($key, ['negative_prompt', 'resolution', 'img_count', 'size', 'style'])) {
if (!empty($value)) {
$request[$key] = $value;
}
}
if ($key == 'resolution' && !empty($value)) {
foreach ($value as &$valueResolution) {
$valueResolution = intval($valueResolution);
}
$request['resolution'] = $value;
}
if ($key == 'ref_image' && !empty($value)) {
foreach ($value as &$valueImg) {
$valueImg = self::imageToStream($valueImg);
}
$request['ref_image'] = $value;
}
}
$request['image'] = self::imageToStream($params['image']);
$response = self::requestUrl($request, self::HD_SHOP_IMG2IMG, self::$uid);
if (!$response) {
throw new \Exception('提交商品图生图任务发生错误');
}
self::saveLog(1, $response['request_id'], $params, $response['task_id'], $response['sub_task_ids']);
self::$returnData = ['result' => $response];
return true;
}
/**
* @desc 提交ai试衣生图任务
* @param $params
* @return bool
* @date 2024/7/5 14:48
* @throws \Exception
* @author dagouzi
*/
public static function vton($params)
{
$request = [];
// 组装数据
foreach ($params['persons'] as $item) {
$request['persons'][] = self::imageToStream($item);
}
if (!empty($params['lower_clothes'])) {
$request['lower_clothes'] = self::imageToStream($params['lower_clothes']);
}
if (!empty($params['upper_clothes'])) {
$request['upper_clothes'] = self::imageToStream($params['upper_clothes']);
}
$request['img_count'] = $params['img_count'] ?? 1;
$response = self::requestUrl($request, self::HD_AI_TRY, self::$uid);
if (!$response) {
throw new \Exception('提交ai试衣生图任务错误');
}
self::saveLog(2, $response['request_id'], $params, $response['task_id'], $response['sub_task_ids']);
self::$returnData = ['result' => $response];
return true;
}
/**
* @desc 添加模特案例
* @param $params
* @return bool
* @date 2024/12/25 15:50
* @author dagouzi
*/
public static function addModelCase($params)
{
$data = [
'user_id' => self::$uid,
'case_type' => 4,
'params' => json_encode([]),
'result_image' => FileService::setFileUrl($params['result_image']),
];
HdImageCases::create($data);
return true;
}
/**
* @desc 定时任务查询结果
* @return true|void
* @date 2024/7/5 17:57
* @author dagouzi
*/
public static function cron()
{
try{
$now = time();
// 延迟5秒执行
$task = HdLog::where(['task_status' => 0])->where('create_time', '<', $now - 30)->findOrEmpty();
if ($task->isEmpty()) {
return true;
}
if (empty($task->task_id)) {
self::taskStatus($task, 3);
return true;
}
self::handleResult($task->task_id, $task->type);
return true;
} catch (\Exception $e) {
return true;
}
}
/**
* @desc 提交文生图任务
* @return bool
* @date 2024/7/20 10:50
* @throws \GuzzleHttp\Exception\GuzzleException
* @author dagouzi
*/
public static function txt2img($params)
{
$request = [];
// 组装数据
foreach ($params as $key => $value) {
if (in_array($key, ['negative_prompt', 'aspect_ratio', 'img_count'])) {
if (!empty($value)) {
if ($key == 'img_count') {
$request[$key] = (int)$value;
} else {
$request[$key] = $value;
}
}
}
}
$request['prompt'] = $params['prompt'];
$response = self::requestUrl($request, self::HD_TXT2IMG, self::$uid);
if (!$response) {
throw new \Exception('提交文生图任务错误');
}
$sub_task_ids = $response['sub_task_ids'] ?? '';
self::saveLog(3, $response['request_id'], $params, $response['task_id'], $sub_task_ids);
self::$returnData = ['result' => $response];
return true;
}
/**
* @desc 提交图生图任务
* @return bool
* @date 2024/7/20 10:50
* @throws \GuzzleHttp\Exception\GuzzleException
* @author dagouzi
*/
public static function img2img($params)
{
$request = [];
// 组装数据
foreach ($params as $key => $value) {
if (in_array($key, ['prompt', 'negative_prompt', 'aspect_ratio', 'img_count'])) {
if (!empty($value)) {
if ($key == 'img_count') {
$request[$key] = (int)$value;
} else {
$request[$key] = $value;
}
}
}
}
if(empty($params['prompt'])){
throw new \Exception('请输入图片描述');
}
foreach ($params['image'] as $item) {
$request['image'][] = self::imageToStream($item);
}
$response = self::requestUrl($request, self::HD_IMG2IMG, self::$uid);
if (!$response) {
throw new \Exception('提交图生图任务错误');
}
self::saveLog(4, $response['request_id'], $params, $response['task_id'], $response['sub_task_ids']);
self::$returnData = ['result' => $response];
return true;
}
/**
* @desc 获取任务状态
* @param $request
* @return true
* @date 2024/7/18 17:15
* @throws \GuzzleHttp\Exception\GuzzleException
* @author dagouzi
*/
public static function getTaskStatus($request)
{
$task_id = $request['task_id'];
$type = $request['type'];
$result = self::handleResult($task_id, $type);
self::$returnData = ['result' => $result];
return true;
}
/**
* @desc 处理查询结果
* @param $task_id
* @param $type
* @return array|mixed|true|null
* @date 2024/7/18 18:30
* @throws \GuzzleHttp\Exception\GuzzleException
* @author dagouzi
*/
private static function handleResult($task_id, $type)
{
$result = [];
$task = HdLog::where(['task_id' => $task_id])->findOrEmpty();
$typeName = '';
//转换
switch ($type) {
case 1:
$scene = self::HD_SHOP_IMG2IMG_STATUS;
$typeName = '商品图';
break;
case 2:
$scene = self::HD_AI_TRY_STATUS;
$typeName = 'ai试衣';
break;
case 3:
$scene = self::HD_TXT2IMG_STATUS;
$typeName = '文生图';
break;
case 4:
$scene = self::HD_IMG2IMG_STATUS;
$typeName = '图生图';
break;
default:
throw new \Exception('参数错误');
}
try {
$result = self::requestUrl(['task_id' => $task_id], $scene, self::$uid);
if (!$result) {
//记录 失败进行恢复
throw new \Exception('获取[' . $typeName . ']结果错误');
}
if (empty($result['sub_task_results'])) {
self::taskStatus($task, 4, json_encode($result));
return true;
}
$sub_task_results = $result['sub_task_results'];
$scene = match ((int)$type) {
1 => ['scene' => 'goods_image', "type" => AccountLogEnum::TOKENS_DEC_GOODS_IMAGE],
2 => ['scene' => 'model_image', "type" => AccountLogEnum::TOKENS_DEC_MODEL_IMAGE],
3 => ['scene' => 'text_to_image', "type" => AccountLogEnum::TOKENS_DEC_TEXT_TO_IMAGE],
4 => ['scene' => 'image_to_image', "type" => AccountLogEnum::TOKENS_DEC_IMAGE_TO_IMAGE],
};
$unit = TokenLogService::getTypeScore($scene['scene']);
foreach ($sub_task_results as $item) {
// 循环每个图的生成进度
$sub_task = HdImage::where(['sub_task_id' => $item['sub_task_id']])->findOrEmpty();
$image = $item['image'] ?? '';
if($image){
$image = FileService::downloadFileBySource($image, 'image');
}
if ($sub_task->isEmpty()) {
$sub_task_data = [
'log_id' => $task->id,
'image' => $image,
'sub_task_id' => $item['sub_task_id'],
'task_status' => $item['task_status'],
'task_completion' => $item['task_completion']
];
HdImage::create($sub_task_data);
} else {
if (!in_array($item['task_status'], [0, 2])) {
$sub_task->task_status = $item['task_status'];
$sub_task->task_completion = $item['task_completion'];
$sub_task->image = $image;
$sub_task->save();
}
}
$task_status = $item['task_status'];
//假设某张失败 则恢复对应的算力
if (in_array($task_status, [3, 4])) {
$extra = ['图片生成失败' => 1, '算力单价' => $unit,'恢复算力'=> $unit];
if (isset($item['task_id'])){
AccountLogLogic::recordUserTokensLog(false, $task->user_id, $scene['type'], $unit, $item['task_id'],$extra);
}
if (isset($item['sub_task_id'])){
AccountLogLogic::recordUserTokensLog(false, $task->user_id, $scene['type'], $unit, $item['sub_task_id'],$extra);
}
}
}
$status_array = array_column($sub_task_results, 'task_status');
// 子任务的状态0: 等待中1: 完成2: 处理中3: 失败4: 未通过审核
if (in_array(0, $status_array) || in_array(2, $status_array)) {
self::taskStatus($task, 0);
} else {
self::taskStatus($task, 1);
}
} catch (\Exception $e) {
self::taskStatus($task, 5, $e->getMessage());
}
return ['result' => $result];
}
/**
* @desc 修改任务状态
* @param $task
* @param $status
* @return void
* @date 2024/7/5 18:12
* @author dagouzi
*/
public static function taskStatus($task, $status = 2, $remark = '')
{
$task->task_status = $status;
if (!empty($remark)) {
$task->remark = $remark;
}
$task->save();
}
/**
* 请求上游接口与计费
* @param array $request
* @param string $scene
* @param int $userId
* @param string $taskId
* @return array
* @throws \Exception
*/
private static function requestUrl(array $request, string $scene, int $userId): array
{
$requestService = \app\common\service\ToolsService::HiDream();
[$tokenScene, $tokenCode] = match ($scene) {
self::HD_TXT2IMG => ['text_to_image', AccountLogEnum::TOKENS_DEC_TEXT_TO_IMAGE],
self::HD_IMG2IMG => ['image_to_image', AccountLogEnum::TOKENS_DEC_IMAGE_TO_IMAGE],
self::HD_SHOP_IMG2IMG => ['goods_image', AccountLogEnum::TOKENS_DEC_GOODS_IMAGE],
self::HD_AI_TRY => ['model_image', AccountLogEnum::TOKENS_DEC_MODEL_IMAGE],
default => ['', '']
};
if ($tokenScene) {
//补充
$request['img_count'] = $request['img_count'] ?? 1;
}
switch ($scene) {
case self::HD_TXT2IMG:
$response = $requestService->txt2Img($request);
break;
case self::HD_IMG2IMG:
$response = $requestService->img2Img($request);
break;
case self::HD_SHOP_IMG2IMG:
$response = $requestService->shopImg2Img($request);
break;
case self::HD_TEMPLATE_LISTS:
$response = $requestService->templateList($request);
break;
case self::HD_AI_TRY:
$response = $requestService->vtonCreate($request);
break;
case self::HD_TXT2IMG_STATUS:
$response = $requestService->txt2ImgStatus($request);
break;
case self::HD_IMG2IMG_STATUS:
$response = $requestService->img2ImgStatus($request);
break;
case self::HD_SHOP_IMG2IMG_STATUS:
$response = $requestService->shopImg2ImgStatus($request);
break;
case self::HD_AI_TRY_STATUS:
$response = $requestService->vtonStatus($request);
break;
default:
}
if ($tokenScene && isset($response['code']) && $response['code'] == 10000) {
$taskId = $response['data']['task_id'];
if(!isset($response['data']['sub_task_ids'])){
throw new \Exception('目前生图正在维护中,请稍后再试');
};
//计费
$unit = TokenLogService::checkToken($userId, $tokenScene);
$points = ceil($request['img_count'] * $unit);
//token扣除
User::userTokensChange(self::$uid, $points);
$extra = ['生成图片数' => $request['img_count'], '算力单价' => $unit, '实际消耗算力' => $points];
//扣费记录
AccountLogLogic::recordUserTokensLog(true, $userId, $tokenCode, $points, $taskId, $extra);
}
return $response['data'] ?? [];
}
/**
* @desc 图片转base64
* @param $url
* @return string
* @date 2024/7/5 10:55
* @author dagouzi
*/
private static function imageToStream($url)
{
$img = file_get_contents($url);
return base64_encode($img);
}
}