| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- <?php
- namespace app\api\controller;
- use app\common\controller\Api;
- use app\service\AIGatewayService;
- use think\Db;
- use think\Exception;
- /**
- * 本地测试接口
- */
- class Index extends Api
- {
- protected $noNeedLogin = ['*'];
- protected $noNeedRight = ['*'];
- /**
- * 首页
- */
- public function index()
- {
- $this->success('请求成功');
- }
- // 向量引擎配置
- private $baseUrl = "https://api.vectorengine.ai/v1";
- private $apiKey = "sk-P877pnXMk2erRS2an7qEa3Kdb3rIb7JVAWZ39lhA8HeN71gZ"; // 从控制台获取
- private $timeout = 120; // 超时时间(秒),视频生成需要更长
- /**
- * 文生图接口
- * POST /api/index/textToImage
- * 参数: prompt (string) 提示词
- */
- public function textToImage()
- {
- $params = $this->request->param();
- if (empty($params)) {
- $this->error('提示词不能为空');
- }
- $data = [
- "model" => "gemini-3-pro-image-preview", // 可替换为 dall-e-3 等
- "prompt" => $params['prompt'],
- "n" => 1,
- "size" => "1024x1024"
- ];
- $result = $this->requestVectorEngine("/images/generations", $data);
- if ($result['code'] === 0) {
- $this->success('生成成功', $result['data']);
- } else {
- $this->error($result['msg'], $result['data']);
- }
- }
- /**
- * 图生文接口
- * POST /api/index/imageToText
- * 参数: image_url (string) 公网图片URL, prompt (string) 提问指令
- */
- public function imageToText()
- {
- $imageUrl = $this->request->post('image_url');
- $prompt = $this->request->post('prompt', '描述这张图片的内容');
- if (empty($imageUrl)) {
- $this->error('图片URL不能为空');
- }
- $data = [
- "model" => "gpt-4-vision-preview",
- "messages" => [
- [
- "role" => "user",
- "content" => [
- ["type" => "text", "text" => $prompt],
- ["type" => "image_url", "image_url" => ["url" => $imageUrl]]
- ]
- ]
- ],
- "max_tokens" => 1000
- ];
- $result = $this->requestVectorEngine("/chat/completions", $data);
- if ($result['code'] === 0) {
- $this->success('识别成功', $result['data']);
- } else {
- $this->error($result['msg'], $result['data']);
- }
- }
- /**
- * 文生视频接口
- * POST /api/index/textToVideo
- * 参数: prompt (string) 提示词, duration (int) 时长(秒), resolution (string) 分辨率
- */
- public function textToVideo()
- {
- $prompt = $this->request->post('prompt');
- $duration = $this->request->post('duration', 5);
- $resolution = $this->request->post('resolution', '720p');
- if (empty($prompt)) {
- $this->error('提示词不能为空');
- }
- $data = [
- "model" => "kling-1.6", // 可替换为 seedance-2.0 等
- "prompt" => $prompt,
- "duration" => (int)$duration,
- "resolution" => $resolution
- ];
- $result = $this->requestVectorEngine("/videos/generations", $data);
- if ($result['code'] === 0) {
- $this->success('生成成功', $result['data']);
- } else {
- $this->error($result['msg'], $result['data']);
- }
- }
- /**
- * 封装向量引擎通用CURL请求
- */
- private function requestVectorEngine($endpoint, $data)
- {
- $url = $this->baseUrl . $endpoint;
- $ch = curl_init();
- curl_setopt_array($ch, [
- CURLOPT_URL => $url,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_POST => true,
- CURLOPT_POSTFIELDS => json_encode($data),
- CURLOPT_HTTPHEADER => [
- "Content-Type: application/json",
- "Authorization: Bearer " . $this->apiKey
- ],
- CURLOPT_SSL_VERIFYPEER => false, // 生产环境建议开启
- CURLOPT_TIMEOUT => $this->timeout
- ]);
- $response = curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $error = curl_error($ch);
- curl_close($ch);
- if ($error) {
- return ['code' => -1, 'msg' => '请求失败: ' . $error, 'data' => null];
- }
- if ($httpCode !== 200) {
- return ['code' => $httpCode, 'msg' => 'API返回异常', 'data' => json_decode($response, true)];
- }
- return ['code' => 0, 'msg' => '成功', 'data' => json_decode($response, true)];
- }
- public function GetTxtToImg(){
- $params = $this->request->param();
- $prompt = $params['prompt'];//提示词
- $model = $params['model'];//模型
- $size = $params['size'];//尺寸
- // 调用AI生成图片
- $aiGateway = new AIGatewayService();
- $res = $aiGateway->callDalleApi($prompt, $model, $size);
- // 提取base64图片数据
- $imageData = '';
- $imageType = 'png'; // 默认图片类型
- if (isset($res['candidates'][0]['content']['parts'][0]['text'])) {
- $text_content = $res['candidates'][0]['content']['parts'][0]['text'];
- // 匹配base64图片数据和类型
- if (preg_match('/data:image\/([a-zA-Z0-9]+);base64,([^\s]+)/', $text_content, $matches)) {
- $imageType = strtolower($matches[1]);
- $base64Data = $matches[2];
- // 解码base64数据
- $imageData = base64_decode($base64Data);
- }
- }
- if (!$imageData) {
- return json(['code' => 1, 'msg' => '图片生成失败,未找到有效图片数据']);
- }
- // 创建保存目录(public/uploads/log/YYYY-MM/)
- $yearMonth = date('Ym');
- $saveDir = ROOT_PATH . 'public/uploads/ceshi/' . $yearMonth . '/';
- if (!is_dir($saveDir)) {
- mkdir($saveDir, 0755, true);
- }
- // 生成唯一文件名
- $fileName = uniqid() . '.' . $imageType;
- $filePath = $saveDir . $fileName;
- // 保存图片到本地文件
- if (file_put_contents($filePath, $imageData) === false) {
- return json(['code' => 1, 'msg' => '图片保存失败']);
- }
- // 生成前端可访问的URL
- $imageUrl = '/uploads/ceshi/' . $yearMonth . '/' . $fileName;
- // 返回标准JSON响应
- return json([
- 'code' => 0,
- 'msg' => '图片生成成功',
- 'data' => [
- 'url' => $imageUrl
- ]
- ]);
- }
- /**
- * 学生端文生视频接口 - 用于生成视频
- */
- public function GetTxtToVideo(){
- $aiGateway = new AIGatewayService();
- $apiUrl = $aiGateway->config['videos']['api_url'];
- $apiKey = $aiGateway->config['videos']['api_key'];
- // 获取并验证参数
- $params = $this->request->param();
- // echo "<pre>";
- // print_r($params);
- // echo "<pre>";die;
- $postData = [
- 'prompt' => $params['prompt'],
- 'model' => $params['model'],
- 'seconds' => $params['duration'],
- 'size' => $params['size'],
- ];
- // 初始化CURL
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $apiUrl);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
- curl_setopt($ch, CURLOPT_HTTPHEADER, [
- 'Authorization: Bearer ' . $apiKey
- ]);
- curl_setopt($ch, CURLOPT_TIMEOUT, 300);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
- curl_setopt($ch, CURLOPT_HEADER, true); // 获取响应头
- curl_setopt($ch, CURLOPT_VERBOSE, true); // 启用详细输出以进行调试
- // 创建临时文件来捕获详细的cURL输出
- $verbose = fopen('php://temp', 'w+');
- curl_setopt($ch, CURLOPT_STDERR, $verbose);
- // 执行请求
- $response = curl_exec($ch);
- //HTTP状态码
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- // 获取详细的cURL调试信息
- rewind($verbose);
- //CURL调试信息
- $verboseLog = stream_get_contents($verbose);
- fclose($verbose);
- // 分离头部和主体
- $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
- //响应头部
- $header = substr($response, 0, $header_size);
- //响应主体
- $body = substr($response, $header_size);
- // 检查CURL错误
- $curlError = curl_error($ch);
- curl_close($ch);
- $responseData = json_decode($body, true);
- // 检查API是否返回了错误信息
- if (isset($responseData['error'])) {
- $errorMessage = isset($responseData['error']['message']) ? $responseData['error']['message'] : 'API请求失败';
- return json([
- 'code' => 1,
- 'msg' => '视频生成请求失败',
- 'data' => [
- 'error_type' => isset($responseData['error']['type']) ? $responseData['error']['type'] : 'unknown',
- 'error_code' => isset($responseData['error']['code']) ? $responseData['error']['code'] : 'unknown',
- 'error_message' => $errorMessage
- ]
- ]);
- }
- // 检查是否有自定义错误格式
- if (isset($responseData['code']) && $responseData['code'] === 'fail_to_fetch_task' && isset($responseData['message'])) {
- return json([
- 'code' => 1,
- 'msg' => '视频生成请求失败',
- 'data' => [
- 'error_code' => $responseData['code'],
- 'error_message' => $responseData['message']
- ]
- ]);
- }
- // 检查是否存在id字段
- if (!isset($responseData['id'])) {
- return json([
- 'code' => 1,
- 'msg' => '无法获取视频ID',
- 'data' => [
- 'response_data' => $responseData,
- 'http_code' => $httpCode
- ]
- ]);
- }
- // 1. 先检查视频状态
- $statusUrl = 'https://chatapi.onechats.ai/v1/videos/' . $responseData['id'];
- $statusData = $this->fetchVideoStatus($statusUrl, $apiKey);
- // 检查视频状态
- if ($statusData['status'] !== 'completed') {
- return json([
- 'code' => 202,
- 'msg' => '视频尚未生成完成',
- 'data' => [
- 'video_id' => $responseData['id'],
- 'status' => $statusData['status'],
- 'progress' => $statusData['progress'],
- 'created_at' => $statusData['created_at'],
- 'message' => '请稍后再试,视频仍在' . ($statusData['status'] === 'queued' ? '排队中' : '处理中')
- ]
- ]);
- }
- // 2. 视频生成完成,准备下载
- $apiUrl = 'https://chatapi.onechats.ai/v1/videos/' . $responseData['id'] . '/content';
- // 获取可选的variant参数
- $variant = $this->request->get('variant', '');
- if (!empty($variant)) {
- $apiUrl .= '?variant=' . urlencode($variant);
- }
- // 创建保存目录
- $saveDir = ROOT_PATH . 'public' . DS . 'uploads' . DS . 'videos' . DS . date('Ymd');
- if (!is_dir($saveDir)) {
- mkdir($saveDir, 0755, true);
- }
- // 生成唯一文件名
- $filename = $responseData['id'] . '.mp4';
- $localPath = DS . 'uploads' . DS . 'videos' . DS . date('Ymd') . DS . $filename;
- $fullPath = $saveDir . DS . $filename;
- // 3. 下载视频
- $videoData = $this->downloadVideo($apiUrl, $apiKey);
- // 4. 保存视频文件
- if (file_put_contents($fullPath, $videoData) === false) {
- throw new Exception('视频保存失败');
- }
- // 确保路径使用正斜杠,并只保存相对路径部分
- $localPath = str_replace('\\', '/', $localPath);
- // 移除开头的斜杠,确保路径格式为uploads/videos/...
- $savePath = ltrim($localPath, '/');
- // 返回成功响应
- return json([
- 'code' => 0,
- 'msg' => '视频下载成功',
- 'data' => [
- 'video_id' => $responseData['id'],
- 'web_url' => $savePath
- ]
- ]);
- }
- /**
- * 学生端视频状态查询接口 - 用于通过video_id查询视频状态
- */
- public function Getvideo_id(){
- // 获取并验证参数
- $params = $this->request->param();
- $videoId = $params['video_id'] ?? '';
- // 验证video_id参数
- if (empty($videoId)) {
- return json([
- 'code' => 1,
- 'msg' => '缺少必要参数:video_id',
- 'data' => []
- ]);
- }
- $aiGateway = new AIGatewayService();
- // $apiUrl = $aiGateway->config['videos']['api_url'];
- $apiKey = $aiGateway->config['videos']['api_key'];
- $apiUrl = 'https://chatapi.onechats.ai/v1/videos/' . $videoId;
- // 先检查本地是否已经有该视频
- $localVideoPath = ROOT_PATH . 'public' . DS . 'uploads' . DS . 'videos' . DS . date('Ymd') . DS . $videoId . '.mp4';
- if (file_exists($localVideoPath)) {
- // 视频已存在本地,直接返回
- $webPath = '/uploads/videos/' . date('Ymd') . '/' . $videoId . '.mp4';
- return json([
- 'code' => 0,
- 'msg' => '视频下载成功',
- 'data' => [
- 'video_id' => $videoId,
- 'web_url' => substr($webPath, 1) // 移除开头的斜杠
- ]
- ]);
- }
- // 检查视频状态
- $statusData = $this->fetchVideoStatus($apiUrl, $apiKey);
- // 检查视频状态
- if ($statusData['status'] !== 'completed') {
- return json([
- 'code' => 202,
- 'msg' => '视频尚未生成完成',
- 'data' => [
- 'video_id' => $videoId,
- 'status' => $statusData['status'],
- 'progress' => isset($statusData['progress']) ? $statusData['progress'] : 0,
- 'created_at' => $statusData['created_at'],
- 'message' => '请稍后再试,视频仍在' . ($statusData['status'] === 'queued' ? '排队中' : '处理中')
- ]
- ]);
- }
- // 视频生成完成,下载视频
- $downloadUrl = 'https://chatapi.onechats.ai/v1/videos/' . $videoId . '/content';
- // 获取可选的variant参数
- $variant = $this->request->get('variant', '');
- if (!empty($variant)) {
- $downloadUrl .= '?variant=' . urlencode($variant);
- }
- // 创建保存目录
- $saveDir = ROOT_PATH . 'public' . DS . 'uploads' . DS . 'videos' . DS . date('Ymd');
- if (!is_dir($saveDir)) {
- mkdir($saveDir, 0755, true);
- }
- // 生成唯一文件名
- $filename = $videoId . '.mp4';
- $localPath = DS . 'uploads' . DS . 'videos' . DS . date('Ymd') . DS . $filename;
- $fullPath = $saveDir . DS . $filename;
- // 下载视频
- $videoData = $this->downloadVideo($downloadUrl, $apiKey);
- // 保存视频文件
- if (file_put_contents($fullPath, $videoData) === false) {
- throw new Exception('视频保存失败');
- }
- // 确保路径使用正斜杠,并只保存相对路径部分
- $localPath = str_replace(DIRECTORY_SEPARATOR, '/', $localPath);
- // 移除开头的斜杠,确保路径格式为uploads/videos/...
- $savePath = ltrim($localPath, '/');
- // 返回成功响应
- return json([
- 'code' => 0,
- 'msg' => '视频下载成功',
- 'data' => [
- 'video_id' => $videoId,
- 'web_url' => $savePath
- ]
- ]);
- }
- /**
- * 九个分镜头生成流程
- * 模型:gemini-3-pro-image-preview
- * 说明:
- * 第一步:(提示词 + 原始图片 = 九个分镜头图片) 或 (提示词 = 九个分镜头图片)
- * 第二步:使用九个分镜头图进行裁剪单图生成连贯视频
- * 第三步:在通过分镜头视频拼接成一个完整的视频(列如每个分镜头视频为8秒,九个为72秒形成完整视频)
- */
- public function Get_txttonineimg()
- {
- // 发起接口请求
- // $apiUrl = 'https://chatapi.onechats.ai/v1beta/models/gemini-3-pro-image-preview:generateContent';
- $apiUrl = 'https://chatapi.onechats.ai/v1beta/models/gemini-3-pro-image-preview:streamGenerateContent';
- $apiKey = '';
- // $params = $this->request->param();
- $prompt = '生成一个苹果(九个分镜头图片)';
- $requestData = [
- "contents" => [
- [
- "role" => "user",
- "parts" => [
- ["text" => $prompt]
- ]
- ]
- ],
- "generationConfig" => [
- "responseModalities" => ["TEXT", "IMAGE"],
- "imageConfig" => [
- "aspectRatio" => "1:1"
- ]
- ]
- ];
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $apiUrl);
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($requestData, JSON_UNESCAPED_UNICODE));
- curl_setopt($ch, CURLOPT_HTTPHEADER, [
- 'Content-Type: application/json',
- 'Authorization: Bearer ' . $apiKey
- ]);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 开发环境临时关闭SSL验证
- curl_setopt($ch, CURLOPT_TIMEOUT, 60); // 生成图片超时时间(建议60秒)
- $response = curl_exec($ch);
- $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $error = curl_error($ch);
- curl_close($ch);
- $res = json_decode($response, true);
- // 构建URL路径(使用正斜杠)
- $url_path = '/uploads/txtnewimg/';
- // 构建物理路径(使用正斜杠确保统一格式)
- $save_path = ROOT_PATH . 'public' . '/' . 'uploads' . '/' . 'txtnewimg' . '/';
- // 移除ROOT_PATH中可能存在的反斜杠,确保统一使用正斜杠
- $save_path = str_replace('\\', '/', $save_path);
- // 自动创建文件夹(如果不存在)
- if (!is_dir($save_path)) {
- mkdir($save_path, 0755, true);
- }
- // 提取base64图片数据
- $text_content = $res['candidates'][0]['content']['parts'][0]['inlineData']['data'];
- $str = 'data:image/jpeg;base64,';
- $text_content = $str. $text_content;
- // 匹配base64图片数据
- preg_match('/data:image\/(png|jpg|jpeg);base64,([^"]+)/', $text_content, $matches);
- if (empty($matches)) {
- return '未找到图片数据';
- }
- $image_type = $matches[1];
- $base64_data = $matches[2];
- // 解码base64数据
- $image_data = base64_decode($base64_data);
- if ($image_data === false) {
- return '图片解码失败';
- }
- // 生成唯一文件名(包含扩展名)
- $file_name = uniqid() . '.' . $image_type;
- $full_file_path = $save_path . $file_name;
- // 保存图片到文件系统
- if (!file_put_contents($full_file_path, $image_data)) {
- return '图片保存失败';
- }
- // 生成数据库存储路径(使用正斜杠格式)
- $db_img_path = $url_path . $file_name;
- return $db_img_path;
- }
- }
|