bianjunhui 4 órája
szülő
commit
4cce8f721c

+ 54 - 3
application/admin/controller/Ajax.php

@@ -13,7 +13,7 @@ use think\Db;
 use think\Lang;
 use think\Lang;
 use think\Response;
 use think\Response;
 use think\Validate;
 use think\Validate;
-
+use app\common\library\AwsS3;
 /**
 /**
  * Ajax异步请求接口
  * Ajax异步请求接口
  * @internal
  * @internal
@@ -21,7 +21,7 @@ use think\Validate;
 class Ajax extends Backend
 class Ajax extends Backend
 {
 {
 
 
-    protected $noNeedLogin = ['lang'];
+    protected $noNeedLogin = ['lang','delete'];
     protected $noNeedRight = ['*'];
     protected $noNeedRight = ['*'];
     protected $layout = '';
     protected $layout = '';
 
 
@@ -52,11 +52,62 @@ class Ajax extends Backend
         $this->loadlang($controllername);
         $this->loadlang($controllername);
         return jsonp(Lang::get(), 200, $header, ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
         return jsonp(Lang::get(), 200, $header, ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
     }
     }
-
     /**
     /**
      * 上传文件
      * 上传文件
      */
      */
     public function upload()
     public function upload()
+    {
+        //默认普通上传文件
+        $file = $this->request->file('file');
+        // 验证文件
+        $config = config('aws.s3.upload');
+        $info = $file->validate([
+            'size' => $config['max_size'],
+            'ext'  => $config['exts']
+        ]);
+        if (!$info) {
+            $this->error($file->getError());
+        }
+        $website=$this->auth->username;
+        try {
+            $s3 = new AwsS3();
+            $result = $s3->upload($file,'','',$website);
+            if ($result['success']) {
+                $this->success('上传成功', null, [
+                    'url'  => $result['url'],
+                    'key'  => $result['key'],
+                    'name' => basename($result['key']),
+                    'size' => $result['size']
+                ]);
+
+            } else {
+                $this->error($result['msg']);
+            }
+        } catch (UploadException $e) {
+            $this->error($e->getMessage());
+        }
+    }
+    public function delete()
+    {
+        $key = $this->request->post('key');
+
+        if (empty($key)) {
+            $this->error('请指定要删除的文件');
+        }
+
+        $s3 = new AwsS3();
+        $result = $s3->delete($key);
+
+        if ($result) {
+            $this->success('删除成功');
+        } else {
+            $this->error('删除失败');
+        }
+    }
+    /**
+     * 上传文件
+     */
+    public function uploads()
     {
     {
         Config::set('default_return_type', 'json');
         Config::set('default_return_type', 'json');
         //必须设定cdnurl为空,否则cdnurl函数计算错误
         //必须设定cdnurl为空,否则cdnurl函数计算错误

+ 0 - 15
application/api/controller/Index.php

@@ -33,22 +33,7 @@ class Index extends Api
         unset($where);
         unset($where);
         $where['fid']=$list['id'];
         $where['fid']=$list['id'];
         $where['status']='normal';
         $where['status']='normal';
-        $domain2 = $this->request->domain();
-        if($list['image']){
-            $list['image']=$domain2.$list['image'];
-        }
-        if($list['image_m']){
-            $list['image_m']=$domain2.$list['image_m'];
-        }
         $list['details']=Db::name('functional_del')->where($where)->select();
         $list['details']=Db::name('functional_del')->where($where)->select();
-        foreach ($list['details'] as $k=>$v){
-            if($v['image']){
-                $list['details'][$k]['image']=$domain2.$v['image'];
-            }
-            if($v['image_m']){
-                $list['details'][$k]['image_m']=$domain2.$v['image_m'];
-            }
-        }
         $this->success('请求成功',$list);
         $this->success('请求成功',$list);
     }
     }
 }
 }

+ 342 - 0
application/common/library/AwsS3.php

