正在显示
10 个修改的文件
包含
449 行增加
和
5 行删除
app/Helper/FileEtag.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +namespace App\Helper; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 计算文件hash | ||
| 8 | + * | ||
| 9 | + * 七牛云的计算规则 | ||
| 10 | + */ | ||
| 11 | +class FileEtag | ||
| 12 | +{ | ||
| 13 | + const BLOCK_SIZE = 4194304; //4*1024*1024 分块上传块大小,该参数为接口规格,不能修改 | ||
| 14 | + | ||
| 15 | + private static function packArray($v, $a) | ||
| 16 | + { | ||
| 17 | + return call_user_func_array('pack', array_merge(array($v), (array)$a)); | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + private static function blockCount($fsize) | ||
| 21 | + { | ||
| 22 | + return intval(($fsize + (self::BLOCK_SIZE - 1)) / self::BLOCK_SIZE); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + private static function calcSha1($data) | ||
| 26 | + { | ||
| 27 | + $sha1Str = sha1($data, true); | ||
| 28 | + $err = error_get_last(); | ||
| 29 | + if ($err !== null) { | ||
| 30 | + return array(null, $err); | ||
| 31 | + } | ||
| 32 | + $byteArray = unpack('C*', $sha1Str); | ||
| 33 | + return array($byteArray, null); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + private static function base64_urlSafeEncode($data) | ||
| 37 | + { | ||
| 38 | + $find = array('+', '/'); | ||
| 39 | + $replace = array('-', '_'); | ||
| 40 | + return str_replace($find, $replace, base64_encode($data)); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + public static function sum($filename) | ||
| 44 | + { | ||
| 45 | + $fhandler = fopen($filename, 'r'); | ||
| 46 | + $err = error_get_last(); | ||
| 47 | + if ($err !== null) { | ||
| 48 | + return array(null, $err); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + $fstat = fstat($fhandler); | ||
| 52 | + $fsize = $fstat['size']; | ||
| 53 | + if ((int)$fsize === 0) { | ||
| 54 | + fclose($fhandler); | ||
| 55 | + return array('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', null); | ||
| 56 | + } | ||
| 57 | + $blockCnt = self::blockCount($fsize); | ||
| 58 | + $sha1Buf = array(); | ||
| 59 | + | ||
| 60 | + if ($blockCnt <= 1) { | ||
| 61 | + array_push($sha1Buf, 0x16); | ||
| 62 | + $fdata = fread($fhandler, self::BLOCK_SIZE); | ||
| 63 | + if ($err !== null) { | ||
| 64 | + fclose($fhandler); | ||
| 65 | + return array(null, $err); | ||
| 66 | + } | ||
| 67 | + list($sha1Code,) = self::calcSha1($fdata); | ||
| 68 | + $sha1Buf = array_merge($sha1Buf, $sha1Code); | ||
| 69 | + } else { | ||
| 70 | + array_push($sha1Buf, 0x96); | ||
| 71 | + $sha1BlockBuf = array(); | ||
| 72 | + for ($i = 0; $i < $blockCnt; $i++) { | ||
| 73 | + $fdata = fread($fhandler, self::BLOCK_SIZE); | ||
| 74 | + list($sha1Code, $err) = self::calcSha1($fdata); | ||
| 75 | + if ($err !== null) { | ||
| 76 | + fclose($fhandler); | ||
| 77 | + return array(null, $err); | ||
| 78 | + } | ||
| 79 | + $sha1BlockBuf = array_merge($sha1BlockBuf, $sha1Code); | ||
| 80 | + } | ||
| 81 | + $tmpData = self::packArray('C*', $sha1BlockBuf); | ||
| 82 | + list($sha1Final,) = self::calcSha1($tmpData); | ||
| 83 | + $sha1Buf = array_merge($sha1Buf, $sha1Final); | ||
| 84 | + } | ||
| 85 | + $etag = self::base64_urlSafeEncode(self::packArray('C*', $sha1Buf)); | ||
| 86 | + return array($etag, null); | ||
| 87 | + } | ||
| 88 | +} |
| 1 | +<?php | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +namespace App\Http\Controllers\Bside; | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +use App\Services\Facades\Upload; | ||
| 8 | +use Illuminate\Http\Request; | ||
| 9 | +use Illuminate\Support\Facades\Storage; | ||
| 10 | + | ||
| 11 | +class FileController extends BaseController | ||
| 12 | +{ | ||
| 13 | + /** | ||
| 14 | + * 上传 | ||
| 15 | + * @param Request $request | ||
| 16 | + * @return \Illuminate\Http\JsonResponse | ||
| 17 | + * @throws \Psr\Container\ContainerExceptionInterface | ||
| 18 | + * @throws \Psr\Container\NotFoundExceptionInterface | ||
| 19 | + * @author zbj | ||
| 20 | + * @date 2023/4/20 | ||
| 21 | + */ | ||
| 22 | + public function upload(Request $request){ | ||
| 23 | + // 上传文件 | ||
| 24 | + $files = Upload::puts('files', $this->param['config']); | ||
| 25 | + foreach ($files as &$file){ | ||
| 26 | + $file = Storage::disk('upload')->url($file); | ||
| 27 | + } | ||
| 28 | + return $this->success($files); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 下载 | ||
| 33 | + * @param Request $request | ||
| 34 | + * @return \Symfony\Component\HttpFoundation\StreamedResponse | ||
| 35 | + * @author zbj | ||
| 36 | + * @date 2023/4/20 | ||
| 37 | + */ | ||
| 38 | + public function download(Request $request){ | ||
| 39 | + $path = Upload::url2path($this->param['url']); | ||
| 40 | + return Storage::disk('upload')->download($path); | ||
| 41 | + } | ||
| 42 | +} |
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | 2 | ||
| 3 | namespace App\Providers; | 3 | namespace App\Providers; |
| 4 | 4 | ||
| 5 | -use App\Services\PaginatorServer; | 5 | +use App\Services\PaginatorService; |
| 6 | use Illuminate\Support\ServiceProvider; | 6 | use Illuminate\Support\ServiceProvider; |
| 7 | 7 | ||
| 8 | class AppServiceProvider extends ServiceProvider | 8 | class AppServiceProvider extends ServiceProvider |
| @@ -16,7 +16,7 @@ class AppServiceProvider extends ServiceProvider | @@ -16,7 +16,7 @@ class AppServiceProvider extends ServiceProvider | ||
| 16 | { | 16 | { |
| 17 | //自定义分页 | 17 | //自定义分页 |
| 18 | $this->app->bind('Illuminate\Pagination\LengthAwarePaginator',function ($app,$options){ | 18 | $this->app->bind('Illuminate\Pagination\LengthAwarePaginator',function ($app,$options){ |
| 19 | - return new PaginatorServer($options['items'], $options['total'], $options['perPage'], $options['currentPage'] , $options['options']); | 19 | + return new PaginatorService($options['items'], $options['total'], $options['perPage'], $options['currentPage'] , $options['options']); |
| 20 | }); | 20 | }); |
| 21 | } | 21 | } |
| 22 | 22 |
| @@ -13,13 +13,14 @@ class BaseService | @@ -13,13 +13,14 @@ class BaseService | ||
| 13 | { | 13 | { |
| 14 | /** | 14 | /** |
| 15 | * @notes: 手动抛出异常 | 15 | * @notes: 手动抛出异常 |
| 16 | + * @param string $msg | ||
| 16 | * @param string $code | 17 | * @param string $code |
| 17 | * @throws BsideGlobalException | 18 | * @throws BsideGlobalException |
| 18 | * @author:wlj | 19 | * @author:wlj |
| 19 | * @date: 2022/7/19 14:25 | 20 | * @date: 2022/7/19 14:25 |
| 20 | */ | 21 | */ |
| 21 | - public function fail(string $code = Code::SYSTEM_ERROR) | 22 | + public function fail(string $msg, string $code = Code::SYSTEM_ERROR) |
| 22 | { | 23 | { |
| 23 | - throw new BsideGlobalException($code); | 24 | + throw new BsideGlobalException($code, $msg); |
| 24 | } | 25 | } |
| 25 | } | 26 | } |
app/Services/Facades/Upload.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace App\Services\Facades; | ||
| 4 | + | ||
| 5 | +use Illuminate\Support\Facades\Facade; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * @see \App\Services\UploadService | ||
| 9 | + * @method static string put(string $input_name="file", string|array $config="default") 单个文件上传 | ||
| 10 | + * @method static array puts(string $input_name="file", string|array $config="default") 批量获取上传的文件 | ||
| 11 | + * @method static array filePut(string $filename, string $content, string|array $config="default") | ||
| 12 | + * @method static string url2path(string $url, string|array $disk="upload") | ||
| 13 | + */ | ||
| 14 | +class Upload extends Facade | ||
| 15 | +{ | ||
| 16 | + protected static function getFacadeAccessor() | ||
| 17 | + { | ||
| 18 | + return '\App\Services\UploadService'; | ||
| 19 | + } | ||
| 20 | +} |
| @@ -11,7 +11,7 @@ use Illuminate\Pagination\LengthAwarePaginator; | @@ -11,7 +11,7 @@ use Illuminate\Pagination\LengthAwarePaginator; | ||
| 11 | * @author zbj | 11 | * @author zbj |
| 12 | * @date 2023/4/14 | 12 | * @date 2023/4/14 |
| 13 | */ | 13 | */ |
| 14 | -class PaginatorServer extends LengthAwarePaginator | 14 | +class PaginatorService extends LengthAwarePaginator |
| 15 | { | 15 | { |
| 16 | public function toArray() | 16 | public function toArray() |
| 17 | { | 17 | { |
app/Services/UploadService.php
0 → 100644
| 1 | +<?php | ||
| 2 | + | ||
| 3 | +namespace App\Services; | ||
| 4 | + | ||
| 5 | + | ||
| 6 | +use App\Helper\FileEtag; | ||
| 7 | +use Illuminate\Support\Facades\DB; | ||
| 8 | +use Illuminate\Support\Facades\Storage; | ||
| 9 | +use Illuminate\Support\Str; | ||
| 10 | +use Illuminate\Http\UploadedFile; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * Class UploadServer | ||
| 14 | + * @package App\Services | ||
| 15 | + */ | ||
| 16 | +class UploadService extends BaseService | ||
| 17 | +{ | ||
| 18 | + | ||
| 19 | + /** | ||
| 20 | + * 默认配置 | ||
| 21 | + * @see $defaultConfig | ||
| 22 | + * @var array | ||
| 23 | + */ | ||
| 24 | + public $config = []; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 默认配置 | ||
| 28 | + * @var array | ||
| 29 | + */ | ||
| 30 | + public $defaultConfig = [ | ||
| 31 | + // 上传文件的大小范围 | ||
| 32 | + 'size' => [ | ||
| 33 | + 'max' => 1024 * 1024 * 2, // 2M | ||
| 34 | + ], | ||
| 35 | + // 磁盘 | ||
| 36 | + 'disk' => 'upload', | ||
| 37 | + // 扩展名 | ||
| 38 | + 'ext' => [ | ||
| 39 | + 'png', 'jpg', 'jpeg', 'gif' | ||
| 40 | + ], | ||
| 41 | + // 目录 | ||
| 42 | + 'path' => '', | ||
| 43 | + ]; | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * @param string|array $config | ||
| 47 | + * @author:dc | ||
| 48 | + * @time 2022/1/7 14:38 | ||
| 49 | + */ | ||
| 50 | + private function config($config = 'default') | ||
| 51 | + { | ||
| 52 | + if (is_array($config)) { | ||
| 53 | + $driver = $config['driver']; | ||
| 54 | + } else { | ||
| 55 | + $driver = $config; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + // 加载配置 | ||
| 59 | + $this->config = $this->defaultConfig; | ||
| 60 | + if ($driver != 'default') { | ||
| 61 | + $conf = config('upload.' . $driver); | ||
| 62 | + if ($conf) { | ||
| 63 | + // 合并默认配置 | ||
| 64 | + $this->config = array_merge($this->config, $conf); | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + // 追加目录 | ||
| 69 | + if ($config['path'] ?? '') { | ||
| 70 | + $this->config['path'] = $this->config['path'] . (strpos($config['path'], '/') !== 0 ? '/' : '') . $config['path']; | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + /** | ||
| 75 | + * | ||
| 76 | + * @param string $input_file | ||
| 77 | + * @param string $config | ||
| 78 | + * @return string | ||
| 79 | + * @throws \Exception | ||
| 80 | + * @author:dc | ||
| 81 | + * @time 2021/12/30 16:37 | ||
| 82 | + */ | ||
| 83 | + public function put($input_file = "file", $config = 'default'): string | ||
| 84 | + { | ||
| 85 | + $this->config($config); | ||
| 86 | + | ||
| 87 | + if ($input_file instanceof UploadedFile) { | ||
| 88 | + $file = &$input_file; | ||
| 89 | + } else { | ||
| 90 | + // 是否是已存在服务器的图片 | ||
| 91 | + if (Str::contains($input_file, env('APP_URL'))) { | ||
| 92 | + return $input_file; | ||
| 93 | + } else { | ||
| 94 | + // 获取post上传文件 | ||
| 95 | + $file = request()->file($input_file); | ||
| 96 | + // 如果不是上传,是否本地文件 | ||
| 97 | + if (!$file && is_file($input_file)) { | ||
| 98 | + // 获取文件的名字 | ||
| 99 | + $origin_name = explode(DIRECTORY_SEPARATOR, $input_file); | ||
| 100 | + !is_array($origin_name) && $origin_name = [$origin_name]; | ||
| 101 | + // 创建一个上传对象 | ||
| 102 | + $file = UploadedFile::createFromBase(new \Symfony\Component\HttpFoundation\File\UploadedFile($input_file, array_pop($origin_name))); | ||
| 103 | + } | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + if ($file) { | ||
| 109 | + // 验证扩展名 | ||
| 110 | + if (!in_array($file->extension(), $this->config['ext'])) { | ||
| 111 | + $this->fail('文件扩展名不匹配,文件扩展名支持' . implode(',', $this->config['ext'])); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + // 验证文件大小 | ||
| 115 | + if ($file->getSize() > $this->config['size']['max']) { | ||
| 116 | + $this->fail('文件大小超出' . ($this->config['size']['max'] / 1024 / 1024) . 'M'); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + //判断文件hash是否已存在 | ||
| 120 | + if ($filename = $this->getFileHash($file)) { | ||
| 121 | + return $filename; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + $pathDate = date('Y-m'); | ||
| 125 | + | ||
| 126 | + // 保存处理过的图片 | ||
| 127 | + if (!empty($img)) { | ||
| 128 | + // 保存的图片名称 | ||
| 129 | + $save_name = $this->config['path'] . '/' . $pathDate . '/' . $file->hashName(); | ||
| 130 | + // 保存文件 | ||
| 131 | + if (!Storage::disk($this->config['disk']) | ||
| 132 | + ->put($save_name, $img->getEncoded())) { | ||
| 133 | + $save_name = ''; | ||
| 134 | + } | ||
| 135 | + } else { | ||
| 136 | + // 保存文件 | ||
| 137 | + $save_name = $file->storeAs($this->config['path'] . '/' . $pathDate, $file->hashName(), $this->config['disk']); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + if ($save_name) { | ||
| 141 | + //保存文件hash | ||
| 142 | + $this->setFileHash($file, $save_name); | ||
| 143 | + // 返回地址 | ||
| 144 | + return $save_name; | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + $this->fail( '上传失败'); | ||
| 148 | + | ||
| 149 | + } | ||
| 150 | + // 异常 | ||
| 151 | + $this->fail('请上传文件'); | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + /** | ||
| 155 | + * 批量上传 | ||
| 156 | + * @param string $input_file | ||
| 157 | + * @param string $config | ||
| 158 | + * @return array | ||
| 159 | + * @throws \Exception | ||
| 160 | + * @author:dc | ||
| 161 | + * @time 2021/12/30 16:36 | ||
| 162 | + */ | ||
| 163 | + public function puts($input_file = "file", $config = 'default'): array | ||
| 164 | + { | ||
| 165 | + $request = request(); | ||
| 166 | + $files = $request->post($input_file, []); | ||
| 167 | + $files_ = $request->file($input_file); | ||
| 168 | + if ($files_) { | ||
| 169 | + $files = array_merge($files, $files_); | ||
| 170 | + } | ||
| 171 | + $filename = []; | ||
| 172 | + if ($files instanceof UploadedFile) { | ||
| 173 | + $filename[] = $this->put($files, $config); | ||
| 174 | + } else if ($files) { | ||
| 175 | + foreach ($files as $file) { | ||
| 176 | + $filename[] = $this->put($file, $config); | ||
| 177 | + } | ||
| 178 | + } else { | ||
| 179 | + // 异常 | ||
| 180 | + $this->fail('请上传文件'); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + return $filename; | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + /** | ||
| 187 | + * 上传文件,文件内容 | ||
| 188 | + * @param string $filename | ||
| 189 | + * @param string $content | ||
| 190 | + * @param string $config | ||
| 191 | + * @return string | ||
| 192 | + * @throws \App\Exceptions\BsideGlobalException | ||
| 193 | + * @author:dc | ||
| 194 | + * @time 2022/1/7 14:45 | ||
| 195 | + */ | ||
| 196 | + public function filePut(string $filename, string $content, $config = 'default') | ||
| 197 | + { | ||
| 198 | + if ($content) { | ||
| 199 | + $this->config($config); | ||
| 200 | + $save_name = $this->config['path'] . '/' . $filename; | ||
| 201 | + // 保存文件 | ||
| 202 | + $result = Storage::disk($this->config['disk'])->put($save_name, $content); | ||
| 203 | + | ||
| 204 | + if ($result) { | ||
| 205 | + // 返回地址 | ||
| 206 | + return $save_name; | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + $this->fail('上传失败'); | ||
| 210 | + | ||
| 211 | + } | ||
| 212 | + // 异常 | ||
| 213 | + $this->fail('请上传文件'); | ||
| 214 | + } | ||
| 215 | + | ||
| 216 | + /** | ||
| 217 | + * 文件hash是否存在 存在返回文件路径 | ||
| 218 | + * @param $file | ||
| 219 | + * @return mixed|string|null | ||
| 220 | + */ | ||
| 221 | + protected function getFileHash($file) | ||
| 222 | + { | ||
| 223 | + $hash = FileEtag::sum($file)[0]; | ||
| 224 | + if (!$hash) { | ||
| 225 | + return ''; | ||
| 226 | + } | ||
| 227 | + return DB::table('gl_file_hash')->where('hash', $hash)->value('filename'); | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + /** | ||
| 231 | + * 保存文件hash | ||
| 232 | + * @param $file | ||
| 233 | + * @param $save_path | ||
| 234 | + * @return bool | ||
| 235 | + */ | ||
| 236 | + protected function setFileHash($file, $save_path): bool | ||
| 237 | + { | ||
| 238 | + $hash = FileEtag::sum($file)[0]; | ||
| 239 | + | ||
| 240 | + if (!$hash) { | ||
| 241 | + return false; | ||
| 242 | + } | ||
| 243 | + $data = [ | ||
| 244 | + 'filename' => $save_path, | ||
| 245 | + 'hash' => $hash, | ||
| 246 | + ]; | ||
| 247 | + DB::table('gl_file_hash')->insert($data); | ||
| 248 | + return true; | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + /** | ||
| 252 | + * 文件地址转本地路径 | ||
| 253 | + * @param $url | ||
| 254 | + * @param string $disk | ||
| 255 | + * @return array|string|string[] | ||
| 256 | + * @author zbj | ||
| 257 | + * @date 2023/4/20 | ||
| 258 | + */ | ||
| 259 | + public function url2path($url, $disk = 'upload'){ | ||
| 260 | + $upload_url = config('filesystems')['disks'][$disk]['url']; | ||
| 261 | + return str_replace($upload_url . '/', '', $url); | ||
| 262 | + } | ||
| 263 | +} |
| @@ -53,6 +53,13 @@ return [ | @@ -53,6 +53,13 @@ return [ | ||
| 53 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), | 53 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), |
| 54 | ], | 54 | ], |
| 55 | 55 | ||
| 56 | + 'upload' => [ | ||
| 57 | + 'driver' => 'local', | ||
| 58 | + 'root' => env('UPLOAD_ROOT_PATH') ?: public_path('upload'), | ||
| 59 | + 'url' => env('UPLOAD_LOCAL_URL') ?: env('APP_URL') . '/upload', | ||
| 60 | + 'visibility' => 'public', | ||
| 61 | + ] | ||
| 62 | + | ||
| 56 | ], | 63 | ], |
| 57 | 64 | ||
| 58 | /* | 65 | /* |
config/upload.php
0 → 100644
| @@ -133,9 +133,15 @@ Route::middleware(['bloginauth'])->group(function () { | @@ -133,9 +133,15 @@ Route::middleware(['bloginauth'])->group(function () { | ||
| 133 | Route::post('/save', [\App\Http\Controllers\Bside\DeptController::class, 'save'])->name('dept_save'); | 133 | Route::post('/save', [\App\Http\Controllers\Bside\DeptController::class, 'save'])->name('dept_save'); |
| 134 | Route::any('/delete', [\App\Http\Controllers\Bside\DeptController::class, 'delete'])->name('dept_delete'); | 134 | Route::any('/delete', [\App\Http\Controllers\Bside\DeptController::class, 'delete'])->name('dept_delete'); |
| 135 | }); | 135 | }); |
| 136 | + | ||
| 137 | + //文件操作 | ||
| 138 | + Route::prefix('file')->group(function () { | ||
| 139 | + Route::post('/upload', [\App\Http\Controllers\Bside\FileController::class, 'upload'])->name('file_upload'); | ||
| 140 | + }); | ||
| 136 | }); | 141 | }); |
| 137 | 142 | ||
| 138 | //无需登录验证的路由组 | 143 | //无需登录验证的路由组 |
| 139 | Route::group([], function () { | 144 | Route::group([], function () { |
| 140 | Route::any('/login', [\App\Http\Controllers\Bside\ComController::class, 'login'])->name('login'); | 145 | Route::any('/login', [\App\Http\Controllers\Bside\ComController::class, 'login'])->name('login'); |
| 146 | + Route::get('/file/download', [\App\Http\Controllers\Bside\FileController::class, 'download'])->name('file_download'); | ||
| 141 | }); | 147 | }); |
-
请 注册 或 登录 后发表评论