gengx
This commit is contained in:
@@ -5,6 +5,7 @@ namespace app\controller\api;
|
||||
use app\model\AiModel;
|
||||
use app\model\Conversation as ConversationModel;
|
||||
use app\model\Department;
|
||||
use app\model\InvitationCode;
|
||||
use app\model\MembershipLevel;
|
||||
use app\model\Message;
|
||||
use app\model\Role;
|
||||
@@ -155,6 +156,7 @@ class Admin extends BaseApi
|
||||
->leftJoin('roles r', 'u.role_id = r.id')
|
||||
->leftJoin('departments d', 'u.department_id = d.id')
|
||||
->field('u.id,u.username,u.email,u.nickname,u.role,u.role_id,u.department_id,u.status,u.membership_level_id,u.created_at,u.last_login_at,ml.name as membership_name,r.name as role_name,r.slug as role_slug,d.name as department_name')
|
||||
->whereRaw('LEFT(u.username, 6) <> ?', ['guest_'])
|
||||
->order('u.id', 'desc');
|
||||
|
||||
AdminScopeService::applyUserScope($query, $this->authUser(), 'u');
|
||||
@@ -183,6 +185,9 @@ class Admin extends BaseApi
|
||||
if (strlen($username) < 3 || strlen($username) > 50) {
|
||||
return $this->error('用户名长度需 3-50 个字符');
|
||||
}
|
||||
if (str_starts_with(strtolower($username), 'guest_')) {
|
||||
return $this->error('guest_ 为系统访客账号保留前缀');
|
||||
}
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
return $this->error('邮箱格式不正确');
|
||||
}
|
||||
@@ -249,6 +254,9 @@ class Admin extends BaseApi
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在', 404);
|
||||
}
|
||||
if (str_starts_with($user->username, 'guest_')) {
|
||||
return $this->error('访客请在访客管理中操作', 422);
|
||||
}
|
||||
|
||||
$roleSlug = Role::where('id', $user->role_id)->value('slug');
|
||||
if ($roleSlug === 'super_admin') {
|
||||
@@ -322,6 +330,9 @@ class Admin extends BaseApi
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在', 404);
|
||||
}
|
||||
if (str_starts_with($user->username, 'guest_')) {
|
||||
return $this->error('访客请在访客管理中操作', 422);
|
||||
}
|
||||
|
||||
User::where('id', $targetId)->update($data);
|
||||
|
||||
@@ -332,6 +343,77 @@ class Admin extends BaseApi
|
||||
return $this->success(null, '更新成功');
|
||||
}
|
||||
|
||||
public function guests()
|
||||
{
|
||||
$auth = $this->authUser();
|
||||
AdminScopeService::requireAny($auth, ['menu:guests']);
|
||||
|
||||
$page = max(1, (int) $this->request->get('page', 1));
|
||||
$limit = min(50, max(1, (int) $this->request->get('limit', 20)));
|
||||
$status = trim((string) $this->request->get('status', ''));
|
||||
$keyword = trim((string) $this->request->get('keyword', ''));
|
||||
|
||||
$query = User::alias('u')
|
||||
->whereRaw('LEFT(u.username, 6) = ?', ['guest_'])
|
||||
->field("u.id,u.username,u.nickname,u.status,u.created_at,u.last_login_at,
|
||||
(SELECT COUNT(*) FROM conversations c WHERE c.user_id = u.id AND c.deleted_at IS NULL) AS conversation_count,
|
||||
(SELECT COUNT(*) FROM messages msg INNER JOIN conversations c2 ON msg.conversation_id = c2.id WHERE c2.user_id = u.id) AS message_count")
|
||||
->order('u.last_login_at', 'desc')
|
||||
->order('u.id', 'desc');
|
||||
|
||||
AdminScopeService::applyUserScope($query, $auth, 'u');
|
||||
if (in_array($status, ['active', 'disabled'], true)) {
|
||||
$query->where('u.status', $status);
|
||||
}
|
||||
if ($keyword !== '') {
|
||||
$query->where('u.username', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
$total = (clone $query)->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
return $this->success(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
public function updateGuestStatus($id)
|
||||
{
|
||||
$auth = $this->authUser();
|
||||
AdminScopeService::requireAny($auth, ['btn:guest:status']);
|
||||
|
||||
$guest = User::find((int) $id);
|
||||
if (!$guest || !str_starts_with($guest->username, 'guest_')) {
|
||||
return $this->error('访客不存在', 404);
|
||||
}
|
||||
if (!AdminScopeService::canViewUser($auth, (int) $guest->id)) {
|
||||
return $this->error('无权操作该访客', 403);
|
||||
}
|
||||
|
||||
$status = trim((string) $this->request->put('status', ''));
|
||||
if (!in_array($status, ['active', 'disabled'], true)) {
|
||||
return $this->error('访客状态无效', 422);
|
||||
}
|
||||
|
||||
$guest->save(['status' => $status]);
|
||||
return $this->success(null, $status === 'active' ? '访客已启用' : '访客已禁用');
|
||||
}
|
||||
|
||||
public function deleteGuest($id)
|
||||
{
|
||||
$auth = $this->authUser();
|
||||
AdminScopeService::requireAny($auth, ['btn:guest:delete']);
|
||||
|
||||
$guest = User::find((int) $id);
|
||||
if (!$guest || !str_starts_with($guest->username, 'guest_')) {
|
||||
return $this->error('访客不存在', 404);
|
||||
}
|
||||
if (!AdminScopeService::canViewUser($auth, (int) $guest->id)) {
|
||||
return $this->error('无权操作该访客', 403);
|
||||
}
|
||||
|
||||
User::destroy((int) $guest->id);
|
||||
return $this->success(null, '访客记录已删除');
|
||||
}
|
||||
|
||||
public function conversations()
|
||||
{
|
||||
AdminScopeService::requireAny($this->authUser(), [
|
||||
@@ -557,6 +639,109 @@ class Admin extends BaseApi
|
||||
return $this->success($options);
|
||||
}
|
||||
|
||||
public function invitations()
|
||||
{
|
||||
AdminScopeService::requireAny($this->authUser(), [
|
||||
'menu:invitations',
|
||||
'btn:invitation:create',
|
||||
'btn:invitation:revoke',
|
||||
'can_manage_users',
|
||||
]);
|
||||
|
||||
$status = trim((string) $this->request->get('status', ''));
|
||||
$query = InvitationCode::alias('i')
|
||||
->leftJoin('departments d', 'i.department_id = d.id')
|
||||
->leftJoin('users creator', 'i.created_by = creator.id')
|
||||
->leftJoin('users used', 'i.used_by = used.id')
|
||||
->field('i.*,d.name as department_name,creator.username as creator_name,used.username as used_by_name')
|
||||
->order('i.created_at', 'desc')
|
||||
->order('i.id', 'desc');
|
||||
|
||||
if (in_array($status, ['active', 'used', 'revoked'], true)) {
|
||||
$query->where('i.status', $status);
|
||||
}
|
||||
|
||||
return $this->success($query->limit(500)->select());
|
||||
}
|
||||
|
||||
public function createInvitation()
|
||||
{
|
||||
$user = $this->authUser();
|
||||
AdminScopeService::requireAny($user, [
|
||||
'btn:invitation:create',
|
||||
'menu:invitations',
|
||||
'can_manage_users',
|
||||
]);
|
||||
|
||||
$input = $this->request->post();
|
||||
$departmentId = $input['department_id'] ?? null;
|
||||
if ($departmentId !== null && $departmentId !== '') {
|
||||
$departmentId = (int) $departmentId;
|
||||
if ($departmentId <= 0 || !Department::find($departmentId)) {
|
||||
return $this->error('所选部门不存在', 422);
|
||||
}
|
||||
} else {
|
||||
$departmentId = null;
|
||||
}
|
||||
|
||||
$expiresAt = trim((string) ($input['expires_at'] ?? ''));
|
||||
if ($expiresAt !== '') {
|
||||
$expiresAt = str_replace('T', ' ', $expiresAt);
|
||||
$expiresTimestamp = strtotime($expiresAt);
|
||||
if (!$expiresTimestamp || $expiresTimestamp <= time()) {
|
||||
return $this->error('过期时间必须晚于当前时间', 422);
|
||||
}
|
||||
$expiresAt = date('Y-m-d H:i:s', $expiresTimestamp);
|
||||
} else {
|
||||
$expiresAt = null;
|
||||
}
|
||||
|
||||
do {
|
||||
$code = 'INV-' . strtoupper(bin2hex(random_bytes(5)));
|
||||
} while (InvitationCode::where('code', $code)->find());
|
||||
|
||||
$invitation = InvitationCode::create([
|
||||
'code' => $code,
|
||||
'department_id' => $departmentId,
|
||||
'created_by' => (int) $user['id'],
|
||||
'status' => 'active',
|
||||
'expires_at' => $expiresAt,
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'id' => (int) $invitation->id,
|
||||
'code' => $invitation->code,
|
||||
'department_id' => $departmentId,
|
||||
'department_name' => $departmentId ? Department::where('id', $departmentId)->value('name') : null,
|
||||
'status' => 'active',
|
||||
'expires_at' => $expiresAt,
|
||||
'created_at' => $invitation->created_at,
|
||||
], '邀请码已生成');
|
||||
}
|
||||
|
||||
public function revokeInvitation($id)
|
||||
{
|
||||
AdminScopeService::requireAny($this->authUser(), [
|
||||
'btn:invitation:revoke',
|
||||
'menu:invitations',
|
||||
'can_manage_users',
|
||||
]);
|
||||
|
||||
$invitation = InvitationCode::find((int) $id);
|
||||
if (!$invitation) {
|
||||
return $this->error('邀请码不存在', 404);
|
||||
}
|
||||
if ($invitation->status === 'used') {
|
||||
return $this->error('已使用的邀请码不能作废', 422);
|
||||
}
|
||||
if ($invitation->status === 'revoked') {
|
||||
return $this->success(null, '邀请码已作废');
|
||||
}
|
||||
|
||||
$invitation->save(['status' => 'revoked']);
|
||||
return $this->success(null, '邀请码已作废');
|
||||
}
|
||||
|
||||
public function createDepartment()
|
||||
{
|
||||
AdminScopeService::requireAny($this->authUser(), ['btn:dept:create', 'can_manage_departments']);
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
namespace app\controller\api;
|
||||
|
||||
use app\model\User;
|
||||
use app\model\InvitationCode;
|
||||
use app\model\MembershipLevel;
|
||||
use app\model\Role;
|
||||
use app\service\JwtService;
|
||||
use app\service\SettingsService;
|
||||
use app\service\UserContextService;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\facade\Db;
|
||||
|
||||
class Auth extends BaseApi
|
||||
{
|
||||
@@ -51,6 +54,8 @@ class Auth extends BaseApi
|
||||
return $this->error('游客访问暂不可用', 403);
|
||||
}
|
||||
|
||||
$user->save(['last_login_at' => date('Y-m-d H:i:s')]);
|
||||
|
||||
$token = JwtService::generateToken(['user_id' => $user->id]);
|
||||
|
||||
return $this->success([
|
||||
@@ -70,6 +75,7 @@ class Auth extends BaseApi
|
||||
$username = trim($input['username'] ?? '');
|
||||
$email = trim($input['email'] ?? '');
|
||||
$password = $input['password'] ?? '';
|
||||
$invitationCode = strtoupper(preg_replace('/\s+/', '', trim((string) ($input['invitation_code'] ?? ''))));
|
||||
|
||||
if (strlen($username) < 3 || strlen($username) > 50) {
|
||||
return $this->error('用户名长度需 3-50 个字符');
|
||||
@@ -80,21 +86,59 @@ class Auth extends BaseApi
|
||||
if (strlen($password) < 6) {
|
||||
return $this->error('密码至少 6 位');
|
||||
}
|
||||
if ($invitationCode === '') {
|
||||
return $this->error('请输入邀请码', 422);
|
||||
}
|
||||
|
||||
if (User::where('username', $username)->whereOr('email', $email)->find()) {
|
||||
return $this->error('用户名或邮箱已存在');
|
||||
}
|
||||
|
||||
$defaultRoleId = \app\model\Role::where('slug', 'user')->value('id');
|
||||
$defaultRoleId = Role::where('slug', 'user')->value('id');
|
||||
$membershipId = MembershipLevel::where('slug', 'free')->value('id') ?: 1;
|
||||
|
||||
$user = User::create([
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
'password_hash' => password_hash($password, PASSWORD_BCRYPT),
|
||||
'nickname' => $username,
|
||||
'membership_level_id' => 1,
|
||||
'role_id' => $defaultRoleId ?: null,
|
||||
]);
|
||||
$user = Db::transaction(function () use (
|
||||
$invitationCode,
|
||||
$username,
|
||||
$email,
|
||||
$password,
|
||||
$defaultRoleId,
|
||||
$membershipId
|
||||
) {
|
||||
$invitation = InvitationCode::where('code', $invitationCode)->lock(true)->find();
|
||||
if (!$invitation) {
|
||||
$this->abortRegistration('邀请码不存在', 422);
|
||||
}
|
||||
if ($invitation->status !== 'active') {
|
||||
$this->abortRegistration($invitation->status === 'used' ? '邀请码已被使用' : '邀请码已作废', 422);
|
||||
}
|
||||
if ($invitation->expires_at && strtotime((string) $invitation->expires_at) <= time()) {
|
||||
$this->abortRegistration('邀请码已过期', 422);
|
||||
}
|
||||
if (User::where('username', $username)->whereOr('email', $email)->find()) {
|
||||
$this->abortRegistration('用户名或邮箱已存在', 422);
|
||||
}
|
||||
|
||||
$user = User::create([
|
||||
'username' => $username,
|
||||
'email' => $email,
|
||||
'password_hash' => password_hash($password, PASSWORD_BCRYPT),
|
||||
'nickname' => $username,
|
||||
'role' => 'user',
|
||||
'role_id' => $defaultRoleId ?: null,
|
||||
'department_id' => $invitation->department_id ?: null,
|
||||
'membership_level_id' => $membershipId,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$invitation->save([
|
||||
'status' => 'used',
|
||||
'used_by' => $user->id,
|
||||
'used_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return $user;
|
||||
});
|
||||
|
||||
$token = JwtService::generateToken(['user_id' => $user->id]);
|
||||
|
||||
@@ -157,4 +201,13 @@ class Auth extends BaseApi
|
||||
{
|
||||
return UserContextService::formatPublicUser($userId);
|
||||
}
|
||||
|
||||
private function abortRegistration(string $message, int $httpCode): never
|
||||
{
|
||||
throw new HttpResponseException(json([
|
||||
'code' => 1,
|
||||
'message' => $message,
|
||||
'data' => null,
|
||||
], $httpCode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use app\service\ComfyUIService;
|
||||
use app\service\CosyVoiceService;
|
||||
use app\service\DifyService;
|
||||
use app\service\DocumentTextService;
|
||||
use app\service\GuestAccessService;
|
||||
use app\service\OpenAIService;
|
||||
use app\service\PermissionService;
|
||||
use app\service\SettingsService;
|
||||
@@ -21,7 +22,8 @@ class Chat extends BaseApi
|
||||
{
|
||||
public function speech()
|
||||
{
|
||||
$this->authUser();
|
||||
$user = $this->authUser();
|
||||
GuestAccessService::assertAccountRequired($user, '语音对话');
|
||||
$input = $this->request->post();
|
||||
$text = trim((string) ($input['text'] ?? ''));
|
||||
|
||||
@@ -71,7 +73,8 @@ class Chat extends BaseApi
|
||||
|
||||
public function speechStream(): never
|
||||
{
|
||||
$this->authUser();
|
||||
$user = $this->authUser();
|
||||
GuestAccessService::assertAccountRequired($user, '语音对话');
|
||||
$input = $this->request->post();
|
||||
$text = trim((string) ($input['text'] ?? ''));
|
||||
$requestId = trim((string) ($input['request_id'] ?? ''));
|
||||
@@ -134,7 +137,8 @@ class Chat extends BaseApi
|
||||
|
||||
public function speechCancel()
|
||||
{
|
||||
$this->authUser();
|
||||
$user = $this->authUser();
|
||||
GuestAccessService::assertAccountRequired($user, '语音对话');
|
||||
$requestId = trim((string) $this->request->post('request_id', ''));
|
||||
if (!preg_match('/^[A-Za-z0-9._:-]{8,128}$/', $requestId)) {
|
||||
return $this->error('语音请求标识无效', 422);
|
||||
@@ -168,6 +172,7 @@ class Chat extends BaseApi
|
||||
if (!is_array($attachments)) {
|
||||
return $this->error('附件格式无效', 422);
|
||||
}
|
||||
GuestAccessService::assertTextChatOnly($user, $attachments, $agentId, $imageTool, $voiceMode);
|
||||
|
||||
if (!$conversationId) {
|
||||
return $this->error('缺少 conversation_id');
|
||||
@@ -178,10 +183,6 @@ class Chat extends BaseApi
|
||||
if ($imageTool !== '' && !$this->hasImageAttachments($attachments)) {
|
||||
return $this->error('图片处理工具需要一张原图', 422);
|
||||
}
|
||||
if (!empty($user['is_guest']) && $this->hasImageAttachments($attachments)) {
|
||||
return $this->error('游客模式不支持发送图片,请登录后重试', 403);
|
||||
}
|
||||
|
||||
$agent = AgentCatalog::find($agentId);
|
||||
if ($agentId !== '' && !$agent) {
|
||||
return $this->error('所选 Agent 不存在或已停用', 422);
|
||||
@@ -197,7 +198,17 @@ class Chat extends BaseApi
|
||||
}
|
||||
|
||||
$imageGenerationContent = $content;
|
||||
$model = OpenAIService::getModel($conversation->model_id ? (int) $conversation->model_id : null);
|
||||
if (GuestAccessService::isGuest($user)) {
|
||||
$model = GuestAccessService::model();
|
||||
if ((int) $conversation->model_id !== (int) $model->id) {
|
||||
$conversation->save([
|
||||
'model_id' => (int) $model->id,
|
||||
'external_conversation_id' => null,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$model = OpenAIService::getModel($conversation->model_id ? (int) $conversation->model_id : null);
|
||||
}
|
||||
if ($imageTool !== '' && $imageTool !== 'commit') {
|
||||
$preferredImageModelId = ($model->provider ?? '') === 'comfy' ? (int) ($model->id ?? 0) : null;
|
||||
$model = OpenAIService::getImageModel($preferredImageModelId ?: null);
|
||||
|
||||
@@ -6,6 +6,7 @@ use app\model\AiModel;
|
||||
use app\model\Conversation as ConversationModel;
|
||||
use app\model\Message;
|
||||
use app\service\ComfyUIService;
|
||||
use app\service\GuestAccessService;
|
||||
use app\service\PermissionService;
|
||||
use think\exception\HttpResponseException;
|
||||
|
||||
@@ -14,6 +15,12 @@ class Conversation extends BaseApi
|
||||
public function index()
|
||||
{
|
||||
$user = $this->authUser();
|
||||
if (GuestAccessService::isGuest($user)) {
|
||||
$guestModelId = GuestAccessService::modelId();
|
||||
$reset = ['model_id' => $guestModelId, 'external_conversation_id' => null];
|
||||
ConversationModel::where('user_id', $user['id'])->whereNull('deleted_at')->whereNull('model_id')->update($reset);
|
||||
ConversationModel::where('user_id', $user['id'])->whereNull('deleted_at')->where('model_id', '<>', $guestModelId)->update($reset);
|
||||
}
|
||||
$page = max(1, (int) $this->request->get('page', 1));
|
||||
$limit = min(50, max(1, (int) $this->request->get('limit', 20)));
|
||||
|
||||
@@ -51,6 +58,12 @@ class Conversation extends BaseApi
|
||||
$modelId = null;
|
||||
}
|
||||
|
||||
if (GuestAccessService::isGuest($user)) {
|
||||
$modelId = GuestAccessService::assertModelAllowed($user, $modelId);
|
||||
} elseif ($modelId && !AiModel::where('id', $modelId)->where('enabled', 1)->find()) {
|
||||
return $this->error('所选模型不存在或已停用', 422);
|
||||
}
|
||||
|
||||
$conversation = ConversationModel::create([
|
||||
'user_id' => $user['id'],
|
||||
'title' => trim($input['title'] ?? '新对话'),
|
||||
@@ -94,6 +107,13 @@ class Conversation extends BaseApi
|
||||
$data['model_id'] = (int) $mid;
|
||||
}
|
||||
|
||||
if (GuestAccessService::isGuest($user)) {
|
||||
$requestedModelId = $data['model_id'] ?: null;
|
||||
$data['model_id'] = GuestAccessService::assertModelAllowed($user, $requestedModelId);
|
||||
} elseif ($data['model_id'] && !AiModel::where('id', $data['model_id'])->where('enabled', 1)->find()) {
|
||||
return $this->error('所选模型不存在或已停用', 422);
|
||||
}
|
||||
|
||||
$currentModelId = $conversation->model_id === null
|
||||
? null
|
||||
: (int) $conversation->model_id;
|
||||
@@ -103,6 +123,14 @@ class Conversation extends BaseApi
|
||||
}
|
||||
}
|
||||
|
||||
if (GuestAccessService::isGuest($user) && !array_key_exists('model_id', $input)) {
|
||||
$guestModelId = GuestAccessService::modelId();
|
||||
if ((int) $conversation->model_id !== $guestModelId) {
|
||||
$data['model_id'] = $guestModelId;
|
||||
$data['external_conversation_id'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($data)) {
|
||||
return $this->error('无更新内容');
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace app\controller\api;
|
||||
use app\model\AiModel;
|
||||
use app\service\AgentCatalog;
|
||||
use app\service\CosyVoiceService;
|
||||
use app\service\GuestAccessService;
|
||||
use app\service\SettingsService;
|
||||
|
||||
class Settings extends BaseApi
|
||||
@@ -20,6 +21,7 @@ class Settings extends BaseApi
|
||||
return $this->success([
|
||||
'site_name' => SettingsService::get('site_name', 'AI Chat'),
|
||||
'allow_register' => $allow === true || $allow === 'true',
|
||||
'registration_requires_invite' => true,
|
||||
'features' => SettingsService::getFeatures(),
|
||||
'voice_persona' => CosyVoiceService::publicPersona(),
|
||||
]);
|
||||
@@ -27,6 +29,20 @@ class Settings extends BaseApi
|
||||
|
||||
public function models()
|
||||
{
|
||||
$user = $this->authUser();
|
||||
if (GuestAccessService::isGuest($user)) {
|
||||
$model = GuestAccessService::model();
|
||||
return $this->success([[
|
||||
'id' => (int) $model->id,
|
||||
'name' => $model->name,
|
||||
'provider' => $model->provider,
|
||||
'model_id' => $model->model_id,
|
||||
'is_default' => 1,
|
||||
'support_context' => (int) $model->support_context,
|
||||
'support_image' => 0,
|
||||
]]);
|
||||
}
|
||||
|
||||
$list = AiModel::where('enabled', 1)
|
||||
->field('id,name,provider,model_id,is_default,support_context,support_image')
|
||||
->order('sort_order,id')
|
||||
@@ -37,6 +53,10 @@ class Settings extends BaseApi
|
||||
|
||||
public function agents()
|
||||
{
|
||||
if (GuestAccessService::isGuest($this->authUser())) {
|
||||
return $this->success([]);
|
||||
}
|
||||
|
||||
return $this->success(AgentCatalog::publicList());
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -118,12 +118,46 @@ class Upload extends BaseApi
|
||||
}
|
||||
|
||||
$mime = $upload->mime_type ?: (@mime_content_type($path) ?: 'application/octet-stream');
|
||||
|
||||
return response(file_get_contents($path), 200, [
|
||||
'Content-Type' => $mime,
|
||||
'Content-Length' => (string) filesize($path),
|
||||
$size = (int) filesize($path);
|
||||
$headers = [
|
||||
'Content-Type' => $mime,
|
||||
'Content-Length' => (string) $size,
|
||||
'Accept-Ranges' => 'bytes',
|
||||
'Cache-Control' => 'public, max-age=604800',
|
||||
]);
|
||||
];
|
||||
|
||||
$range = trim((string) $this->request->header('range', ''));
|
||||
if ($range !== '' && preg_match('/^bytes=(\d*)-(\d*)$/', $range, $matches)) {
|
||||
$start = $matches[1] === '' ? 0 : (int) $matches[1];
|
||||
$end = $matches[2] === '' ? $size - 1 : (int) $matches[2];
|
||||
if ($matches[1] === '' && $matches[2] !== '') {
|
||||
$length = min($size, (int) $matches[2]);
|
||||
$start = $size - $length;
|
||||
$end = $size - 1;
|
||||
}
|
||||
if ($start < 0 || $start >= $size || $end < $start) {
|
||||
return response('', 416, [
|
||||
'Content-Range' => 'bytes */' . $size,
|
||||
'Accept-Ranges' => 'bytes',
|
||||
]);
|
||||
}
|
||||
$end = min($end, $size - 1);
|
||||
$length = $end - $start + 1;
|
||||
$handle = fopen($path, 'rb');
|
||||
if ($handle === false || fseek($handle, $start) !== 0) {
|
||||
if (is_resource($handle)) {
|
||||
fclose($handle);
|
||||
}
|
||||
throw new HttpResponseException(response('文件读取失败', 500));
|
||||
}
|
||||
$content = (string) fread($handle, $length);
|
||||
fclose($handle);
|
||||
$headers['Content-Length'] = (string) strlen($content);
|
||||
$headers['Content-Range'] = "bytes {$start}-{$end}/{$size}";
|
||||
return response($content, 206, $headers);
|
||||
}
|
||||
|
||||
return response(file_get_contents($path), 200, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user