@@ -0,0 +1,342 @@
+<?php
+namespace app\common\library;
+
+use think\Config;
+use think\Exception;
+use think\File;
+use GuzzleHttp\Client;
+use GuzzleHttp\Psr7\Request;
+
+class AwsS3
+{
+    protected $config;
+    protected $client;
+    protected $bucket;
+    protected $region;
+
+    public function __construct()
+    {
+        $this->config = Config::get('aws.s3');
+        $this->bucket = $this->config['bucket'];
+        $this->region = $this->config['region'];
+        $this->client = new Client([
+            'timeout' => 30,
+            'verify' => false, // 根据环境调整
+        ]);
+    }
+
+    /**
+     * 生成 AWS S4 签名
+     */
+    protected function generateSignature($method, $uri, $headers, $payload = '')
+    {
+        $accessKey = $this->config['credentials']['key'];
+        $secretKey = $this->config['credentials']['secret'];
+
+        $service = 's3';
+        $algorithm = 'AWS4-HMAC-SHA256';
+
+        // 当前时间
+        $timestamp = time();
+        $longDate = gmdate('Ymd\THis\Z', $timestamp);
+        $shortDate = substr($longDate, 0, 8);
+
+        // 计算签名所需组件
+        $scope = $shortDate . '/' . $this->region . '/' . $service . '/aws4_request';
+
+        // 规范化请求
+        $canonicalHeaders = '';
+        $signedHeaders = '';
+
+        ksort($headers);
+        foreach ($headers as $key => $value) {
+            $canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
+            $signedHeaders .= strtolower($key) . ';';
+        }
+        $signedHeaders = rtrim($signedHeaders, ';');
+
+        $canonicalRequest = implode("\n", [
+            $method,
+            $uri,
+            '', // 查询字符串
+            $canonicalHeaders,
+            $signedHeaders,
+            hash('sha256', $payload)
+        ]);
+
+        // 计算签名
+        $stringToSign = implode("\n", [
+            $algorithm,
+            $longDate,
+            $scope,
+            hash('sha256', $canonicalRequest)
+        ]);
+
+        // 派生签名密钥
+        $kDate = hash_hmac('sha256', $shortDate, 'AWS4' . $secretKey, true);
+        $kRegion = hash_hmac('sha256', $this->region, $kDate, true);
+        $kService = hash_hmac('sha256', $service, $kRegion, true);
+        $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
+
+        $signature = hash_hmac('sha256', $stringToSign, $kSigning);
+
+        return $algorithm . ' ' . implode(', ', [
+                'Credential=' . $accessKey . '/' . $scope,
+                'SignedHeaders=' . $signedHeaders,
+                'Signature=' . $signature
+            ]);
+    }
+
+    /**
+     * 上传文件到 S3
+     */
+    public function upload($file, $key = null, $options = [],$website)
+    {
+        $link ='https://cdn.alipearlhair.com';
+        if($website=='alipearlhair'){
+            $this->bucket ='alipearl-images';
+            $link ='https://cdn.alipearlhair.com';
+        }
+        try {
+            // 处理文件
+            if ($file instanceof File) {
+                $filePath = $file->getRealPath();
+                $fileContent = file_get_contents($filePath);
+                $mimeType = $file->getMime();
+            } elseif (is_string($file) && file_exists($file)) {
+                $fileContent = file_get_contents($file);
+                $mimeType = mime_content_type($file);
+            } else {
+                $fileContent = $file;
+                $mimeType = $options['mime_type'] ?? 'application/octet-stream';
+            }
+
+            // 生成存储 key
+            if (empty($key)) {
+                $key = $this->generateKey($file);
+            }
+            // 准备请求
+            $method = 'PUT';
+            $uri = '/' . $key;
+            $url = 'https://' . $this->bucket . '.s3.' . $this->region . '.amazonaws.com' . $uri;
+            $urls = $link . $uri;
+            $headers = [
+                'Host' => $this->bucket . '.s3.' . $this->region . '.amazonaws.com',
+                'Content-Type' => $mimeType,
+                'Content-Length' => strlen($fileContent),
+                'x-amz-content-sha256' => hash('sha256', $fileContent),
+                'x-amz-date' => gmdate('Ymd\THis\Z'),
+
+            ];
+
+            // 添加额外头部
+            if (!empty($options['headers'])) {
+                $headers = array_merge($headers, $options['headers']);
+            }
+
+            // 生成签名
+            $headers['Authorization'] = $this->generateSignature($method, $uri, $headers, $fileContent);
+
+            // 发送请求
+            $response = $this->client->request($method, $url, [
+                'headers' => $headers,
+                'body' => $fileContent,
+            ]);
+
+            if ($response->getStatusCode() == 200) {
+                return [
+                    'success' => true,
+                    'key'     => $key,
+                    'url'     => $urls,
+                    'size'    => strlen($fileContent),
+                    'type'    => $mimeType
+                ];
+            } else {
+                throw new Exception('上传失败,状态码: ' . $response->getStatusCode());
+            }
+
+        } catch (\Exception $e) {
+            return [
+                'success' => false,
+                'msg'     => '上传失败: ' . $e->getMessage(),
+                'error'   => $e->getMessage()
+            ];
+        }
+    }
+
+    /**
+     * 生成存储路径 key
+     */
+    protected function generateKey($file)
+    {
+        $config = $this->config['upload'];
+        $savePath = $config['save_path'];
+
+        // 替换路径变量
+        $replace = [
+            '{year}'     => date('Y'),
+            '{mon}'      => date('m'),
+            '{day}'      => date('d'),
+            '{hour}'     => date('H'),
+            '{min}'      => date('i'),
+            '{sec}'      => date('s'),
+            '{random}'   => mt_rand(1000, 9999),
+            '{uniqid}'   => uniqid(),
+            '{timestamp}'=> time(),
+        ];
+
+        $savePath = str_replace(array_keys($replace), array_values($replace), $savePath);
+
+        // 生成文件名
+        // 获取原始文件名和扩展名
+        $originalName = $file->getInfo('name'); // 原始文件名,如 "image.jpg"
+        $originalExtension = pathinfo($originalName, PATHINFO_EXTENSION);
+
+// 如果原始扩展名有效,使用它
+        if (!empty($originalExtension) && strlen($originalExtension) <= 6) {
+            $extension = strtolower($originalExtension);
+        } else {
+            // 否则通过内容检测
+            $extension = $this->getRealExtensionByContent($file->getRealPath());
+        }
+        $sha1 = sha1($file);
+        $filename = date('YmdHis') . '_' . substr($sha1, 0, 16) . '.' . $extension;
+        return $savePath . $filename;
+    }
+
+    /**
+     * 删除 S3 文件
+     */
+    public function delete($key)
+    {
+        try {
+            $method = 'DELETE';
+            $uri = '/' . $key;
+            $url = 'https://' . $this->bucket . '.s3.' . $this->region . '.amazonaws.com' . $uri;
+
+            $headers = [
+                'Host' => $this->bucket . '.s3.' . $this->region . '.amazonaws.com',
+                'x-amz-date' => gmdate('Ymd\THis\Z'),
+                'x-amz-content-sha256' => hash('sha256', ''),
+            ];
+
+            $headers['Authorization'] = $this->generateSignature($method, $uri, $headers);
+
+            $response = $this->client->request($method, $url, [
+                'headers' => $headers,
+            ]);
+
+            return $response->getStatusCode() == 204;
+
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 检查文件是否存在
+     */
+    public function exists($key)
+    {
+        try {
+            $method = 'HEAD';
+            $uri = '/' . $key;
+            $url = 'https://' . $this->bucket . '.s3.' . $this->region . '.amazonaws.com' . $uri;
+
+            $headers = [
+                'Host' => $this->bucket . '.s3.' . $this->region . '.amazonaws.com',
+                'x-amz-date' => gmdate('Ymd\THis\Z'),
+                'x-amz-content-sha256' => hash('sha256', ''),
+            ];
+
+            $headers['Authorization'] = $this->generateSignature($method, $uri, $headers);
+
+            $response = $this->client->request($method, $url, [
+                'headers' => $headers,
+            ]);
+
+            return $response->getStatusCode() == 200;
+
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取文件 URL
+     */
+    public function getUrl($key, $expires = 3600)
+    {
+        if ($expires === null) {
+            // 公开读取的直接 URL
+            $cdnUrl = rtrim($this->config['upload']['cdn_url'], '/');
+            return $cdnUrl . '/' . $key;
+        } else {
+            // 生成预签名 URL
+            return $this->generatePresignedUrl($key, $expires);
+        }
+    }
+
+    /**
+     * 生成预签名 URL
+     */
+    protected function generatePresignedUrl($key, $expires = 3600)
+    {
+        $accessKey = $this->config['credentials']['key'];
+        $secretKey = $this->config['credentials']['secret'];
+
+        $timestamp = time();
+        $expiresAt = $timestamp + $expires;
+        $longDate = gmdate('Ymd\THis\Z', $timestamp);
+        $shortDate = substr($longDate, 0, 8);
+
+        $service = 's3';
+        $algorithm = 'AWS4-HMAC-SHA256';
+        $scope = $shortDate . '/' . $this->region . '/' . $service . '/aws4_request';
+
+        // 生成签名
+        $canonicalQueryString = implode('&', [
+            'X-Amz-Algorithm=AWS4-HMAC-SHA256',
+            'X-Amz-Credential=' . urlencode($accessKey . '/' . $scope),
+            'X-Amz-Date=' . $longDate,
+            'X-Amz-Expires=' . $expires,
+            'X-Amz-SignedHeaders=host'
+        ]);
+
+        $canonicalRequest = implode("\n", [
+            'GET',
+            '/' . $key,
+            $canonicalQueryString,
+            'host:' . $this->bucket . '.s3.' . $this->region . '.amazonaws.com',
+            '',
+            'host',
+            'UNSIGNED-PAYLOAD'
+        ]);
+
+        $stringToSign = implode("\n", [
+            $algorithm,
+            $longDate,
+            $scope,
+            hash('sha256', $canonicalRequest)
+        ]);
+
+        // 派生签名密钥
+        $kDate = hash_hmac('sha256', $shortDate, 'AWS4' . $secretKey, true);
+        $kRegion = hash_hmac('sha256', $this->region, $kDate, true);
+        $kService = hash_hmac('sha256', $service, $kRegion, true);
+        $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
+
+        $signature = hash_hmac('sha256', $stringToSign, $kSigning);
+
+        $url = sprintf(
+            'https://%s.s3.%s.amazonaws.com/%s?%s&X-Amz-Signature=%s',
+            $this->bucket,
+            $this->region,
+            $key,
+            $canonicalQueryString,
+            $signature
+        );
+
+        return $url;
+    }
+}

+ 2 - 1
application/common/library/Upload.php

@@ -9,7 +9,8 @@ use FilesystemIterator;
 use think\Config;
 use think\Config;
 use think\File;
 use think\File;
 use think\Hook;
 use think\Hook;
-
+use app\common\controller\Backend;
+use app\common\library\AwsS3;
 /**
 /**
  * 文件上传类
  * 文件上传类
  */
  */

+ 20 - 0
application/extra/aws.php

@@ -0,0 +1,20 @@
+<?php
+return [
+    's3' => [
+        'version'     => 'latest',
+        'region'      => 'us-west-2', // 如:us-east-1
+        'credentials' => [
+            'key'    => 'AKIARRYIZIQIDUB7WQQK',
+            'secret' => 'R0hDJ8277wLfqwldrkw4LOLSGTROZS6oW5Be0pWV',
+        ],
+        'bucket'      => 'alipearl-images',
+        'endpoint'    => 'https://s3.your-region.amazonaws.com',
+        // 上传配置
+        'upload' => [
+            'max_size'  => 52428800, // 5MB
+            'exts'      => ['jpg', 'png', 'gif', 'jpeg', 'bmp'],
+            'save_path' => 'uploads/{year}{mon}/{day}/', // 保存路径格式
+            'cdn_url'   => 'https://alipearl-images.s3.us-west-2.amazonaws.com/', // CDN地址
+        ]
+    ]
+];