think php处理 异步 url 请求 记录
1、需求 某网站 需要 AI生成音乐,生成mp3文件的时候需要等待,需要程序中实时监听mp3文件是否生成
2、用的开发框架 为php
3、文件结构
配置路由设置
Route::group('/music', function () {
Route::post('/musicLyrics', 'AiMusic/musicLyrics');//Ai生成歌词流式
Route::post('/selectSleepMusicList', 'AiMusic/selectSleepMusicList');//查找睡眠音乐列表
Route::post('/createMusic', 'AiMusic/createMusic');//查找睡眠音乐列表
Route::post('/selectMusicHistory', 'AiMusic/selectMusicHistory');//查找用户AI生成音乐历史记录
Route::post('/deleteMusic', 'AiMusic/deleteMusic');//删除用户选中音乐
Route::get('/musicLyricsNoStream', 'AiMusic/musicLyricsNoStream');//Ai生成歌词非流式
Route::post('/publishMusic', 'AiMusic/publishMusic');//发布音乐到广场
Route::get('/updateMp3FileStatus', 'AiMusic/updateMp3FileStatus');//Ai生成歌词非流式
})->middleware([app\api\middleware\AuthMiddleware::class]);
控制器实现代码
<?php
declare (strict_types=1);
namespace app\api\controller;
use app\constants\Message;
use app\exception\BusinessException;
use app\service\ChatBaseService;
use app\service\ModelService;
use think\exception\ValidateException;
use think\facade\Db;
class AiMusic extends Base
{
//请求生成歌曲网站url
public $url = 'https://api.sunoaiapi.com/api/v1';
//请求生成歌曲key
public $apiKey = 'your key';
public $mv = 'chirp-v3-5';
public $mapList = [
'limit' => '/gateway/limit',
'create' => '/gateway/generate/music',
'query' => '/gateway/query',
];
/**
* ai 生成音乐歌词
* @return
*/
public function musicLyrics()
{
$question = $this->request->all('q');
if (empty($question)) {
throw new BusinessException('q' . Message::NOT_NUL);
}
$modelId = 1;
$rand = rand(00000, 99999);
$conversationId = substr(md5((string)time()), 8, 16) . '-' . substr(md5((string)$rand), 8, 16) . '-' . substr(md5($question . $modelId), 8, 16);
$answer = (new ChatBaseService($this->getModeName(), $conversationId))->chat((string)trim($question), (int)$this->uid, (int)$modelId);
if ($answer) {
return json(['code' => 200, 'msg' => $answer['message']['content']]);
} else {
return json(['code' => 400, 'msg' => 'AI生成歌词失败']);
}
}
/**
* ai 获取模型名字
* @return
*/
private function getModeName()
{
$modelId = 1;
$res = (new ModelService)->getOneById($modelId);
if (empty($res)) {
throw new BusinessException('model_id' . Message::NOT_NUL);
}
return $res['name'];
}
/**
* ai 生成音乐歌词非流式
* @return
*/
public function musicLyricsNoStream()
{
header("Access-Control-Allow-Origin: *");
$command = "curl http://your ip:11434/api/chat -d '{\"model\": \"openchat:latest\",\"messages\": [{\"role\": \"user\",\"content\": \"" . $_GET['question'] . "\"}],\"stream\": false}'";
$output = shell_exec($command); // 执行shell命令并将结果赋值给$output变量
$data = json_decode($output, true);
if ($data) {
return json(['msg' => $data['message']['content']]);
} else {
return json(['code' => 400, 'msg' => 'AI生成歌词失败']);
}
}
/**
* 睡眠时听音乐列表
* @return
*/
public function selectSleepMusicList()
{
$res = Db::name('music')->field('music_file_path,music_name,music_publicity_picture')->select();
if ($res) {
return json(['code' => 200, 'msg' => '获取成功', 'data' => $res]);
} else {
return json(['code' => 400, 'msg' => '获取失败']);
}
}
/**
* 创建歌曲
* @param $this ->request 请求参数 llm 模型ID lyric 歌词 song 歌曲名称
* @return mixed JSON格式的响应数据
*/
public function createMusic()
{
$lyric = $this->request->param('lyric', '');//歌词
$song = $this->request->param('song', '');//歌曲名称
$tags = $this->request->param('tags', '流行');//歌曲风格
$token = trim(ltrim($this->request->header('Authorization'), 'Bearer'));
if (empty($song)) {
throw new ValidateException('歌曲名称必填');
}
$url = $this->url.$this->mapList['create'];
$headers = [
'api-key: '.$this->apiKey,
];
$body = [
'title' => $song,
'tags' => $tags,
'prompt' => $lyric,
'mv' => $this->mv,
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
if (!empty($body)) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_DNS_USE_GLOBAL_CACHE, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$result = curl_exec($ch);
curl_close($ch);
if (empty($result)) {
return '创建失败';
}
// 解码 JSON 数据
$data = json_decode($result, true);
if (empty($data['data'][0]['song_id'])) {
return json(['code' => 400, 'msg' => '没有得到歌曲id']);
} else {
Db::name('ai_generated_music')->insert([
'user_id' => $this->uid,
'song_id' => 'https://cdn1.suno.ai/' . $data['data'][0]['song_id'] . '.mp3',
'title' => $data['data'][0]['title'],
'music_type' => '1',//默认为我的音乐值为1
'meta_tags' => $data['data'][0]['meta_tags'],
'meta_prompt' => $data['data'][0]['meta_prompt'],
'music_generation_state' => '生成中',
'music_img' => 'https://cdn1.suno.ai/image_' . $data['data'][0]['song_id'] . '.jpeg',
]);
$this->runAsyncGetMp3FileStatus('https://cdn1.suno.ai/' . $data['data'][0]['song_id'] . '.mp3',$token);
return json(['code' => 200,
'msg' => '生成歌曲成功',
'song_url' => 'https://cdn1.suno.ai/' . $data['data'][0]['song_id'] . '.mp3',
'music_img' => 'https://cdn1.suno.ai/image_' . $data['data'][0]['song_id'] . '.jpeg',
'music_generation_state' => '生成中',
]);
}
}
/**
* 查找用户生成的音乐列表
* @param 用户id
* @return 当前用户的音乐列表数据
*/
public function selectMusicHistory()
{
$musicType = $this->request->param('type', '');//从页面传入的音乐类型
$res = Db::name('ai_generated_music')->where([['user_id', '=', $this->uid], ['music_type', '=', $musicType]])->select();
if ($res) {
return json(['code' => 200, 'msg' => '获取成功', 'data' => $res]);
} else {
return json(['code' => 400, 'msg' => '获取失败']);
}
}
/**
* 删除用户选中音乐
* @param 音乐ID
* @return 删除成功或失败
*/
public function deleteMusic()
{
$musicId = $this->request->param('musicId', '');//从页面传入的音乐ID
$result = Db::name('ai_generated_music')->where(['id' => (int)$musicId])->delete();
if ($result) {
return json(['code' => 200, 'msg' => '删除成功']);
} else {
return json(['code' => 400, 'msg' => '删除失败']);
}
}
/**
* 发布音乐到广场
* @param 音乐ID
* @return 发布音乐到广场
*/
public function publishMusic()
{
$musicId = $this->request->param('musicId', '');//从页面传入的音乐ID
$res = Db::name('ai_generated_music')->where(['id' => (int)$musicId])->update(['music_type' => '0']);//更新音乐表中的音乐类型为广场音乐
if ($res) {
return json(['code' => 200, 'msg' => '发布音乐到广场成功']);
} else {
return json(['code' => 400, 'msg' => '发布音乐到广场失败或该音乐已发布']);
}
}
/**
* curl 异步调用 api/music/updateMp3FileStatus 接口 更新 mp3 音乐 文件状态
* @param 音乐ID,令牌
* @return
*/
public function runAsyncGetMp3FileStatus($songId,$token)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://headsetmusic.yuzhouxiong.net/api/music/updateMp3FileStatus?songId=".$songId); // 这个 URL 会调用异步的 PHP 脚本
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); // 或 "POST"
$authorizationToken = "Bearer ".$token; // 替换为实际的 Token
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: $authorizationToken",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 1); // 设置超时为 1 秒
curl_setopt($ch, CURLOPT_NOSIGNAL, 1); // 禁用信号
curl_exec($ch);
curl_close($ch);
}
/**
* curl 异步更新mp3 文件状态 方法
* @param 音乐ID
* @return 数据库表h_ai_generated_music 字段 music_generation_state 为 已完成
*/
function updateMp3FileStatus($songId)
{
while (true) {
$headers = @get_headers($songId);
// 如果获取不到响应头或者响应码不是 200,表示文件不存在
if ($headers && strpos($headers[0], '200')) {
$res=Db::name('ai_generated_music')->where(['song_id' => $songId])->update(['music_generation_state' => '已完成']);//更新音乐表中的音乐类型为广场音乐
if ($res) {
$logMessage = 'music id: '.$songId.' MP3 update success';
} else {
$logMessage = 'music id: '.$songId.' MP3 update failure';
}
// 日志文件路径
$logFile = '/www/wwwroot/headset/runtime/updateMp3FileStatus.log';
// 打开文件,如果文件不存在会创建,'a'模式表示追加内容
$file = fopen($logFile, 'a');
// 检查文件是否成功打开
if ($file) {
// 日志内容
$logMessageTemp = "[".date('Y-m-d H:i:s')."] " . $logMessage. PHP_EOL;;
// 写入日志内容
fwrite($file, $logMessageTemp);
// 关闭文件
fclose($file);
} else {
echo "无法打开日志文件";
}
echo "处理完成";
break;
}
// 每隔 1 秒继续检查
sleep(1);
}
}
}