| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- <?php
- namespace app\api\controller;
- use app\common\controller\Api;
- use app\common\exception\UploadException;
- use app\common\library\Upload;
- use app\common\model\Area;
- use app\common\model\Version;
- use fast\Random;
- use OSS\OssClient;
- use think\captcha\Captcha;
- use think\Config;
- use think\Hook;
- use think\Log;
- /**
- * 公共接口
- */
- class Common extends Api
- {
- protected $noNeedLogin = ['init', 'captcha'];
- protected $noNeedRight = '*';
- /**
- * 获取 OSS 配置
- */
- public static function getOssConfig(): array
- {
- $config = Config::get('oss');
- return is_array($config) ? $config : [];
- }
- /**
- * OSS 配置是否可用
- */
- public static function isOssEnabled(): bool
- {
- $config = self::getOssConfig();
- return !empty($config['accessKeyId'])
- && !empty($config['accessKeySecret'])
- && !empty($config['endpoint'])
- && !empty($config['bucket']);
- }
- /**
- * 归一化 OSS 对象键
- */
- public static function normalizeOssObjectKey(string $objectKey): string
- {
- return ltrim(str_replace('\\', '/', trim($objectKey)), '/');
- }
- /**
- * 上传本地文件到 OSS
- *
- * @param string $localFullPath 本地文件完整路径
- * 示例:D:/phpstudy_pro/WWW/mes-ai-server-api/public/uploads/material/2026-03-25/a.png
- * @param string $objectKey OSS 对象键(Bucket 内的相对路径,不要带域名)
- * 示例:uploads/material/2026-03-25/a.png
- * @return bool true=上传成功;false=未配置/本地文件不存在/上传失败
- */
- public static function uploadLocalFileToOss(string $localFullPath, string $objectKey): bool
- {
- if (!self::isOssEnabled() || !is_file($localFullPath)) {
- return false;
- }
- $config = self::getOssConfig();
- $objectKey = self::normalizeOssObjectKey($objectKey);
- if ($objectKey === '') {
- return false;
- }
- try {
- $ossClient = new OssClient(
- $config['accessKeyId'],
- $config['accessKeySecret'],
- $config['endpoint']
- );
- $ossClient->uploadFile($config['bucket'], $objectKey, $localFullPath);
- return true;
- } catch (\Throwable $e) {
- Log::write('[OSS uploadLocalFileToOss] ' . $e->getMessage() . ' | objectKey=' . $objectKey . ' | local=' . $localFullPath, 'error');
- return false;
- }
- }
- /**
- * 删除 OSS 对象
- */
- public static function deleteOssObject(string $objectKeyOrUrl): bool
- {
- if (!self::isOssEnabled()) {
- return false;
- }
- $config = self::getOssConfig();
- $objectKey = self::normalizeOssObjectKey($objectKeyOrUrl);
- if ($objectKey === '') {
- return false;
- }
- try {
- $ossClient = new OssClient(
- $config['accessKeyId'],
- $config['accessKeySecret'],
- $config['endpoint']
- );
- $ossClient->deleteObject($config['bucket'], $objectKey);
- return true;
- } catch (\Throwable $e) {
- return false;
- }
- }
- /**
- * 把相对路径拼接为完整 OSS URL;已是 http(s) 则原样返回
- * 路径$taskInfo['image_url'] = '/uploads/Product/img2img-20260317152818-69b902924548d.png'
- * Common::ossFullUrl((string)$taskInfo['image_url']);
- */
- public static function ossFullUrl(string $path): string
- {
- $path = trim($path);
- if ($path === '' || stripos($path, 'http://') === 0 || stripos($path, 'https://') === 0) {
- return $path;
- }
- $config = self::getOssConfig();
- $host = trim((string)($config['host'] ?? ''));
- if ($host === '') {
- return $path;
- }
- if (stripos($host, 'http://') !== 0 && stripos($host, 'https://') !== 0) {
- $host = 'https://' . $host;
- }
- return rtrim($host, '/') . '/' . ltrim($path, '/');
- }
- /**
- * product_template.chinese_description 入库:多页为 JSON 数组字符串,避免数组被写成 "Array"。
- *
- * @param mixed $raw 前端数组、合法 JSON 数组字符串、或历史单段纯文本
- */
- public static function encodeChineseDescriptionForDb($raw): string
- {
- if (is_array($raw)) {
- return json_encode($raw, JSON_UNESCAPED_UNICODE) ?: '[]';
- }
- if ($raw === null || $raw === '') {
- return '';
- }
- if (!is_string($raw)) {
- return '';
- }
- $trimmed = trim($raw);
- if ($trimmed === '') {
- return '';
- }
- $decoded = json_decode($trimmed, true);
- if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
- return json_encode($decoded, JSON_UNESCAPED_UNICODE) ?: $trimmed;
- }
- return $raw;
- }
- /**
- * 读出给前端:合法 JSON 数组则转 array,否则保持原字符串。
- *
- * @return array|string
- */
- public static function decodeChineseDescriptionForApi($stored)
- {
- if ($stored === null || $stored === '') {
- return [];
- }
- if (!is_string($stored)) {
- return $stored;
- }
- $decoded = json_decode($stored, true);
- if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
- return $decoded;
- }
- return $stored;
- }
- public function _initialize()
- {
- if (isset($_SERVER['HTTP_ORIGIN'])) {
- header('Access-Control-Expose-Headers: __token__');//跨域让客户端获取到
- }
- //跨域检测
- check_cors_request();
- if (!isset($_COOKIE['PHPSESSID'])) {
- Config::set('session.id', $this->request->server("HTTP_SID"));
- }
- parent::_initialize();
- }
- /**
- * 加载初始化
- *
- * @ApiParams (name="version", type="string", required=true, description="版本号")
- * @ApiParams (name="lng", type="string", required=true, description="经度")
- * @ApiParams (name="lat", type="string", required=true, description="纬度")
- */
- public function init()
- {
- if ($version = $this->request->request('version')) {
- $lng = $this->request->request('lng');
- $lat = $this->request->request('lat');
- //配置信息
- $upload = Config::get('upload');
- //如果非服务端中转模式需要修改为中转
- if ($upload['storage'] != 'local' && isset($upload['uploadmode']) && $upload['uploadmode'] != 'server') {
- //临时修改上传模式为服务端中转
- set_addon_config($upload['storage'], ["uploadmode" => "server"], false);
- $upload = \app\common\model\Config::upload();
- // 上传信息配置后
- Hook::listen("upload_config_init", $upload);
- $upload = Config::set('upload', array_merge(Config::get('upload'), $upload));
- }
- $upload['cdnurl'] = $upload['cdnurl'] ? $upload['cdnurl'] : cdnurl('', true);
- $upload['uploadurl'] = preg_match("/^((?:[a-z]+:)?\/\/)(.*)/i", $upload['uploadurl']) ? $upload['uploadurl'] : url($upload['storage'] == 'local' ? '/api/common/upload' : $upload['uploadurl'], '', false, true);
- $content = [
- 'citydata' => Area::getCityFromLngLat($lng, $lat),
- 'versiondata' => Version::check($version),
- 'uploaddata' => $upload,
- 'coverdata' => Config::get("cover"),
- ];
- $this->success('', $content);
- } else {
- $this->error(__('Invalid parameters'));
- }
- }
- /**
- * 上传文件
- * @ApiMethod (POST)
- * @ApiParams (name="file", type="file", required=true, description="文件流")
- */
- public function upload()
- {
- Config::set('default_return_type', 'json');
- //必须设定cdnurl为空,否则cdnurl函数计算错误
- Config::set('upload.cdnurl', '');
- $chunkid = $this->request->post("chunkid");
- if ($chunkid) {
- if (!Config::get('upload.chunking')) {
- $this->error(__('Chunk file disabled'));
- }
- $action = $this->request->post("action");
- $chunkindex = $this->request->post("chunkindex/d");
- $chunkcount = $this->request->post("chunkcount/d");
- $filename = $this->request->post("filename");
- $method = $this->request->method(true);
- if ($action == 'merge') {
- $attachment = null;
- //合并分片文件
- try {
- $upload = new Upload();
- $attachment = $upload->merge($chunkid, $chunkcount, $filename);
- } catch (UploadException $e) {
- $this->error($e->getMessage());
- }
- $this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
- } elseif ($method == 'clean') {
- //删除冗余的分片文件
- try {
- $upload = new Upload();
- $upload->clean($chunkid);
- } catch (UploadException $e) {
- $this->error($e->getMessage());
- }
- $this->success();
- } else {
- //上传分片文件
- //默认普通上传文件
- $file = $this->request->file('file');
- try {
- $upload = new Upload($file);
- $upload->chunk($chunkid, $chunkindex, $chunkcount);
- } catch (UploadException $e) {
- $this->error($e->getMessage());
- }
- $this->success();
- }
- } else {
- $attachment = null;
- //默认普通上传文件
- $file = $this->request->file('file');
- try {
- $upload = new Upload($file);
- $attachment = $upload->upload();
- } catch (UploadException $e) {
- $this->error($e->getMessage());
- } catch (\Exception $e) {
- $this->error($e->getMessage());
- }
- $this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
- }
- }
- /**
- * 验证码
- * @ApiParams (name="id", type="string", required=true, description="要生成验证码的标识")
- * @return \think\Response
- */
- public function captcha($id = "")
- {
- \think\Config::set([
- 'captcha' => array_merge(config('captcha'), [
- 'fontSize' => 44,
- 'imageH' => 150,
- 'imageW' => 350,
- ])
- ]);
- $captcha = new Captcha((array)Config::get('captcha'));
- return $captcha->entry($id);
- }
- }
|