From 6f82d5ff72b03dd86c8122a9e2b766d49d639622 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Wed, 2 Jul 2025 17:12:38 +0800 Subject: [PATCH 001/120] =?UTF-8?q?=E5=B7=A5=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UploadController.php | 117 ++++++ app/common/service/OssService.php | 59 +++ .../controller/AttachmentController.php | 11 + app/ticket/controller/BaseController.php | 51 +++ app/ticket/controller/TicketController.php | 46 +++ app/ticket/logic/AttachmentLogic.php | 82 ++++ app/ticket/logic/TicketLogic.php | 61 +++ app/ticket/model/Attachment.php | 66 +++ app/ticket/model/Ticket.php | 73 ++++ app/ticket/service/TicketService.php | 54 +++ composer.json | 4 +- composer.lock | 377 +++++++++++++++++- config/plugin/shopwwi/filesystem/app.php | 116 ++++++ config/route.php | 11 +- 14 files changed, 1125 insertions(+), 3 deletions(-) create mode 100644 app/common/controller/UploadController.php create mode 100644 app/common/service/OssService.php create mode 100644 app/ticket/controller/AttachmentController.php create mode 100644 app/ticket/controller/BaseController.php create mode 100644 app/ticket/controller/TicketController.php create mode 100644 app/ticket/logic/AttachmentLogic.php create mode 100644 app/ticket/logic/TicketLogic.php create mode 100644 app/ticket/model/Attachment.php create mode 100644 app/ticket/model/Ticket.php create mode 100644 app/ticket/service/TicketService.php create mode 100644 config/plugin/shopwwi/filesystem/app.php diff --git a/app/common/controller/UploadController.php b/app/common/controller/UploadController.php new file mode 100644 index 0000000..c01f084 --- /dev/null +++ b/app/common/controller/UploadController.php @@ -0,0 +1,117 @@ +input('scene', 'image'); + $file = $request->file('file'); + if ($file && $file->isValid()) { + $pathUrl = '/uploads/'.$scene.'/'.date('Ymd').'/'.md5($file->getUploadName() . time()).'.'.$file->getUploadExtension(); + $fullPathUrl = public_path() . $pathUrl; + $file->move($fullPathUrl); + $data = [ + 'domain' => getenv('MAIN_DOMAIN'), + 'file_url' => $pathUrl, + 'full_url' => getenv('MAIN_DOMAIN') . $pathUrl, + 'scene' => $scene, + ]; + return $this->success($data, 'upload success'); + } + return $this->error('file not found'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + // 上传单文件附件 + public function uploadFile(Request $request) + { + try { + $file = $request->file('file'); + + if (!$file || !$file->isValid()) { + return $this->error('上传失败'); + } + + $result = Storage::adapter('oss') + ->path('vehicle_album/'.date('Ymd')) + ->size(1024*1024*5) + ->upload($file); + + $ossservice = new OssService(); + + $data = [ + 'file_name' => $result->file_name, + 'file_url' => $ossservice->generatePrivateUrl($result->file_name) + ]; + + return $this->success($data); + }catch (\Throwable $e){ + return $this->error($e->getMessage()); + } + } + + // 上传单文件图片 + public function uploadImage(Request $request) + { + try { + $file = $request->file('image'); + + if (!$file || !$file->isValid()) { + return $this->error('上传失败'); + } + + $result = Storage::adapter('oss') + ->path('vehicle_album/'.date('Ymd')) + ->size(1024*1024*5) + ->extYes(['image/jpeg','image/png']) + ->extNo(['image/gif']) + ->upload($file); + + $ossservice = new OssService(); + + $data = [ + 'image_name' => $result->file_name, + 'image_url' => $ossservice->generatePrivateUrl($result->file_name) + ]; + + return $this->success($data); + }catch (\Throwable $e){ + return $this->error($e->getMessage()); + } + } + + public function proxy(Request $request) + { + $filePath = $request->get('file'); // 原OSS文件路径 + try { + // 生成签名URL + $signedUrl = (new \app\common\service\OssService())->generatePrivateUrl($filePath); + + // 获取图片数据并返回 + $imageData = file_get_contents($signedUrl); + + // 根据文件类型设置Content-Type + $mime = mime_content_type('data://application/octet-stream;base64,'.base64_encode($imageData)); + + return response($imageData, 200, [ + 'Content-Type' => $mime, + 'Cache-Control' => 'public, max-age=600' // 客户端缓存10分钟 + ]); + } catch (\Exception $e) { + return response('Image not found '.$e->getMessage(), 404); + } + } +} \ No newline at end of file diff --git a/app/common/service/OssService.php b/app/common/service/OssService.php new file mode 100644 index 0000000..4d75b1f --- /dev/null +++ b/app/common/service/OssService.php @@ -0,0 +1,59 @@ +accessKeyId = getenv('OSS_ACCESSID'); + $this->accessKeySecret = getenv('OSS_ACCESSSECRET'); + $this->bucket = getenv('OSS_BUCKET'); + $this->endpoint = getenv('OSS_ENDPOINT'); + $this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint); + } + + /** + * 上传图片到 OSS + * @param string $filePath 本地文件路径 + * @param string $objectKey OSS上存储的对象名称 + * @return string 图片的 URL + */ + public function uploadImage($filePath, $objectKey) + { + try { + // 上传文件到 OSS + $this->ossClient->uploadFile($this->bucket, $objectKey, $filePath); + // 构造图片的私有 URL + $url = $this->generatePrivateUrl($objectKey); + return $url; + } catch (OssException $e) { + // 错误处理 + throw new \RuntimeException('OSS Error: ' . $e->getMessage()); + } catch (\Throwable $th) { + // 错误处理 + throw new BusinessException('Error: ' . $th->getMessage()); + } + } + + /** + * 生成图片的私有访问 URL + * @param string $objectKey + * @return string + */ + public function generatePrivateUrl($objectKey, $expireTime = 3600) + { + // 生成 OSS 私有 URL(有效期为 3600 秒) + return $this->ossClient->signUrl($this->bucket, $objectKey, $expireTime); + } +} \ No newline at end of file diff --git a/app/ticket/controller/AttachmentController.php b/app/ticket/controller/AttachmentController.php new file mode 100644 index 0000000..94e1724 --- /dev/null +++ b/app/ticket/controller/AttachmentController.php @@ -0,0 +1,11 @@ +scene($scene); + } + // 执行数据验证 + if (!$validator->check($data)) { + // 验证失败,抛出业务异常,异常信息为验证器返回的错误信息 + throw new BusinessException($validator->getError()); + } + // 验证成功,返回原始数据 + return $data; + } + + public function page($result){ + $current = $result->currentPage(); + $total = $result->total(); + $size = $result->perPage(); + $result = $result->toArray(); + $result['current'] = $current; + $result['total'] = $total; + $result['size'] = $size; + return $result; + } +} \ No newline at end of file diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php new file mode 100644 index 0000000..ff3defa --- /dev/null +++ b/app/ticket/controller/TicketController.php @@ -0,0 +1,46 @@ +list($request->all()); + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + public function add(Request $request) + { + try { + $request->setPost('user_id', 1); + + return (new TicketService())->add($request->all()) + ? $this->success() + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } + + public function edit(Request $request) + { + try { + $request->setPost('user_id', 1); + $result = (new TicketService())->edit($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } + +} diff --git a/app/ticket/logic/AttachmentLogic.php b/app/ticket/logic/AttachmentLogic.php new file mode 100644 index 0000000..ad3f52d --- /dev/null +++ b/app/ticket/logic/AttachmentLogic.php @@ -0,0 +1,82 @@ +$v){ + $v['ticket_id'] = $ticket_id; + $v['user_id'] = $user_id; + $res = Attachment::create($v); + } + return $res; + } catch (\Throwable $e) { + Log::error('工单创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException($e->getMessage().'工单创建失败,请稍后重试', 500); + } + } + + public function edit(array $data,int $ticket_id,int $user_id) + { + try { + $attachmentsId = []; + foreach ($data as $k=>$v){ + $v['ticket_id'] = $ticket_id; + $v['user_id'] = $user_id; + if(!empty($v['id'])){ //修改附件 + $attachmentId = $v['id']; + $attachmentsId[] = $attachmentId; +// $v_res = Attachment::update($attachmentId, $v); + $v_res = Attachment::updateOrCreate(['id' => $v['id']],$v); + if (!$v_res) { + throw new BusinessException('附件保存失败'); + } + } else { //新增附件 + unset($v['id']); + $v_res = Attachment::create($v); + if (!$v_res) { + throw new BusinessException('附件创建失败'); + } + $vId = $v_res->id; + $attachmentsId[] = $vId; + } + } + + //删除本次没有出现的附件 + $res = Attachment::whereNotIn('id', $attachmentsId)->where('ticket_id',$ticket_id)->delete(); + return $res; + } catch (\Throwable $e) { + Log::error('工单创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException($e->getMessage().'工单创建失败,请稍后重试', 500); + } + } + + public static function generateSimpleTicketNo(): string + { + $date = date('Ymd'); // 日期 + $prefix = 'GD'; // 前缀 + $random = strtoupper(substr(md5(uniqid()), 0, 5)); // 5位随机字母数字 + + return $prefix . $date . '-' . $random; // 如 GD20240702-AB3X9Z + } +} \ No newline at end of file diff --git a/app/ticket/logic/TicketLogic.php b/app/ticket/logic/TicketLogic.php new file mode 100644 index 0000000..0037879 --- /dev/null +++ b/app/ticket/logic/TicketLogic.php @@ -0,0 +1,61 @@ +where($where)->paginate($size, ['*'], 'page', $current); + } + public function add(array $data) + { + $data['status'] = 1; + $data['code'] = self::generateSimpleTicketNo(); + try { + return Ticket::create($data); + } catch (\Throwable $e) { + Log::error('工单创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException($e->getMessage().'工单创建失败,请稍后重试', 500); + } + } + public function edit(array $data) + { + try { + unset($data['code']); + $ticket = Ticket::updateOrCreate(['id' => $data['id']],$data); + return $ticket; + } catch (\Throwable $e) { + Log::error('工单编辑失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException($e->getMessage().'工单提取方法…失败,请稍后重试', 500); + } + } + + public static function generateSimpleTicketNo(): string + { + $date = date('Ymd'); // 日期 + $prefix = 'GD'; // 前缀 + $random = strtoupper(substr(md5(uniqid()), 0, 5)); // 5位随机字母数字 + + return $prefix . $date . '-' . $random; // 如 GD20240702-AB3X9Z + } +} \ No newline at end of file diff --git a/app/ticket/model/Attachment.php b/app/ticket/model/Attachment.php new file mode 100644 index 0000000..b29af53 --- /dev/null +++ b/app/ticket/model/Attachment.php @@ -0,0 +1,66 @@ +belongsTo(Ticket::class, 'ticket_id', 'id'); + } +} diff --git a/app/ticket/model/Ticket.php b/app/ticket/model/Ticket.php new file mode 100644 index 0000000..0327a87 --- /dev/null +++ b/app/ticket/model/Ticket.php @@ -0,0 +1,73 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 + ]; + + protected $fillable = [ + 'code', + 'categorie_id', + 'content', + 'sort', + 'status', + 'user_id' + ]; + + public function attachments(){ + return $this->hasMany(Attachment::class, 'ticket_id', 'id'); + } +} diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php new file mode 100644 index 0000000..650e163 --- /dev/null +++ b/app/ticket/service/TicketService.php @@ -0,0 +1,54 @@ +list($params); + return $res; + } + + public function add(array $data) + { + try { + $attachment = $data['attachment']??[]; + $logic = new TicketLogic(); + $result = $logic->add($data); + if(!empty($attachment)){ + (new AttachmentLogic())->adds($attachment, $result->id, $data['user_id']); + } + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + + public function edit(array $data) + { + try { + Db::beginTransaction(); + $attachment = $data['attachment']??[]; + $logic = new TicketLogic(); + $result = $logic->edit($data); + + if(!empty($attachment)){ + (new AttachmentLogic())->edit($attachment, $result->id, $data['user_id']); + } + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } +} diff --git a/composer.json b/composer.json index 9e2dd39..20cf41f 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,9 @@ "vlucas/phpdotenv": "^5.6", "firebase/php-jwt": "^6.11", "league/oauth2-server": "^9.2", - "topthink/think-validate": "^3.0" + "topthink/think-validate": "^3.0", + "shopwwi/webman-filesystem": "1.1", + "shopwwi/filesystem-oss": "^1.0" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index e10e796..d34c290 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,59 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eecf8b50860214e48bdcb8407fd40e14", + "content-hash": "7421030f864dacf31b06378e1045d034", "packages": [ + { + "name": "aliyuncs/oss-sdk-php", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/aliyun/aliyun-oss-php-sdk.git", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "php-coveralls/php-coveralls": "*", + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "OSS\\": "src/OSS" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aliyuncs", + "homepage": "http://www.aliyun.com" + } + ], + "description": "Aliyun OSS SDK for PHP", + "homepage": "http://www.aliyun.com/product/oss/", + "support": { + "issues": "https://github.com/aliyun/aliyun-oss-php-sdk/issues", + "source": "https://github.com/aliyun/aliyun-oss-php-sdk/tree/v2.7.2" + }, + "time": "2024-10-28T10:41:12+00:00" + }, { "name": "brick/math", "version": "0.13.1", @@ -1997,6 +2048,212 @@ }, "time": "2024-09-04T16:06:53+00:00" }, + { + "name": "league/flysystem", + "version": "3.29.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0", + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.29.0" + }, + "time": "2024-09-29T11:59:11+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.29.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + }, + "time": "2024-08-09T21:24:39+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-01-28T23:22:08+00:00" + }, { "name": "league/oauth2-server", "version": "9.2.0", @@ -3267,6 +3524,124 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "shopwwi/filesystem-oss", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/shopwwi/filesystem-oss.git", + "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shopwwi/filesystem-oss/zipball/85cb385d1a5d11ca3c22bc485a7b18c588fe7645", + "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "aliyuncs/oss-sdk-php": "^2.4", + "league/flysystem": "^2.1|^3.0", + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Shopwwi\\FilesystemOss\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tycoon Song", + "email": "8988354@qq.com" + } + ], + "description": "AliYun OSS adapter for flysystem V2. aliyuncs/oss-sdk-php ~2.4", + "homepage": "https://github.com/phcent/filesystem-oss", + "keywords": [ + "Flysystem", + "aliyun-oss", + "flysystem-aliyun-oss" + ], + "support": { + "issues": "https://github.com/shopwwi/filesystem-oss/issues", + "source": "https://github.com/shopwwi/filesystem-oss/tree/v1.0.3" + }, + "time": "2022-10-01T03:00:18+00:00" + }, + { + "name": "shopwwi/webman-filesystem", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/shopwwi/webman-filesystem.git", + "reference": "26ac82515d3787943573c50d1a6f0ca339f0d3d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shopwwi/webman-filesystem/zipball/26ac82515d3787943573c50d1a6f0ca339f0d3d4", + "reference": "26ac82515d3787943573c50d1a6f0ca339f0d3d4", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "^2.3|^3.0", + "php": ">=7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Shopwwi\\WebmanFilesystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tycoon Song", + "email": "8988354@qq.com" + } + ], + "description": "Flysystem V2 adapter for the webman", + "keywords": [ + "cos", + "filesystem", + "oss", + "qiniu", + "webman" + ], + "support": { + "issues": "https://github.com/shopwwi/webman-filesystem/issues", + "source": "https://github.com/shopwwi/webman-filesystem/tree/v1.1.0" + }, + "time": "2024-01-21T08:03:36+00:00" + }, { "name": "symfony/cache", "version": "v7.3.0", diff --git a/config/plugin/shopwwi/filesystem/app.php b/config/plugin/shopwwi/filesystem/app.php new file mode 100644 index 0000000..f1c74b7 --- /dev/null +++ b/config/plugin/shopwwi/filesystem/app.php @@ -0,0 +1,116 @@ + true, + 'default' => 'local', + 'max_size' => 1024 * 1024 * 10, //单个文件大小10M + 'ext_yes' => [], //允许上传文件类型 为空则为允许所有 + 'ext_no' => [], // 不允许上传文件类型 为空则不限制 + 'storage' => [ + 'public' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class, + 'root' => public_path(), + 'url' => '//127.0.0.1:8787' // 静态文件访问域名 + ], + 'local' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class, + 'root' => runtime_path(), + 'url' => '//127.0.0.1:8787' // 静态文件访问域名 + ], + 'ftp' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class, + 'host' => 'ftp.example.com', + 'username' => 'username', + 'password' => 'password', + 'url' => '' // 静态文件访问域名 + // 'port' => 21, + // 'root' => '/path/to/root', + // 'passive' => true, + // 'ssl' => true, + // 'timeout' => 30, + // 'ignorePassiveAddress' => false, + // 'timestampsOnUnixListingsEnabled' => true, + ], + 'memory' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\MemoryAdapterFactory::class, + ], + 's3' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => false, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'minio' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => true, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'oss' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class, + 'accessId' => getenv('OSS_ACCESSID'), + 'accessSecret' => getenv('OSS_ACCESSSECRET'), + 'bucket' => getenv('OSS_BUCKET'), + 'endpoint' => getenv('OSS_ENDPOINT'), + 'url' => getenv('OSS_STATIC_URL'), // 静态文件访问域名 + // 'timeout' => 3600, + // 'connectTimeout' => 10, + // 'isCName' => false, + // 'token' => null, + // 'proxy' => null, + ], + 'qiniu' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class, + 'accessKey' => 'QINIU_ACCESS_KEY', + 'secretKey' => 'QINIU_SECRET_KEY', + 'bucket' => 'QINIU_BUCKET', + 'domain' => 'QINBIU_DOMAIN', + 'url' => '' // 静态文件访问域名 + ], + 'cos' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class, + 'region' => 'COS_REGION', + 'app_id' => 'COS_APPID', + 'secret_id' => 'COS_SECRET_ID', + 'secret_key' => 'COS_SECRET_KEY', + // 可选,如果 bucket 为私有访问请打开此项 + // 'signed_url' => false, + 'bucket' => 'COS_BUCKET', + 'read_from_cdn' => false, + 'url' => '' // 静态文件访问域名 + // 'timeout' => 60, + // 'connect_timeout' => 60, + // 'cdn' => '', + // 'scheme' => 'https', + ], + ], +]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index 8870d47..fc12662 100644 --- a/config/route.php +++ b/config/route.php @@ -31,4 +31,13 @@ Route::group('/common', function () { Route::post('/login', [app\common\controller\AuthController::class, 'login']); Route::post('/register', [app\common\controller\AuthController::class, 'register']); }); -}); \ No newline at end of file +}); + + +Route::group('/ticket', function () { + Route::post('/ticket/add', [app\ticket\controller\TicketController::class, 'add']); + Route::post('/ticket/edit', [app\ticket\controller\TicketController::class, 'edit']); + Route::get('/ticket/list', [app\ticket\controller\TicketController::class, 'index']); + Route::post('/upload', [app\common\controller\UploadController::class, 'uploadFile']); +}); + From 9d7957d506caed71c0965b781bb515605552251c Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 10:41:55 +0800 Subject: [PATCH 002/120] =?UTF-8?q?=E8=B7=A8=E5=9F=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/middleware/AccessControl.php | 26 ++++++++++++++++++++++++++ config/middleware.php | 6 +++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 app/middleware/AccessControl.php diff --git a/app/middleware/AccessControl.php b/app/middleware/AccessControl.php new file mode 100644 index 0000000..2a93d79 --- /dev/null +++ b/app/middleware/AccessControl.php @@ -0,0 +1,26 @@ +method() == 'OPTIONS' ? response('') : $handler($request); + + // 给响应添加跨域相关的http头 + $response->withHeaders([ + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Allow-Origin' => $request->header('origin', '*'), + 'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'), + 'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'), + ]); + + return $response; + } +} \ No newline at end of file diff --git a/config/middleware.php b/config/middleware.php index 8e964ed..01e8367 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -12,4 +12,8 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ -return []; \ No newline at end of file +return [ + '' => [ + app\middleware\AccessControl::class + ], +]; \ No newline at end of file From 6ef45714454bb7a1dff3c2773459a3f7d0528cbb Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 11:19:51 +0800 Subject: [PATCH 003/120] 1 --- app/ticket/controller/TicketController.php | 2 ++ config/route.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php index ff3defa..ad9f192 100644 --- a/app/ticket/controller/TicketController.php +++ b/app/ticket/controller/TicketController.php @@ -3,6 +3,7 @@ namespace app\ticket\controller; use app\ticket\service\TicketService; +use support\Log; use support\Request; class TicketController extends BaseController @@ -11,6 +12,7 @@ class TicketController extends BaseController public function index(Request $request) { try { + log::info('param',$request->all()); $result = (new TicketService())->list($request->all()); return $this->success($this->page($result)); } catch (\Throwable $e) { diff --git a/config/route.php b/config/route.php index fc12662..8d1c4a1 100644 --- a/config/route.php +++ b/config/route.php @@ -37,7 +37,7 @@ Route::group('/common', function () { Route::group('/ticket', function () { Route::post('/ticket/add', [app\ticket\controller\TicketController::class, 'add']); Route::post('/ticket/edit', [app\ticket\controller\TicketController::class, 'edit']); - Route::get('/ticket/list', [app\ticket\controller\TicketController::class, 'index']); + Route::any('/ticket/list', [app\ticket\controller\TicketController::class, 'index']); Route::post('/upload', [app\common\controller\UploadController::class, 'uploadFile']); }); From 78447737ea8a857fc3b943820662d6fcf508ede3 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 11:23:39 +0800 Subject: [PATCH 004/120] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/controller/TicketController.php | 2 -- config/route.php | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php index ad9f192..ff3defa 100644 --- a/app/ticket/controller/TicketController.php +++ b/app/ticket/controller/TicketController.php @@ -3,7 +3,6 @@ namespace app\ticket\controller; use app\ticket\service\TicketService; -use support\Log; use support\Request; class TicketController extends BaseController @@ -12,7 +11,6 @@ class TicketController extends BaseController public function index(Request $request) { try { - log::info('param',$request->all()); $result = (new TicketService())->list($request->all()); return $this->success($this->page($result)); } catch (\Throwable $e) { diff --git a/config/route.php b/config/route.php index 8d1c4a1..a1b8c64 100644 --- a/config/route.php +++ b/config/route.php @@ -35,9 +35,9 @@ Route::group('/common', function () { Route::group('/ticket', function () { - Route::post('/ticket/add', [app\ticket\controller\TicketController::class, 'add']); - Route::post('/ticket/edit', [app\ticket\controller\TicketController::class, 'edit']); + Route::any('/ticket/add', [app\ticket\controller\TicketController::class, 'add']); + Route::any('/ticket/edit', [app\ticket\controller\TicketController::class, 'edit']); Route::any('/ticket/list', [app\ticket\controller\TicketController::class, 'index']); - Route::post('/upload', [app\common\controller\UploadController::class, 'uploadFile']); + Route::any('/upload', [app\common\controller\UploadController::class, 'uploadFile']); }); From 8d36ffe13baebd0d85daba7a30ede7815f5eda1c Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 15:28:19 +0800 Subject: [PATCH 005/120] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E6=B2=9F=E9=80=9A?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConversationController.php | 46 +++++++++++++++++ app/ticket/controller/TicketController.php | 25 +++++++++- app/ticket/logic/ConversationLogic.php | 46 +++++++++++++++++ app/ticket/logic/TicketLogic.php | 6 +++ app/ticket/model/Conversation.php | 33 ++++++++++++ app/ticket/service/ConversationService.php | 50 +++++++++++++++++++ app/ticket/service/TicketService.php | 48 ++++++++++++++++++ config/constants/Constants.php | 37 ++++++++++++++ config/route.php | 10 ++++ 9 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 app/ticket/controller/ConversationController.php create mode 100644 app/ticket/logic/ConversationLogic.php create mode 100644 app/ticket/model/Conversation.php create mode 100644 app/ticket/service/ConversationService.php create mode 100644 config/constants/Constants.php diff --git a/app/ticket/controller/ConversationController.php b/app/ticket/controller/ConversationController.php new file mode 100644 index 0000000..0e1b7ec --- /dev/null +++ b/app/ticket/controller/ConversationController.php @@ -0,0 +1,46 @@ +input('ticket_id'); + if(empty($ticket_id)){ + return $this->error('ticket_id不能为空'); + } + $result = (new ConversationService())->list($request->all()); + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage().$e->getLine().$e->getFile()); + } + } + public function add(Request $request) + { + try { + $request->setPost('user_id', 1); + return (new ConversationService())->add($request->all()) + ? $this->success() + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } + public function edit(Request $request) + { + try { + $request->setPost('user_id', 1); + $result = (new ConversationService())->edit($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } +} \ No newline at end of file diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php index ff3defa..cc33459 100644 --- a/app/ticket/controller/TicketController.php +++ b/app/ticket/controller/TicketController.php @@ -39,7 +39,30 @@ class TicketController extends BaseController ? $this->success($result) : $this->error(); } catch (\Throwable $th) { - return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + return $this->error($th->getMessage()); + } + } + public function status(Request $request) + { + try { + $request->setPost('user_id', 1); + $result = (new TicketService())->status($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage()); + } + } + public function getStatus(Request $request) + { + try { + $result = (new TicketService())->getStatus($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage()); } } diff --git a/app/ticket/logic/ConversationLogic.php b/app/ticket/logic/ConversationLogic.php new file mode 100644 index 0000000..6ce3cbf --- /dev/null +++ b/app/ticket/logic/ConversationLogic.php @@ -0,0 +1,46 @@ +paginate($size, ['*'], 'page', $current); + } + public function add(array $data) + { + try { + return Conversation::create($data); + } catch (\Throwable $e) { + Log::error('会话创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('会话创建失败,请稍后重试', 500); + } + } + public function edit(array $data) + { + try { + $conversation = Conversation::updateOrCreate(['id' => $data['id']], $data); + return $conversation; + } catch (\Throwable $e) { + Log::error('会话编辑失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException($e->getMessage().'会话编辑失败,请稍后重试', 500); + } + } +} \ No newline at end of file diff --git a/app/ticket/logic/TicketLogic.php b/app/ticket/logic/TicketLogic.php index 0037879..16fb5ce 100644 --- a/app/ticket/logic/TicketLogic.php +++ b/app/ticket/logic/TicketLogic.php @@ -50,6 +50,12 @@ class TicketLogic } } + public function find(int $id) + { + $ticket = Ticket::find($id); + return $ticket; + } + public static function generateSimpleTicketNo(): string { $date = date('Ymd'); // 日期 diff --git a/app/ticket/model/Conversation.php b/app/ticket/model/Conversation.php new file mode 100644 index 0000000..8839455 --- /dev/null +++ b/app/ticket/model/Conversation.php @@ -0,0 +1,33 @@ + 'datetime:Y-m-d H:i:s', + 'created_at' => 'datetime:Y-m-d H:i:s', + ]; + protected $fillable = [ + 'ticket_id', + 'user_id', + 'content', + ]; +} \ No newline at end of file diff --git a/app/ticket/service/ConversationService.php b/app/ticket/service/ConversationService.php new file mode 100644 index 0000000..cc3ef84 --- /dev/null +++ b/app/ticket/service/ConversationService.php @@ -0,0 +1,50 @@ +list($params); + + $status_fanyi = array_map(fn($v) => "{{{$v}}}", array_flip(Constants::TICKET_STATUS)); + foreach ($result as $key => $value) { + $result[$key]['content'] = str_replace($status_fanyi,$ticket_status,$value['content']); + } + return $result; + } + + public function add(array $data) + { + try { + return (new ConversationLogic())->add($data); + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + + public function edit(array $data) + { + try { + Db::beginTransaction(); + $result = (new ConversationLogic())->edit($data); + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } +} \ No newline at end of file diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index 650e163..f01d9c2 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -3,14 +3,17 @@ namespace app\ticket\service; use app\ticket\logic\AttachmentLogic; +use app\ticket\logic\ConversationLogic; use app\ticket\logic\TicketLogic; use app\ticket\model\Ticket; +use config\constants\Constants; use support\Db; use support\exception\BusinessException; use support\Log; class TicketService { + public function list($params) { $res = (new TicketLogic())->list($params); @@ -51,4 +54,49 @@ class TicketService throw $e; } } + + public function status(array $data) + { + try { + Db::beginTransaction(); + $msg = $data['msg']??[]; + $ticket = (new TicketLogic())->find($data['id']); + if(empty($ticket)){ + throw new BusinessException('工单不存在'); + } + if($data['status'] == $ticket->status){ + throw new BusinessException('状态未修改'); + } + +// if(!in_array($ticket->status, [Constants::TICKET_STATUS['PENDING'], Constants::TICKET_STATUS['CONFIRM']]) +// && empty($msg)){ +// throw new BusinessException('提交失败'); +// } + + $ticket->status = $data['status']; + $result = $ticket->save(); + + if(!empty($msg)){ + $conversation = [ + 'ticket_id' => $ticket->id, + 'user_id' => $data['user_id'], + 'content' => '状态更新为{{'.array_search($data['status'],Constants::TICKET_STATUS).'}}:'.$msg + ]; + $res = (new ConversationLogic())->add($conversation); + } + + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + + public function getStatus(array $data) + { + $locale = locale(); + $ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN; + return $ticket_status; + } } diff --git a/config/constants/Constants.php b/config/constants/Constants.php new file mode 100644 index 0000000..e4c9284 --- /dev/null +++ b/config/constants/Constants.php @@ -0,0 +1,37 @@ + 1, // 待受理 + 'PROCESS' => 2, //处理中 + 'CONFIRM' => 3, // 待确认 + 'FINISH' => 4, // 已完成 + 'REJECTED' => 5, // 已驳回 + ]; + + public const TICKET_STATUS_ZH = [ + self::TICKET_STATUS['PENDING'] => '待受理', + self::TICKET_STATUS['PROCESS'] => '处理中', + self::TICKET_STATUS['CONFIRM'] => '待确认', + self::TICKET_STATUS['FINISH'] => '已完成', + self::TICKET_STATUS['REJECTED'] => '已驳回', + ]; + public const TICKET_STATUS_EN = [ + self::TICKET_STATUS['PENDING'] => 'Pending acceptance', + self::TICKET_STATUS['PROCESS'] => 'Processing', + self::TICKET_STATUS['CONFIRM'] => 'To be confirmed', + self::TICKET_STATUS['FINISH'] => 'Completed', + self::TICKET_STATUS['REJECTED'] => 'Rejected', + ]; + public const TICKET_STATUS_RU = [ + self::TICKET_STATUS['PENDING'] => 'Подлежит рассмотрению', + self::TICKET_STATUS['PROCESS'] => 'В процессе обработки', + self::TICKET_STATUS['CONFIRM'] => 'Подлежит подтверждению', + self::TICKET_STATUS['FINISH'] => 'Завершено', + self::TICKET_STATUS['REJECTED'] => 'Отклонено', + ]; + +} \ No newline at end of file diff --git a/config/route.php b/config/route.php index a1b8c64..6a53a53 100644 --- a/config/route.php +++ b/config/route.php @@ -35,9 +35,19 @@ Route::group('/common', function () { Route::group('/ticket', function () { + //工单 Route::any('/ticket/add', [app\ticket\controller\TicketController::class, 'add']); Route::any('/ticket/edit', [app\ticket\controller\TicketController::class, 'edit']); Route::any('/ticket/list', [app\ticket\controller\TicketController::class, 'index']); + Route::any('/ticket/status', [app\ticket\controller\TicketController::class, 'status']); + Route::any('/ticket/getStatus', [app\ticket\controller\TicketController::class, 'getStatus']); + Route::any('/upload', [app\common\controller\UploadController::class, 'uploadFile']); + + //工单沟通记录 + Route::any('/conversation/add', [app\ticket\controller\ConversationController::class, 'add']); + Route::any('/conversation/edit', [app\ticket\controller\ConversationController::class, 'edit']); + Route::any('/conversation/list', [app\ticket\controller\ConversationController::class, 'index']); + }); From fec7988a2656e752da5d75e324ec0ce56627b085 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 15:42:06 +0800 Subject: [PATCH 006/120] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=97=B6=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=8E=9F=E5=A7=8B=E6=96=87=E4=BB=B6=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UploadController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/common/controller/UploadController.php b/app/common/controller/UploadController.php index c01f084..9d39a05 100644 --- a/app/common/controller/UploadController.php +++ b/app/common/controller/UploadController.php @@ -44,6 +44,7 @@ class UploadController if (!$file || !$file->isValid()) { return $this->error('上传失败'); } + $originalName = $file->getUploadName(); $result = Storage::adapter('oss') ->path('vehicle_album/'.date('Ymd')) @@ -53,6 +54,7 @@ class UploadController $ossservice = new OssService(); $data = [ + 'original_name' => $originalName, 'file_name' => $result->file_name, 'file_url' => $ossservice->generatePrivateUrl($result->file_name) ]; From 6bb4e87280361be0820e732dfaae430482c8a740 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 15:46:08 +0800 Subject: [PATCH 007/120] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/route.php | 1 + 1 file changed, 1 insertion(+) diff --git a/config/route.php b/config/route.php index 6a53a53..c7b7413 100644 --- a/config/route.php +++ b/config/route.php @@ -43,6 +43,7 @@ Route::group('/ticket', function () { Route::any('/ticket/getStatus', [app\ticket\controller\TicketController::class, 'getStatus']); Route::any('/upload', [app\common\controller\UploadController::class, 'uploadFile']); + Route::any('/upload/image', [app\common\controller\UploadController::class, 'uploadImage']); //工单沟通记录 Route::any('/conversation/add', [app\ticket\controller\ConversationController::class, 'add']); From b9d909527825fec1b96d9b5c56af7251484c5056 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 15:53:34 +0800 Subject: [PATCH 008/120] =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=8B=E6=8B=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/TicketService.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index f01d9c2..4ca7c73 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -97,6 +97,11 @@ class TicketService { $locale = locale(); $ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN; - return $ticket_status; + + $status = []; + foreach ($ticket_status as $key => $value) { + $status[] = ['value' => $key, 'text' => $value]; + } + return $status; } } From 92abd946b047b7d95343a0809345759ea7f41ceb Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 16:36:00 +0800 Subject: [PATCH 009/120] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=BB=93=E6=9E=84=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UploadController.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/common/controller/UploadController.php b/app/common/controller/UploadController.php index 9d39a05..f47cd90 100644 --- a/app/common/controller/UploadController.php +++ b/app/common/controller/UploadController.php @@ -85,13 +85,16 @@ class UploadController $ossservice = new OssService(); $data = [ - 'image_name' => $result->file_name, - 'image_url' => $ossservice->generatePrivateUrl($result->file_name) + 'url' => $result->file_name, + 'alt' => '', + 'href' => $ossservice->generatePrivateUrl($result->file_name) ]; - return $this->success($data); - }catch (\Throwable $e){ - return $this->error($e->getMessage()); + return json(['errno' => 0, 'data' => $data]); +// return $this->success($data); + } catch (\Throwable $e) { + return json(['errno' => 1, 'message' => $e->getMessage()]); +// return $this->error($e->getMessage()); } } From 2be25889ea2ea2af9be3437dfc8f614b2b156845 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 3 Jul 2025 16:37:56 +0800 Subject: [PATCH 010/120] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UploadController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/common/controller/UploadController.php b/app/common/controller/UploadController.php index f47cd90..9d8959d 100644 --- a/app/common/controller/UploadController.php +++ b/app/common/controller/UploadController.php @@ -85,9 +85,9 @@ class UploadController $ossservice = new OssService(); $data = [ - 'url' => $result->file_name, + 'href' => $result->file_name, 'alt' => '', - 'href' => $ossservice->generatePrivateUrl($result->file_name) + 'url' => $ossservice->generatePrivateUrl($result->file_name) ]; return json(['errno' => 0, 'data' => $data]); From 3be2ae983c2af8246b8f2a9bcfdb698f03e0fc07 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 13:45:50 +0800 Subject: [PATCH 011/120] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/functions.php | 22 +++ app/queue/translation/config/trans.php | 14 ++ app/queue/translation/jobs/TranslationJob.php | 64 +++++++++ app/queue/translation/queue/Translation.php | 50 +++++++ .../service/TranslationService.php | 41 ++++++ .../translation/service/TranslatorClient.php | 38 +++++ app/queue/utils/Translat.php | 21 +++ app/queue/utils/XunfeiFanyi.php | 134 ++++++++++++++++++ app/ticket/controller/CategorieController.php | 52 +++++++ app/ticket/logic/CategorieLogic.php | 52 +++++++ app/ticket/logic/TicketLogic.php | 8 +- app/ticket/model/TicketCategorie.php | 47 ++++++ .../model/TicketCategorieTranslation.php | 35 +++++ app/ticket/service/CategorieService.php | 85 +++++++++++ app/ticket/service/ConversationService.php | 5 +- app/ticket/service/TicketService.php | 2 +- composer.json | 4 +- composer.lock | 131 ++++++++++++++++- config/plugin/webman/redis-queue/app.php | 4 + config/plugin/webman/redis-queue/command.php | 7 + config/plugin/webman/redis-queue/log.php | 32 +++++ config/plugin/webman/redis-queue/process.php | 12 ++ config/plugin/webman/redis-queue/redis.php | 22 +++ config/route.php | 6 + resource/translations/ar/messages.php | 5 + resource/translations/en/messages.php | 5 + resource/translations/ru/messages.php | 5 + resource/translations/zh/messages.php | 5 + 28 files changed, 900 insertions(+), 8 deletions(-) create mode 100644 app/queue/translation/config/trans.php create mode 100644 app/queue/translation/jobs/TranslationJob.php create mode 100644 app/queue/translation/queue/Translation.php create mode 100644 app/queue/translation/service/TranslationService.php create mode 100644 app/queue/translation/service/TranslatorClient.php create mode 100644 app/queue/utils/Translat.php create mode 100644 app/queue/utils/XunfeiFanyi.php create mode 100644 app/ticket/controller/CategorieController.php create mode 100644 app/ticket/logic/CategorieLogic.php create mode 100644 app/ticket/model/TicketCategorie.php create mode 100644 app/ticket/model/TicketCategorieTranslation.php create mode 100644 app/ticket/service/CategorieService.php create mode 100644 config/plugin/webman/redis-queue/app.php create mode 100644 config/plugin/webman/redis-queue/command.php create mode 100644 config/plugin/webman/redis-queue/log.php create mode 100644 config/plugin/webman/redis-queue/process.php create mode 100644 config/plugin/webman/redis-queue/redis.php create mode 100644 resource/translations/ar/messages.php create mode 100644 resource/translations/en/messages.php create mode 100644 resource/translations/ru/messages.php create mode 100644 resource/translations/zh/messages.php diff --git a/app/functions.php b/app/functions.php index 5c9c58d..c0c16fa 100644 --- a/app/functions.php +++ b/app/functions.php @@ -2,3 +2,25 @@ /** * Here is your custom functions. */ + +use support\exception\BusinessException; + +/** + * 获取配置 + */ +function getConfig($filePath = null) +{ + // 构建文件路径 + $config_file = app_path($filePath); + // 检查文件是否存在 + if (!file_exists($config_file)) { + return []; + } + // 包含文件并获取返回的数组 + $config = include $config_file; + // 验证是否为数组 + if (!is_array($config)) { + return []; + } + return $config; +} \ No newline at end of file diff --git a/app/queue/translation/config/trans.php b/app/queue/translation/config/trans.php new file mode 100644 index 0000000..73b55b1 --- /dev/null +++ b/app/queue/translation/config/trans.php @@ -0,0 +1,14 @@ + [ + 'ticket_categorie' => [ + 'translation_model' => \app\ticket\model\TicketCategorieTranslation::class, + 'foreign_key' => 'categorie_id', + ], + ], + + // 默认支持的目标语种列表 + 'locales' => ['zh','en','ru','ar'], +]; diff --git a/app/queue/translation/jobs/TranslationJob.php b/app/queue/translation/jobs/TranslationJob.php new file mode 100644 index 0000000..eb5e704 --- /dev/null +++ b/app/queue/translation/jobs/TranslationJob.php @@ -0,0 +1,64 @@ +config = getConfig('/queue/translation/config/trans.php'); + $this->payload = $payload; + } + + public function handle(): void + { + $cfgAll = $this->config['entities']; + $pt = $this->payload; + $type = $pt['entityType']; + $id = $pt['entityId']; + $locale = $pt['locale']; + $sourceLocale = $pt['sourceLocale']; + $sourceFields = $pt['sourceFields']; + $clientFields = $pt['clientFields']; + + if (! isset($cfgAll[$type])) { + Log::warning("TranslationJob: 未配置实体 {$type}"); + return; + } + + $cfg = $cfgAll[$type]; + $modelClass = $cfg['translation_model']; + $foreignKey = $cfg['foreign_key']; + + // 决定采用前端提供、源语种还是第三方翻译 + if (! empty($clientFields)) { + $fields = $clientFields; + } elseif ($locale === $sourceLocale) { + $fields = $sourceFields; + } else { + $translator = new TranslatorClient(); + $fields = []; + foreach ($sourceFields as $key => $text) { + $fields[$key] = $translator->translate($text, $sourceLocale, $locale); + } + } + + // 写库 + try { + $modelClass::updateOrCreate( + [ + $foreignKey => $id, + 'locale' => $locale, + ], + array_merge($fields, ['locale' => $locale]) + ); + } catch (\Throwable $e) { + Log::error("TranslationJob 保存失败: {$type}#{$id} locale={$locale}: ".$e->getMessage()); + } + } +} diff --git a/app/queue/translation/queue/Translation.php b/app/queue/translation/queue/Translation.php new file mode 100644 index 0000000..0b5841a --- /dev/null +++ b/app/queue/translation/queue/Translation.php @@ -0,0 +1,50 @@ + 'tom@gmail.com', 'content' => 'hello'] + + $job = new TranslationJob($data); + $job->handle(); + } catch (\Throwable $e) { + Log::error("TranslationService 异步任务异常: ".$e->getMessage()); + } + } + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file diff --git a/app/queue/translation/service/TranslationService.php b/app/queue/translation/service/TranslationService.php new file mode 100644 index 0000000..3578d89 --- /dev/null +++ b/app/queue/translation/service/TranslationService.php @@ -0,0 +1,41 @@ +config = getConfig('/queue/translation/config/trans.php'); + } + + /** + * 异步排队所有语种的翻译任务 + * + * @param string $entityType 实体名,如 'vehicle' + * @param int $entityId 主表 ID + * @param string $sourceLocale 源语种,如 'zh' + * @param array $sourceFields 源语种字段 ['name'=>…,'condition_desc'=>…] + * @param array $clientFields 前端已传翻译 ['en'=>[...], …] + */ + public function queueTranslations(string $entityType, int $entityId, string $sourceLocale, array $sourceFields, array $clientFields = []): void + { + foreach ($this->config['locales'] as $locale) { + $payload = [ + 'entityType' => $entityType, + 'entityId' => $entityId, + 'locale' => $locale, + 'sourceLocale' => $sourceLocale, + 'sourceFields' => $sourceFields, + 'clientFields' => $clientFields[$locale] ?? [], + ]; + Redis::send('transTask', $payload); + } + } +} \ No newline at end of file diff --git a/app/queue/translation/service/TranslatorClient.php b/app/queue/translation/service/TranslatorClient.php new file mode 100644 index 0000000..3abc1df --- /dev/null +++ b/app/queue/translation/service/TranslatorClient.php @@ -0,0 +1,38 @@ +endpoint = env('TRANSLATION_API_URL'); + // $this->apiKey = env('TRANSLATION_API_KEY'); + // } + + /** + * 翻译文本 + * @throws \RuntimeException + */ + public function translate(string $text, string $from = 'cn', string $to = 'ru'): string + { + try { + $xunfei = new XunfeiFanyi(); + $rst_dst = $xunfei->fanyi($text, $from, $to); + if ($rst_dst) { + return $rst_dst["trans_result"]["dst"]; + } else { + return ''; + } + } catch (\Throwable $e) { + Log::error("TranslatorClient 错误: ".$e->getMessage()); + throw new \RuntimeException('翻译服务调用失败'); + } + } +} diff --git a/app/queue/utils/Translat.php b/app/queue/utils/Translat.php new file mode 100644 index 0000000..3000756 --- /dev/null +++ b/app/queue/utils/Translat.php @@ -0,0 +1,21 @@ + $header,'body' => $response); + return $data; + } + + + public function xfyun($text = "中华人民共和国于1949年成立", $from = "cn", $to = "en") + { + //在控制台-我的应用-机器翻译获取 + $app_id = "3d46198c"; + //在控制台-我的应用-机器翻译获取 + $api_sec = "ZWY2MmUyNjJjYmFkOTFlYTVmYWVhMGYy"; + //在控制台-我的应用-机器翻译获取 + $api_key = "b6d805f240176ecaea85130cb5f2bca1"; + // 机器翻译接口地址 + $url = "https://ntrans.xfyun.cn/v2/ots"; + //body组装 + // $text = "中华人民共和国于1949年成立";//待翻译文本 + $body = json_encode($this->getBody($app_id, $from, $to, $text)); + + // 组装http请求头 + $date =gmdate('D, d M Y H:i:s') . ' GMT'; + + $digestBase64 = "SHA-256=".base64_encode(hash("sha256", $body, true)); + $builder = sprintf("host: %s +date: %s +POST /v2/ots HTTP/1.1 +digest: %s", "ntrans.xfyun.cn", $date, $digestBase64); + // echo($builder); + $sha = base64_encode(hash_hmac("sha256", $builder, $api_sec, true)); + + $authorization = sprintf("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", $api_key,"hmac-sha256", + "host date request-line digest", $sha); + + $header = [ + "Authorization: ".$authorization, + 'Content-Type: application/json', + 'Accept: application/json,version=1.0', + 'Host: ntrans.xfyun.cn', + 'Date: ' .$date, + 'Digest: '.$digestBase64 + ]; + $response = $this->tocurl($url, $header, $body); + + // var_dump($response['body']); + return $response; + } + + + public function getBody($app_id, $from, $to, $text) + { + $common_param = [ + 'app_id' => $app_id + ]; + + $business = [ + 'from' => $from, + 'to' => $to, + ]; + + $data = [ + "text" => base64_encode($text) + ]; + + return $body = [ + 'common' => $common_param, + 'business' => $business, + 'data' => $data + ]; + } + + // 翻译 + public function fanyi($text = "", $from = "", $to = "") + { + try { + $res = $this->xfyun($text, $from, $to); + $data = json_decode($res["body"], true); + if ($data["code"] === 0) { + return $data["data"]["result"]; + } else { + $this->error = $data["message"]; + return false; + } + } catch (\Throwable $th) { + $this->error = $th->getMessage(); + return false; + } + } + + // 异常 + public function error() + { + return $this->error; + } + +} \ No newline at end of file diff --git a/app/ticket/controller/CategorieController.php b/app/ticket/controller/CategorieController.php new file mode 100644 index 0000000..d9fe422 --- /dev/null +++ b/app/ticket/controller/CategorieController.php @@ -0,0 +1,52 @@ +list($request->all()); + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function getAll(Request $request) + { + try { + $result = (new CategorieService())->getAll($request->all()); + return $this->success($result); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function add(Request $request) + { + try { + return (new CategorieService())->add($request->all()) + ? $this->success() + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } + + public function edit(Request $request) + { + try { + $result = (new CategorieService())->edit($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } +} \ No newline at end of file diff --git a/app/ticket/logic/CategorieLogic.php b/app/ticket/logic/CategorieLogic.php new file mode 100644 index 0000000..ab100b4 --- /dev/null +++ b/app/ticket/logic/CategorieLogic.php @@ -0,0 +1,52 @@ +where($where)->paginate($size, ['*'], 'page', $current); + } + + public function getAll($params) + { + $where = []; + $where[] = ['status', '=', 1]; + return TicketCategorie::with('translation')->where($where)->get(); + } + + public function add(array $data) + { + try { + return TicketCategorie::create($data); + } catch (\Throwable $e) { + Log::error('分类创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException($e->getMessage().'分类创建失败,请稍后重试', 500); + } + } + + public function edit(array $data) + { + try { + $categorie = TicketCategorie::updateOrCreate(['id' => $data['id']], $data); + return $categorie; + } catch (\Throwable $e) { + Log::error('分类编辑失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException($e->getMessage().'分类编辑失败,请稍后重试', 500); + } + } +} \ No newline at end of file diff --git a/app/ticket/logic/TicketLogic.php b/app/ticket/logic/TicketLogic.php index 16fb5ce..21e8539 100644 --- a/app/ticket/logic/TicketLogic.php +++ b/app/ticket/logic/TicketLogic.php @@ -17,9 +17,15 @@ class TicketLogic public function list($params) { $where = []; + if(!empty($params['categorie_id'])){ + $where[] = ['categorie_id', '=', $params['categorie_id']]; + } + if(!empty($params['status'])){ + $where[] = ['status', '=', $params['status']]; + } $size = $params['size']; $current = $params['current']; - return Ticket::with('attachments')->where($where)->paginate($size, ['*'], 'page', $current); + return Ticket::with('attachments')->where($where)->orderBy('id','desc')->paginate($size, ['*'], 'page', $current); } public function add(array $data) { diff --git a/app/ticket/model/TicketCategorie.php b/app/ticket/model/TicketCategorie.php new file mode 100644 index 0000000..79f4f75 --- /dev/null +++ b/app/ticket/model/TicketCategorie.php @@ -0,0 +1,47 @@ + 'datetime:Y-m-d H:i:s', + 'created_at' => 'datetime:Y-m-d H:i:s', + ]; + protected $fillable = [ + 'name', + 'description', + ]; + + /** + * 单条翻译 + */ + public function translation() + { + return $this->hasOne(TicketCategorieTranslation::class, 'categorie_id')->where('locale', locale()); + } + + /** + * 多条翻译 + */ + public function translations() + { + return $this->hasMany(TicketCategorieTranslation::class, 'categorie_id'); + } +} \ No newline at end of file diff --git a/app/ticket/model/TicketCategorieTranslation.php b/app/ticket/model/TicketCategorieTranslation.php new file mode 100644 index 0000000..66a8f95 --- /dev/null +++ b/app/ticket/model/TicketCategorieTranslation.php @@ -0,0 +1,35 @@ + 'datetime:Y-m-d H:i:s', + 'created_at' => 'datetime:Y-m-d H:i:s', + ]; + protected $fillable = [ + 'categorie_id', + 'locale', + 'name', + 'description', + ]; +} \ No newline at end of file diff --git a/app/ticket/service/CategorieService.php b/app/ticket/service/CategorieService.php new file mode 100644 index 0000000..5a0e9ee --- /dev/null +++ b/app/ticket/service/CategorieService.php @@ -0,0 +1,85 @@ +list($params); + + foreach ($result as $key => &$value) { + $value['name'] = isset($item['translation']) ? $item['translation']['name'] : ''; + unset($value['translation']); + } + unset($value); + + return $result; + } + + public function getAll($params) + { + $result = (new CategorieLogic())->getAll($params); + foreach ($result as $key => &$value) { + $value['name'] =isset($item['translation']) ? $item['translation']['name'] : ''; + unset($value['translation']); + } + unset($value); + + return $result; + } + + public $translatFields = ['name']; + + public function add(array $data) + { + try { + Db::beginTransaction(); + $sourceLocale = locale(); + $traitsourceFields = Translat::extractSourceFields($data, $this->translatFields); + + $result = (new CategorieLogic())->add($data); + + (new TranslationService())->queueTranslations( + 'ticket_categorie', + $result->id, + $sourceLocale, + $traitsourceFields + ); + + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + public function edit(array $data) + { + try { + Db::beginTransaction(); + $sourceLocale = locale(); + $traitsourceFields = Translat::extractSourceFields($data, $this->translatFields); + + $result = (new CategorieLogic())->edit($data); + + (new TranslationService())->queueTranslations( + 'ticket_categorie', + $data['id'], + $sourceLocale, + $traitsourceFields + ); + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } +} \ No newline at end of file diff --git a/app/ticket/service/ConversationService.php b/app/ticket/service/ConversationService.php index cc3ef84..1716b8f 100644 --- a/app/ticket/service/ConversationService.php +++ b/app/ticket/service/ConversationService.php @@ -11,8 +11,8 @@ class ConversationService { public function list($params) { -// $locale = locale(); - $locale = 'zh'; + $locale = locale(); +// $locale = 'zh'; $ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN; // $ticket_status = Constants::TICKET_STATUS_ZH; // $params['locale'] = locale(); @@ -30,7 +30,6 @@ class ConversationService try { return (new ConversationLogic())->add($data); } catch (\Throwable $e) { - Db::rollback(); throw $e; } } diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index 4ca7c73..a20c20b 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -65,7 +65,7 @@ class TicketService throw new BusinessException('工单不存在'); } if($data['status'] == $ticket->status){ - throw new BusinessException('状态未修改'); + throw new BusinessException(trans('status_not_modified')); } // if(!in_array($ticket->status, [Constants::TICKET_STATUS['PENDING'], Constants::TICKET_STATUS['CONFIRM']]) diff --git a/composer.json b/composer.json index 20cf41f..ccf259e 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,9 @@ "league/oauth2-server": "^9.2", "topthink/think-validate": "^3.0", "shopwwi/webman-filesystem": "1.1", - "shopwwi/filesystem-oss": "^1.0" + "shopwwi/filesystem-oss": "^1.0", + "webman/redis-queue": "^1.3", + "symfony/translation": "^7.3" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index d34c290..01843a4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7421030f864dacf31b06378e1045d034", + "content-hash": "acb636fcb8774558a04bd674f9f95f55", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -5365,7 +5365,13 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/translation/zipball/4aba29076a29a3aa667e09b791e5f868973a8667", "reference": "4aba29076a29a3aa667e09b791e5f868973a8667", - "shasum": "" + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "php": ">=8.2", @@ -6147,6 +6153,43 @@ }, "time": "2025-03-14T03:52:14+00:00" }, + { + "name": "webman/redis-queue", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/webman-php/redis-queue.git", + "reference": "6fd491b19acb006d60931724aa2577a046ccb612" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/6fd491b19acb006d60931724aa2577a046ccb612", + "reference": "6fd491b19acb006d60931724aa2577a046ccb612", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "workerman/redis-queue": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\RedisQueue\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "description": "Redis message queue plugin for webman.", + "support": { + "issues": "https://github.com/webman-php/redis-queue/issues", + "source": "https://github.com/webman-php/redis-queue/tree/v1.3.1" + }, + "time": "2024-02-28T07:21:52+00:00" + }, { "name": "workerman/coroutine", "version": "v1.1.3", @@ -6186,6 +6229,90 @@ }, "time": "2025-02-17T03:34:21+00:00" }, + { + "name": "workerman/redis", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/walkor/redis.git", + "reference": "82b398c5bdd5d683a3458aaf645b159b7b39b84e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/redis/zipball/82b398c5bdd5d683a3458aaf645b159b7b39b84e", + "reference": "82b398c5bdd5d683a3458aaf645b159b7b39b84e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7", + "workerman/workerman": "^4.1.0||^5.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\Redis\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/redis/issues", + "source": "https://github.com/walkor/redis/tree/v2.0.4" + }, + "time": "2025-04-05T07:45:12+00:00" + }, + { + "name": "workerman/redis-queue", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/redis-queue.git", + "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/redis-queue/zipball/75dbf7ed2ea228c45dc0df82c0fea35879b715d0", + "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0", + "workerman/redis": "^1.0||^2.0", + "workerman/workerman": ">=4.0.20" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\RedisQueue\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Message queue system written in PHP based on workerman and backed by Redis.", + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/redis-queue/issues", + "source": "https://github.com/walkor/redis-queue/tree/v1.2.1" + }, + "time": "2025-01-02T09:21:45+00:00" + }, { "name": "workerman/webman-framework", "version": "v2.1.2", diff --git a/config/plugin/webman/redis-queue/app.php b/config/plugin/webman/redis-queue/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/redis-queue/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/command.php b/config/plugin/webman/redis-queue/command.php new file mode 100644 index 0000000..8bfe2a1 --- /dev/null +++ b/config/plugin/webman/redis-queue/command.php @@ -0,0 +1,7 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/redis-queue/queue.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ] +]; diff --git a/config/plugin/webman/redis-queue/process.php b/config/plugin/webman/redis-queue/process.php new file mode 100644 index 0000000..9fac733 --- /dev/null +++ b/config/plugin/webman/redis-queue/process.php @@ -0,0 +1,12 @@ + [ + 'handler' => Webman\RedisQueue\Process\Consumer::class, + 'count' => getenv('REDIS_QUEUE_PROCESS_COUNT'), // 可以设置多进程同时消费 + 'constructor' => [ + // 消费者类目录 + // 'consumer_dir' => app_path() . '/queue/redis' + 'consumer_dir' => app_path() . getenv('REDIS_QUEUE_CONSUMER_DIR') + ] + ] +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/redis.php b/config/plugin/webman/redis-queue/redis.php new file mode 100644 index 0000000..420914d --- /dev/null +++ b/config/plugin/webman/redis-queue/redis.php @@ -0,0 +1,22 @@ + [ +// 'host' => 'redis://127.0.0.1:6379', + 'host' => getenv('REDIS_QUEUE_HOST'), + 'options' => [ + 'auth' => getenv('REDIS_QUEUE_AUTH'), // 密码,字符串类型,可选参数 + 'db' => getenv('REDIS_QUEUE_DB'), // 数据库 + 'prefix' => getenv('REDIS_QUEUE_PREFIX'), // key 前缀 + 'max_attempts' => getenv('REDIS_QUEUE_MAX_ATTEMPTS'), // 消费失败后,重试次数 + 'retry_seconds' => getenv('REDIS_QUEUE_RETRY_SECONDS'), // 重试间隔,单位秒 + ], + // Connection pool, supports only Swoole or Swow drivers. + 'pool' => [ + 'max_connections' => 5, + 'min_connections' => 1, + 'wait_timeout' => 3, + 'idle_timeout' => 60, + 'heartbeat_interval' => 50, + ] + ], +]; diff --git a/config/route.php b/config/route.php index c7b7413..76a9435 100644 --- a/config/route.php +++ b/config/route.php @@ -50,5 +50,11 @@ Route::group('/ticket', function () { Route::any('/conversation/edit', [app\ticket\controller\ConversationController::class, 'edit']); Route::any('/conversation/list', [app\ticket\controller\ConversationController::class, 'index']); + //工单分类 + Route::any('/categorie/add', [app\ticket\controller\CategorieController::class, 'add']); + Route::any('/categorie/edit', [app\ticket\controller\CategorieController::class, 'edit']); + Route::any('/categorie/list', [app\ticket\controller\CategorieController::class, 'index']); + Route::any('/categorie/getAll', [app\ticket\controller\CategorieController::class, 'getAll']); + }); diff --git a/resource/translations/ar/messages.php b/resource/translations/ar/messages.php new file mode 100644 index 0000000..342a451 --- /dev/null +++ b/resource/translations/ar/messages.php @@ -0,0 +1,5 @@ + 'الوضع لم يتغير', +]; \ No newline at end of file diff --git a/resource/translations/en/messages.php b/resource/translations/en/messages.php new file mode 100644 index 0000000..53c6d07 --- /dev/null +++ b/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'status not modified', +]; \ No newline at end of file diff --git a/resource/translations/ru/messages.php b/resource/translations/ru/messages.php new file mode 100644 index 0000000..fc398a5 --- /dev/null +++ b/resource/translations/ru/messages.php @@ -0,0 +1,5 @@ + 'Статус не изменен', +]; \ No newline at end of file diff --git a/resource/translations/zh/messages.php b/resource/translations/zh/messages.php new file mode 100644 index 0000000..7c95aaf --- /dev/null +++ b/resource/translations/zh/messages.php @@ -0,0 +1,5 @@ + '状态未修改', +]; \ No newline at end of file From 345e2391b12e015e51adb97e7145bb7f9bfb8665 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 14:21:21 +0800 Subject: [PATCH 012/120] =?UTF-8?q?=E9=99=84=E4=BB=B6oss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/CategorieService.php | 6 +++--- app/ticket/service/TicketService.php | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/ticket/service/CategorieService.php b/app/ticket/service/CategorieService.php index 5a0e9ee..a6843e1 100644 --- a/app/ticket/service/CategorieService.php +++ b/app/ticket/service/CategorieService.php @@ -13,9 +13,9 @@ class CategorieService public function list($params) { $result = (new CategorieLogic())->list($params); - + $value=$result[0]->toArray(); foreach ($result as $key => &$value) { - $value['name'] = isset($item['translation']) ? $item['translation']['name'] : ''; + $value['name'] = isset($value['translation']) ? $value['translation']['name'] : ''; unset($value['translation']); } unset($value); @@ -27,7 +27,7 @@ class CategorieService { $result = (new CategorieLogic())->getAll($params); foreach ($result as $key => &$value) { - $value['name'] =isset($item['translation']) ? $item['translation']['name'] : ''; + $value['name'] = isset($value['translation']) ? $value['translation']['name'] : ''; unset($value['translation']); } unset($value); diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index a20c20b..98ad7b1 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -17,6 +17,14 @@ class TicketService public function list($params) { $res = (new TicketLogic())->list($params); + foreach ($res as $k => &$v) { + foreach ($v['attachments'] as &$attachments) { + $attachments['storage_path_full'] = (new \app\common\service\OssService())->generatePrivateUrl($attachments['storage_path']); + } + } + unset($v); + unset($attachments); + return $res; } From 1b73974d46b4ea8e8ac2628162f1a9c474019fd5 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 14:38:00 +0800 Subject: [PATCH 013/120] 1 --- app/ticket/controller/CategorieController.php | 12 ++++++++++++ app/ticket/logic/AttachmentLogic.php | 4 ++-- app/ticket/logic/CategorieLogic.php | 18 ++++++++++++++++-- app/ticket/logic/ConversationLogic.php | 2 +- app/ticket/logic/TicketLogic.php | 4 ++-- app/ticket/model/TicketCategorie.php | 3 +++ app/ticket/service/CategorieService.php | 12 ++++++++++++ config/route.php | 1 + 8 files changed, 49 insertions(+), 7 deletions(-) diff --git a/app/ticket/controller/CategorieController.php b/app/ticket/controller/CategorieController.php index d9fe422..a7a4d3d 100644 --- a/app/ticket/controller/CategorieController.php +++ b/app/ticket/controller/CategorieController.php @@ -49,4 +49,16 @@ class CategorieController extends BaseController return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); } } + + public function delete(Request $request) + { + try { + $result = (new CategorieService())->delete($request->get('id')); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } } \ No newline at end of file diff --git a/app/ticket/logic/AttachmentLogic.php b/app/ticket/logic/AttachmentLogic.php index ad3f52d..dac59f9 100644 --- a/app/ticket/logic/AttachmentLogic.php +++ b/app/ticket/logic/AttachmentLogic.php @@ -29,7 +29,7 @@ class AttachmentLogic 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException($e->getMessage().'工单创建失败,请稍后重试', 500); + throw new BusinessException('工单创建失败,请稍后重试', 500); } } @@ -67,7 +67,7 @@ class AttachmentLogic 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException($e->getMessage().'工单创建失败,请稍后重试', 500); + throw new BusinessException('工单创建失败,请稍后重试', 500); } } diff --git a/app/ticket/logic/CategorieLogic.php b/app/ticket/logic/CategorieLogic.php index ab100b4..4f63ebe 100644 --- a/app/ticket/logic/CategorieLogic.php +++ b/app/ticket/logic/CategorieLogic.php @@ -32,7 +32,7 @@ class CategorieLogic 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException($e->getMessage().'分类创建失败,请稍后重试', 500); + throw new BusinessException('分类创建失败,请稍后重试', 500); } } @@ -46,7 +46,21 @@ class CategorieLogic 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException($e->getMessage().'分类编辑失败,请稍后重试', 500); + throw new BusinessException('分类编辑失败,请稍后重试', 500); + } + } + + public function delete(int $id) + { + try { + $categorie = TicketCategorie::where(['id'=>$id])->delete(); + return $categorie; + } catch (\Throwable $e) { + Log::error('分类删除失败', [ + 'id' => $id, + 'error' => $e->getMessage() + ]); + throw new BusinessException('分类删除失败,请稍后重试', 500); } } } \ No newline at end of file diff --git a/app/ticket/logic/ConversationLogic.php b/app/ticket/logic/ConversationLogic.php index 6ce3cbf..d47a709 100644 --- a/app/ticket/logic/ConversationLogic.php +++ b/app/ticket/logic/ConversationLogic.php @@ -40,7 +40,7 @@ class ConversationLogic 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException($e->getMessage().'会话编辑失败,请稍后重试', 500); + throw new BusinessException('会话编辑失败,请稍后重试', 500); } } } \ No newline at end of file diff --git a/app/ticket/logic/TicketLogic.php b/app/ticket/logic/TicketLogic.php index 21e8539..8293654 100644 --- a/app/ticket/logic/TicketLogic.php +++ b/app/ticket/logic/TicketLogic.php @@ -38,7 +38,7 @@ class TicketLogic 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException($e->getMessage().'工单创建失败,请稍后重试', 500); + throw new BusinessException('工单创建失败,请稍后重试', 500); } } public function edit(array $data) @@ -52,7 +52,7 @@ class TicketLogic 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException($e->getMessage().'工单提取方法…失败,请稍后重试', 500); + throw new BusinessException('工单提取方法…失败,请稍后重试', 500); } } diff --git a/app/ticket/model/TicketCategorie.php b/app/ticket/model/TicketCategorie.php index 79f4f75..6d0c675 100644 --- a/app/ticket/model/TicketCategorie.php +++ b/app/ticket/model/TicketCategorie.php @@ -2,6 +2,7 @@ namespace app\ticket\model; +use Illuminate\Database\Eloquent\SoftDeletes; use support\Model; /** @@ -11,9 +12,11 @@ use support\Model; * @property string $description 分类描述 * @property string $created_at 创建时间 * @property string $updated_at 更新时间 + * @property string $deleted_at 删除时间 */ class TicketCategorie extends Model { + use SoftDeletes; protected $connection = 'mysql'; protected $table = 'ticket_categorie'; protected $primaryKey = 'id'; diff --git a/app/ticket/service/CategorieService.php b/app/ticket/service/CategorieService.php index a6843e1..d769a58 100644 --- a/app/ticket/service/CategorieService.php +++ b/app/ticket/service/CategorieService.php @@ -82,4 +82,16 @@ class CategorieService throw $e; } } + public function delete(int $id) + { + if(empty($id)){ + throw new BusinessException('id不能为空'); + } + try { + $result = (new CategorieLogic())->delete($id); + return $result; + } catch (\Throwable $e) { + throw $e; + } + } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index 76a9435..6d7ce98 100644 --- a/config/route.php +++ b/config/route.php @@ -55,6 +55,7 @@ Route::group('/ticket', function () { Route::any('/categorie/edit', [app\ticket\controller\CategorieController::class, 'edit']); Route::any('/categorie/list', [app\ticket\controller\CategorieController::class, 'index']); Route::any('/categorie/getAll', [app\ticket\controller\CategorieController::class, 'getAll']); + Route::any('/categorie/delete', [app\ticket\controller\CategorieController::class, 'delete']); }); From b70fc6c4f84234d1d838688adc0238b1b8ded7ea Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 14:44:46 +0800 Subject: [PATCH 014/120] 1 --- app/ticket/controller/CategorieController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ticket/controller/CategorieController.php b/app/ticket/controller/CategorieController.php index a7a4d3d..84e8bd7 100644 --- a/app/ticket/controller/CategorieController.php +++ b/app/ticket/controller/CategorieController.php @@ -53,7 +53,7 @@ class CategorieController extends BaseController public function delete(Request $request) { try { - $result = (new CategorieService())->delete($request->get('id')); + $result = (new CategorieService())->delete($request->input('id')); return $result ? $this->success($result) : $this->error(); From 388747634a2e35ecba352410c1e3a3810973fe6d Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 15:04:29 +0800 Subject: [PATCH 015/120] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/queue/translation/config/trans.php | 2 +- app/ticket/logic/CategorieLogic.php | 12 ++++++------ app/ticket/logic/TicketLogic.php | 2 +- .../model/{TicketCategorie.php => Categorie.php} | 10 +++++++--- ...gorieTranslation.php => CategorieTranslation.php} | 2 +- app/ticket/model/Ticket.php | 4 ++++ app/ticket/service/TicketService.php | 2 ++ 7 files changed, 22 insertions(+), 12 deletions(-) rename app/ticket/model/{TicketCategorie.php => Categorie.php} (76%) rename app/ticket/model/{TicketCategorieTranslation.php => CategorieTranslation.php} (95%) diff --git a/app/queue/translation/config/trans.php b/app/queue/translation/config/trans.php index 73b55b1..e2c7702 100644 --- a/app/queue/translation/config/trans.php +++ b/app/queue/translation/config/trans.php @@ -4,7 +4,7 @@ return [ // 定义所有需要多语言支持的实体 'entities' => [ 'ticket_categorie' => [ - 'translation_model' => \app\ticket\model\TicketCategorieTranslation::class, + 'translation_model' => \app\ticket\model\CategorieTranslation::class, 'foreign_key' => 'categorie_id', ], ], diff --git a/app/ticket/logic/CategorieLogic.php b/app/ticket/logic/CategorieLogic.php index 4f63ebe..c11bd64 100644 --- a/app/ticket/logic/CategorieLogic.php +++ b/app/ticket/logic/CategorieLogic.php @@ -2,7 +2,7 @@ namespace app\ticket\logic; -use app\ticket\model\TicketCategorie; +use app\ticket\model\Categorie; use support\Log; use support\exception\BusinessException; @@ -13,20 +13,20 @@ class CategorieLogic $where = []; $size = $params['size'] ?? 10; $current = $params['current'] ?? 1; - return TicketCategorie::with('translation')->where($where)->paginate($size, ['*'], 'page', $current); + return Categorie::with('translation')->where($where)->paginate($size, ['*'], 'page', $current); } public function getAll($params) { $where = []; $where[] = ['status', '=', 1]; - return TicketCategorie::with('translation')->where($where)->get(); + return Categorie::with('translation')->where($where)->get(); } public function add(array $data) { try { - return TicketCategorie::create($data); + return Categorie::create($data); } catch (\Throwable $e) { Log::error('分类创建失败', [ 'data' => $data, @@ -39,7 +39,7 @@ class CategorieLogic public function edit(array $data) { try { - $categorie = TicketCategorie::updateOrCreate(['id' => $data['id']], $data); + $categorie = Categorie::updateOrCreate(['id' => $data['id']], $data); return $categorie; } catch (\Throwable $e) { Log::error('分类编辑失败', [ @@ -53,7 +53,7 @@ class CategorieLogic public function delete(int $id) { try { - $categorie = TicketCategorie::where(['id'=>$id])->delete(); + $categorie = Categorie::where(['id'=>$id])->delete(); return $categorie; } catch (\Throwable $e) { Log::error('分类删除失败', [ diff --git a/app/ticket/logic/TicketLogic.php b/app/ticket/logic/TicketLogic.php index 8293654..ca5532f 100644 --- a/app/ticket/logic/TicketLogic.php +++ b/app/ticket/logic/TicketLogic.php @@ -25,7 +25,7 @@ class TicketLogic } $size = $params['size']; $current = $params['current']; - return Ticket::with('attachments')->where($where)->orderBy('id','desc')->paginate($size, ['*'], 'page', $current); + return Ticket::with('attachments')->with('categorie')->with('categorie.translation')->where($where)->orderBy('id','desc')->paginate($size, ['*'], 'page', $current); } public function add(array $data) { diff --git a/app/ticket/model/TicketCategorie.php b/app/ticket/model/Categorie.php similarity index 76% rename from app/ticket/model/TicketCategorie.php rename to app/ticket/model/Categorie.php index 6d0c675..70a92cf 100644 --- a/app/ticket/model/TicketCategorie.php +++ b/app/ticket/model/Categorie.php @@ -14,7 +14,7 @@ use support\Model; * @property string $updated_at 更新时间 * @property string $deleted_at 删除时间 */ -class TicketCategorie extends Model +class Categorie extends Model { use SoftDeletes; protected $connection = 'mysql'; @@ -37,7 +37,7 @@ class TicketCategorie extends Model */ public function translation() { - return $this->hasOne(TicketCategorieTranslation::class, 'categorie_id')->where('locale', locale()); + return $this->hasOne(CategorieTranslation::class, 'categorie_id')->where('locale', locale()); } /** @@ -45,6 +45,10 @@ class TicketCategorie extends Model */ public function translations() { - return $this->hasMany(TicketCategorieTranslation::class, 'categorie_id'); + return $this->hasMany(CategorieTranslation::class, 'categorie_id'); + } + + public function ticket(){ + return $this->hasOne(Ticket::class, 'categorie_id', 'id'); } } \ No newline at end of file diff --git a/app/ticket/model/TicketCategorieTranslation.php b/app/ticket/model/CategorieTranslation.php similarity index 95% rename from app/ticket/model/TicketCategorieTranslation.php rename to app/ticket/model/CategorieTranslation.php index 66a8f95..5d72369 100644 --- a/app/ticket/model/TicketCategorieTranslation.php +++ b/app/ticket/model/CategorieTranslation.php @@ -14,7 +14,7 @@ use support\Model; * @property string $created_at 创建时间 * @property string $updated_at 更新时间 */ -class TicketCategorieTranslation extends Model +class CategorieTranslation extends Model { protected $connection = 'mysql'; protected $table = 'ticket_categorie_translation'; diff --git a/app/ticket/model/Ticket.php b/app/ticket/model/Ticket.php index 0327a87..d10672c 100644 --- a/app/ticket/model/Ticket.php +++ b/app/ticket/model/Ticket.php @@ -70,4 +70,8 @@ class Ticket extends Model public function attachments(){ return $this->hasMany(Attachment::class, 'ticket_id', 'id'); } + + public function categorie(){ + return $this->belongsTo(Categorie::class, 'categorie_id', 'id'); + } } diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index 98ad7b1..28afe14 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -18,6 +18,8 @@ class TicketService { $res = (new TicketLogic())->list($params); foreach ($res as $k => &$v) { + $v['categorie']['name'] = isset($v['categorie']['translation']) ? $v['categorie']['translation']['name'] : ''; + unset($v['categorie']['translation']); foreach ($v['attachments'] as &$attachments) { $attachments['storage_path_full'] = (new \app\common\service\OssService())->generatePrivateUrl($attachments['storage_path']); } From 510fec0533046090bbc836bcb7aaeaa6215f65d3 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 15:06:41 +0800 Subject: [PATCH 016/120] 1 --- app/ticket/service/TicketService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index 28afe14..a1f1825 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -18,7 +18,7 @@ class TicketService { $res = (new TicketLogic())->list($params); foreach ($res as $k => &$v) { - $v['categorie']['name'] = isset($v['categorie']['translation']) ? $v['categorie']['translation']['name'] : ''; + $res[$k]['categorie']['name'] = isset($v['categorie']['translation']) ? $v['categorie']['translation']['name'] : ''; unset($v['categorie']['translation']); foreach ($v['attachments'] as &$attachments) { $attachments['storage_path_full'] = (new \app\common\service\OssService())->generatePrivateUrl($attachments['storage_path']); From 6fa4b8ddfd5d1d10e7804ab130053d3fe629c73b Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 15:27:09 +0800 Subject: [PATCH 017/120] =?UTF-8?q?=E7=8A=B6=E6=80=81=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/TicketService.php | 5 +++-- config/constants/Constants.php | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index a1f1825..fd39be0 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -18,8 +18,9 @@ class TicketService { $res = (new TicketLogic())->list($params); foreach ($res as $k => &$v) { - $res[$k]['categorie']['name'] = isset($v['categorie']['translation']) ? $v['categorie']['translation']['name'] : ''; - unset($v['categorie']['translation']); + $name = isset($v['categorie']['translation']) ? $v['categorie']['translation']['name'] : ''; + $res[$k]['categorie']['name'] = $name; + unset($res[$k]['categorie']['translation']); foreach ($v['attachments'] as &$attachments) { $attachments['storage_path_full'] = (new \app\common\service\OssService())->generatePrivateUrl($attachments['storage_path']); } diff --git a/config/constants/Constants.php b/config/constants/Constants.php index e4c9284..1e77a2b 100644 --- a/config/constants/Constants.php +++ b/config/constants/Constants.php @@ -17,21 +17,21 @@ class Constants self::TICKET_STATUS['PROCESS'] => '处理中', self::TICKET_STATUS['CONFIRM'] => '待确认', self::TICKET_STATUS['FINISH'] => '已完成', - self::TICKET_STATUS['REJECTED'] => '已驳回', + self::TICKET_STATUS['REJECTED'] => '关闭', ]; public const TICKET_STATUS_EN = [ self::TICKET_STATUS['PENDING'] => 'Pending acceptance', self::TICKET_STATUS['PROCESS'] => 'Processing', self::TICKET_STATUS['CONFIRM'] => 'To be confirmed', self::TICKET_STATUS['FINISH'] => 'Completed', - self::TICKET_STATUS['REJECTED'] => 'Rejected', + self::TICKET_STATUS['REJECTED'] => 'close', ]; public const TICKET_STATUS_RU = [ self::TICKET_STATUS['PENDING'] => 'Подлежит рассмотрению', self::TICKET_STATUS['PROCESS'] => 'В процессе обработки', self::TICKET_STATUS['CONFIRM'] => 'Подлежит подтверждению', self::TICKET_STATUS['FINISH'] => 'Завершено', - self::TICKET_STATUS['REJECTED'] => 'Отклонено', + self::TICKET_STATUS['REJECTED'] => 'Закрыть', ]; } \ No newline at end of file From 92fd6d4d9f690b12f756e70dcdaff45dffe94679 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 15:42:03 +0800 Subject: [PATCH 018/120] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/TicketService.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index fd39be0..5d1ee0a 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -19,8 +19,12 @@ class TicketService $res = (new TicketLogic())->list($params); foreach ($res as $k => &$v) { $name = isset($v['categorie']['translation']) ? $v['categorie']['translation']['name'] : ''; - $res[$k]['categorie']['name'] = $name; - unset($res[$k]['categorie']['translation']); + if(isset($res[$k]['categorie'])){ + $res[$k]['categorie']['name'] = $name; + } + if (isset($v['categorie']['translation'])){ + unset($res[$k]['categorie']['translation']); + } foreach ($v['attachments'] as &$attachments) { $attachments['storage_path_full'] = (new \app\common\service\OssService())->generatePrivateUrl($attachments['storage_path']); } From bb1f105021e3cba700fa9a304014101640aa25de Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 4 Jul 2025 16:10:16 +0800 Subject: [PATCH 019/120] =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=88=E4=B8=8D=E8=83=BD=E5=80=92=E9=80=80?= =?UTF-8?q?=E7=8A=B6=E6=80=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/controller/TicketController.php | 2 +- app/ticket/logic/AttachmentLogic.php | 4 ++-- app/ticket/logic/TicketLogic.php | 2 +- app/ticket/service/TicketService.php | 2 +- resource/translations/ar/messages.php | 2 +- resource/translations/en/messages.php | 2 +- resource/translations/ru/messages.php | 2 +- resource/translations/zh/messages.php | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php index cc33459..782dc74 100644 --- a/app/ticket/controller/TicketController.php +++ b/app/ticket/controller/TicketController.php @@ -26,7 +26,7 @@ class TicketController extends BaseController ? $this->success() : $this->error(); } catch (\Throwable $th) { - return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + return $this->error($th->getMessage()); } } diff --git a/app/ticket/logic/AttachmentLogic.php b/app/ticket/logic/AttachmentLogic.php index dac59f9..b498f7e 100644 --- a/app/ticket/logic/AttachmentLogic.php +++ b/app/ticket/logic/AttachmentLogic.php @@ -63,11 +63,11 @@ class AttachmentLogic $res = Attachment::whereNotIn('id', $attachmentsId)->where('ticket_id',$ticket_id)->delete(); return $res; } catch (\Throwable $e) { - Log::error('工单创建失败', [ + Log::error('工单编辑(附件)失败', [ 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException('工单创建失败,请稍后重试', 500); + throw new BusinessException('工单编辑失败,请稍后重试', 500); } } diff --git a/app/ticket/logic/TicketLogic.php b/app/ticket/logic/TicketLogic.php index ca5532f..e22cdb7 100644 --- a/app/ticket/logic/TicketLogic.php +++ b/app/ticket/logic/TicketLogic.php @@ -52,7 +52,7 @@ class TicketLogic 'data' => $data, 'error' => $e->getMessage() ]); - throw new BusinessException('工单提取方法…失败,请稍后重试', 500); + throw new BusinessException('工单编辑失败,请稍后重试', 500); } } diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index 5d1ee0a..f5a0d72 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -79,7 +79,7 @@ class TicketService if(empty($ticket)){ throw new BusinessException('工单不存在'); } - if($data['status'] == $ticket->status){ + if($data['status'] <= $ticket->status){ throw new BusinessException(trans('status_not_modified')); } diff --git a/resource/translations/ar/messages.php b/resource/translations/ar/messages.php index 342a451..a701f87 100644 --- a/resource/translations/ar/messages.php +++ b/resource/translations/ar/messages.php @@ -1,5 +1,5 @@ 'الوضع لم يتغير', + 'status_not_modified' => 'الدولة لا يمكن تعديلها عكسيا', ]; \ No newline at end of file diff --git a/resource/translations/en/messages.php b/resource/translations/en/messages.php index 53c6d07..c676eb2 100644 --- a/resource/translations/en/messages.php +++ b/resource/translations/en/messages.php @@ -1,5 +1,5 @@ 'status not modified', + 'status_not_modified' => 'The status cannot be reversed for modification', ]; \ No newline at end of file diff --git a/resource/translations/ru/messages.php b/resource/translations/ru/messages.php index fc398a5..96ab3a4 100644 --- a/resource/translations/ru/messages.php +++ b/resource/translations/ru/messages.php @@ -1,5 +1,5 @@ 'Статус не изменен', + 'status_not_modified' => 'Статус не может быть изменен в обратном направлении', ]; \ No newline at end of file diff --git a/resource/translations/zh/messages.php b/resource/translations/zh/messages.php index 7c95aaf..229d7ee 100644 --- a/resource/translations/zh/messages.php +++ b/resource/translations/zh/messages.php @@ -1,5 +1,5 @@ '状态未修改', + 'status_not_modified' => '状态不能逆向修改', ]; \ No newline at end of file From 03a6d65b009012c882e4efc70e9701a028c09d61 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Mon, 7 Jul 2025 15:27:48 +0800 Subject: [PATCH 020/120] =?UTF-8?q?=E8=A1=A8=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/model/Attachment.php | 2 +- app/ticket/model/Categorie.php | 2 +- app/ticket/model/CategorieTranslation.php | 2 +- app/ticket/model/Conversation.php | 2 +- app/ticket/model/Ticket.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/ticket/model/Attachment.php b/app/ticket/model/Attachment.php index b29af53..f5fc843 100644 --- a/app/ticket/model/Attachment.php +++ b/app/ticket/model/Attachment.php @@ -33,7 +33,7 @@ class Attachment extends Model * * @var string */ - protected $table = 'ticket_attachment'; + protected $table = 'tkt_ticket_attachment'; /** * The primary key associated with the table. diff --git a/app/ticket/model/Categorie.php b/app/ticket/model/Categorie.php index 70a92cf..610c693 100644 --- a/app/ticket/model/Categorie.php +++ b/app/ticket/model/Categorie.php @@ -18,7 +18,7 @@ class Categorie extends Model { use SoftDeletes; protected $connection = 'mysql'; - protected $table = 'ticket_categorie'; + protected $table = 'tkt_ticket_categorie'; protected $primaryKey = 'id'; public $timestamps = true; protected $dateFormat = 'U'; diff --git a/app/ticket/model/CategorieTranslation.php b/app/ticket/model/CategorieTranslation.php index 5d72369..6660dea 100644 --- a/app/ticket/model/CategorieTranslation.php +++ b/app/ticket/model/CategorieTranslation.php @@ -17,7 +17,7 @@ use support\Model; class CategorieTranslation extends Model { protected $connection = 'mysql'; - protected $table = 'ticket_categorie_translation'; + protected $table = 'tkt_ticket_categorie_translation'; protected $primaryKey = 'id'; public $timestamps = true; protected $dateFormat = 'U'; diff --git a/app/ticket/model/Conversation.php b/app/ticket/model/Conversation.php index 8839455..6d63cb5 100644 --- a/app/ticket/model/Conversation.php +++ b/app/ticket/model/Conversation.php @@ -16,7 +16,7 @@ use support\Model; class Conversation extends Model { protected $connection = 'mysql'; - protected $table = 'ticket_conversation'; + protected $table = 'tkt_ticket_conversation'; protected $primaryKey = 'id'; public $timestamps = true; protected $dateFormat = 'U'; diff --git a/app/ticket/model/Ticket.php b/app/ticket/model/Ticket.php index d10672c..cf71b2a 100644 --- a/app/ticket/model/Ticket.php +++ b/app/ticket/model/Ticket.php @@ -33,7 +33,7 @@ class Ticket extends Model * * @var string */ - protected $table = 'ticket'; + protected $table = 'tkt_ticket'; /** * The primary key associated with the table. From 4ddf4921b2bce7467b014a5803473672b7a5f830 Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 7 Jul 2025 18:34:21 +0800 Subject: [PATCH 021/120] =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/AuthController.php | 10 ++ app/common/controller/BaseController.php | 2 - app/common/logic/AuthLogic.php | 52 ++++++++++ app/common/model/Corp.php | 49 ++++++++++ app/common/model/CorpFeishuApp.php | 56 +++++++++++ app/common/service/AuthService.php | 9 ++ app/common/service/BasicDataService.php | 118 +++++++++++++++++++++++ app/common/traits/ResponseTrait.php | 4 +- app/common/validate/UserValidate.php | 17 +++- app/middleware/AccessControl.php | 25 +++++ app/middleware/BasicDataMiddleware.php | 30 ++++++ config/middleware.php | 6 +- config/route.php | 1 + 13 files changed, 372 insertions(+), 7 deletions(-) create mode 100644 app/common/model/Corp.php create mode 100644 app/common/model/CorpFeishuApp.php create mode 100644 app/common/service/BasicDataService.php create mode 100644 app/middleware/AccessControl.php create mode 100644 app/middleware/BasicDataMiddleware.php diff --git a/app/common/controller/AuthController.php b/app/common/controller/AuthController.php index df35e72..c03c7f6 100644 --- a/app/common/controller/AuthController.php +++ b/app/common/controller/AuthController.php @@ -8,6 +8,16 @@ use support\Request; class AuthController extends BaseController { + + public function feishuScope(Request $request) + { + try { + $result = (new AuthService())->feishuAuthorize($request->corp_abbr, $request->system_code); + return $this->success($result); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } public function login(Request $request) { diff --git a/app/common/controller/BaseController.php b/app/common/controller/BaseController.php index 10b3e95..f399906 100644 --- a/app/common/controller/BaseController.php +++ b/app/common/controller/BaseController.php @@ -2,8 +2,6 @@ namespace app\common\controller; -use support\Request; -use support\Response; use support\exception\BusinessException; use app\common\traits\ResponseTrait; use think\Validate; diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index de97262..0270f17 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -2,6 +2,8 @@ namespace app\common\logic; +use app\common\model\Corp; +use app\common\model\CorpFeishuApp; use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; @@ -375,6 +377,30 @@ class AuthLogic return $system; } + /** + * 公司验证 + */ + private function validateCorp(string $corpAbbr) + { + $corp = Corp::where('abbr', $corpAbbr)->first(); + if (!$corp) { + throw new BusinessException('公司不存在', 403); + } + return $corp; + } + + /** + * 飞书应用信息 + */ + private function validateFeishuApp(int $corpId, string $systemCode) + { + $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first(); + if (!$feishuAppInfo) { + throw new BusinessException('飞书应用不存在', 403); + } + return $feishuAppInfo; + } + /** * 用户系统权限校验 */ @@ -450,6 +476,16 @@ class AuthLogic */ private function getFeishuUserInfo(string $code) { + // // 获取凭证 + // $certificate = certificate($param['state']); + // // 获取token + // $class = new Feishu($certificate['appId'], $certificate['appSecre_s'], $param['state']); + // $userAccessToken = $class->userAccessToken($param['code'], $certificate['rUrl']); + // // 获取用户信息 + // $rst = $class->userInfo($userAccessToken['access_token']); + // if ($rst['code'] != 0) { + // throw new BusinessException('授权失败,请重试'); + // } // 实际实现需要调用飞书 API return [ 'user_id' => 'feishu_user_123', @@ -472,4 +508,20 @@ class AuthLogic } return $user; } + + /** + * 飞书应用信息 + */ + public function feishuAppInfo(string $corpAbbr, string $systemCode) + { + $corpInfo = $this->validateCorp($corpAbbr); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); + return [ + 'app_id' => $fsAppInfo->app_id, + 'app_secret' => $fsAppInfo->app_secret, + 'r_url' => $fsAppInfo->r_url, + 'scope' => $fsAppInfo->scopes, + 'state' => $fsAppInfo->state + ]; + } } \ No newline at end of file diff --git a/app/common/model/Corp.php b/app/common/model/Corp.php new file mode 100644 index 0000000..52c160e --- /dev/null +++ b/app/common/model/Corp.php @@ -0,0 +1,49 @@ +feishuAppInfo($corpAbbr, $systemCcode); + } + /** * 登录,支持多类型 */ diff --git a/app/common/service/BasicDataService.php b/app/common/service/BasicDataService.php new file mode 100644 index 0000000..af63d7a --- /dev/null +++ b/app/common/service/BasicDataService.php @@ -0,0 +1,118 @@ +checkParamInCache($loginTypeKey, $loginType)) { + // 如果 Redis 没有缓存,执行数据库查询并存入缓存 + $this->cacheLoginType($loginTypeKey, $loginType); + } + + // 检查缓存中是否有系统码参数 + if (!$this->checkParamInCache($systemCodeKey, $systemCode)) { + // 如果 Redis 没有缓存,执行数据库查询并存入缓存 + $this->cacheSystemCode($systemCodeKey, $systemCode); + } + + // 检查缓存中是否有公司标识参数 + if (!$this->checkParamInCache($corpAbbrKey, $corpAbbr)) { + // 如果 Redis 没有缓存,执行数据库查询并存入缓存 + $this->cacheCorpAbbr($corpAbbrKey, $corpAbbr); + } + + return true; + } + + /** + * 检查参数是否在 Redis 缓存中 + */ + private function checkParamInCache($cacheKey, $param) + { + return Redis::get($cacheKey) === $param; + } + + /** + * 缓存登录类型 + */ + private function cacheLoginType($cacheKey, $loginType) + { + // 假设我们从数据库或其他来源验证 loginType 的合法性 + if (!$this->validateLoginType($loginType)) { + throw new BusinessException('无效的登录方式'); + } + // 存入缓存,缓存有效期可以根据需求调整 + Redis::setex($cacheKey, 3600, $loginType); // 1小时有效期 + } + + /** + * 缓存系统码 + */ + private function cacheSystemCode($cacheKey, $systemCode) + { + // 假设我们从数据库或其他来源验证 systemCode 的合法性 + if (!$this->validateSystemCode($systemCode)) { + throw new BusinessException('无效的系统码'); + } + // 存入缓存,缓存有效期可以根据需求调整 + Redis::setex($cacheKey, 3600, $systemCode); // 1小时有效期 + } + + /** + * 缓存公司标识 + */ + private function cacheCorpAbbr($cacheKey, $corpAbbr) + { + // 假设我们从数据库或其他来源验证 corpAbbr 的合法性 + if (!$this->validateCorpAbbr($corpAbbr)) { + throw new BusinessException('无效的公司标识'); + } + // 存入缓存,缓存有效期可以根据需求调整 + Redis::setex($cacheKey, 3600, $corpAbbr); // 1小时有效期 + } + + /** + * 验证登录方式 + */ + private function validateLoginType($loginType) + { + // 假设这里是验证登录方式的具体逻辑 + return in_array($loginType, ['upwd', 'feishu', 'qrcode']); + } + + /** + * 验证系统码 + */ + private function validateSystemCode($systemCode) + { + // 假设这里是验证系统码的具体逻辑 + $system = Systems::where('code', $systemCode)->first(); + return $system !== null; + } + + /** + * 验证公司标识 + */ + private function validateCorpAbbr($corpAbbr) + { + // 假设这里是验证公司标识的具体逻辑 + $company = Corp::where('abbr', $corpAbbr)->first(); + return $company !== null; + } +} diff --git a/app/common/traits/ResponseTrait.php b/app/common/traits/ResponseTrait.php index 227fa99..f15dc92 100644 --- a/app/common/traits/ResponseTrait.php +++ b/app/common/traits/ResponseTrait.php @@ -19,7 +19,7 @@ trait ResponseTrait public function success( $data = [], string $message = '请求成功', - int $code = 0, + int $code = 200, int $httpStatus = 200, array $header = ['Content-Type' => 'application/json'] ): Response { @@ -38,7 +38,7 @@ trait ResponseTrait */ public function error( string $message = '请求失败', - int $code = 1, + int $code = 400, int $httpStatus = 400, $data = [], array $header = ['Content-Type' => 'application/json'] diff --git a/app/common/validate/UserValidate.php b/app/common/validate/UserValidate.php index 415ee0b..f161ba3 100644 --- a/app/common/validate/UserValidate.php +++ b/app/common/validate/UserValidate.php @@ -2,6 +2,7 @@ namespace app\common\validate; +use app\common\model\Corp; use app\common\model\Systems; use app\common\model\User; use think\Validate; @@ -17,7 +18,8 @@ class UserValidate extends Validate 'phone' => 'checkMobile|checkPhoneUnique', 'email' => 'email|checkEmailUnique', 'login_type' => 'require|in:upwd,feishu,qrcode', - 'system_code' => 'require|checkSystemCode' + 'system_code' => 'require|checkSystemCode', + 'corp_abbr' => 'require|checkCorpAbbr', ]; protected $message = [ @@ -40,12 +42,15 @@ class UserValidate extends Validate 'login_type.require' => '请选择登录方式', 'login_type.in' => '登录方式不支持', 'system_code.require' => '系统码不能为空', - 'system_code.checkSystemCode' => '系统码不存在或不合法' + 'system_code.checkSystemCode' => '系统码不存在或不合法', + 'corp_abbr.require' => '公司标识不能为空', + 'corp_abbr.checkCorpAbbr' => '公司标识不存在或不合法' ]; protected $scene = [ 'upwd_register' => ['name', 'account', 'password', 'confirm_password', 'login_type', 'system_code'], 'upwd_login' => ['account', 'password', 'login_type', 'system_code'], + 'feishu_auth' => ['corp_abbr', 'system_code', 'login_type'] ]; /** @@ -129,4 +134,12 @@ class UserValidate extends Validate { return Systems::where('code', $value)->exists(); } + + /** + * 验证 corp_abbr 企业标识是否存在 + */ + protected function checkCorpAbbr($value, $rule, $data, $field) + { + return Corp::where('abbr', $value)->exists(); + } } \ No newline at end of file diff --git a/app/middleware/AccessControl.php b/app/middleware/AccessControl.php new file mode 100644 index 0000000..6fa3822 --- /dev/null +++ b/app/middleware/AccessControl.php @@ -0,0 +1,25 @@ +method() == 'OPTIONS' ? response('') : $handler($request); + + // 给响应添加跨域相关的http头 + $response->withHeaders([ + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Allow-Origin' => $request->header('origin', '*'), + 'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'), + 'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'), + ]); + + return $response; + } +} \ No newline at end of file diff --git a/app/middleware/BasicDataMiddleware.php b/app/middleware/BasicDataMiddleware.php new file mode 100644 index 0000000..6929ef8 --- /dev/null +++ b/app/middleware/BasicDataMiddleware.php @@ -0,0 +1,30 @@ +header('login_type'); + $systemCode = $request->header('system_code'); + $corpAbbr = $request->header('corp_abbr'); + $basicDataService = new BasicDataService(); + if ($basicDataService->checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)) { + $request->login_type = $loginType; + $request->system_code = $systemCode; + $request->corp_abbr = $corpAbbr; + } + return $next($request); + } catch (\Throwable $th) { + throw $th; + } + } + +} diff --git a/config/middleware.php b/config/middleware.php index 8e964ed..5512630 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -12,4 +12,8 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ -return []; \ No newline at end of file +return [ + '' => [ + \app\middleware\BasicDataMiddleware::class + ] +]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index 8870d47..aaf9098 100644 --- a/config/route.php +++ b/config/route.php @@ -30,5 +30,6 @@ Route::group('/common', function () { Route::group('/auth', function () { Route::post('/login', [app\common\controller\AuthController::class, 'login']); Route::post('/register', [app\common\controller\AuthController::class, 'register']); + Route::post('/feishu/scope', [app\common\controller\AuthController::class, 'feishuScope']); }); }); \ No newline at end of file From f19efd85b060f011e3f61f9dc9aca43a119f3b23 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Wed, 9 Jul 2025 15:10:14 +0800 Subject: [PATCH 022/120] 1 --- app/crm/controller/BaseController.php | 51 ++++++++ app/crm/controller/CarTypeController.php | 34 ++++++ app/crm/controller/CustomerController.php | 49 ++++++++ app/crm/controller/FollowController.php | 127 ++++++++++++++++++++ app/crm/controller/TagGroupController.php | 67 +++++++++++ app/crm/logic/CustomerLogic.php | 8 ++ app/crm/logic/FollowLogic.php | 8 ++ app/crm/model/CarType.php | 36 ++++++ app/crm/model/Channel.php | 48 ++++++++ app/crm/model/Country.php | 41 +++++++ app/crm/model/Customer.php | 135 ++++++++++++++++++++++ app/crm/model/CustomerHis.php | 68 +++++++++++ app/crm/model/CustomerTag.php | 36 ++++++ app/crm/model/Follow.php | 61 ++++++++++ app/crm/model/Tag.php | 55 +++++++++ app/crm/model/TagGroup.php | 50 ++++++++ app/crm/service/CustomerService.php | 27 +++++ app/crm/service/FollowService.php | 53 +++++++++ app/crm/utils/Util.php | 121 +++++++++++++++++++ app/crm/validate/FollowValidate.php | 21 ++++ config/constants/Constants.php | 100 ++++++++++++++++ 21 files changed, 1196 insertions(+) create mode 100644 app/crm/controller/BaseController.php create mode 100644 app/crm/controller/CarTypeController.php create mode 100644 app/crm/controller/CustomerController.php create mode 100644 app/crm/controller/FollowController.php create mode 100644 app/crm/controller/TagGroupController.php create mode 100644 app/crm/logic/CustomerLogic.php create mode 100644 app/crm/logic/FollowLogic.php create mode 100644 app/crm/model/CarType.php create mode 100644 app/crm/model/Channel.php create mode 100644 app/crm/model/Country.php create mode 100644 app/crm/model/Customer.php create mode 100644 app/crm/model/CustomerHis.php create mode 100644 app/crm/model/CustomerTag.php create mode 100644 app/crm/model/Follow.php create mode 100644 app/crm/model/Tag.php create mode 100644 app/crm/model/TagGroup.php create mode 100644 app/crm/service/CustomerService.php create mode 100644 app/crm/service/FollowService.php create mode 100644 app/crm/utils/Util.php create mode 100644 app/crm/validate/FollowValidate.php diff --git a/app/crm/controller/BaseController.php b/app/crm/controller/BaseController.php new file mode 100644 index 0000000..1b824e0 --- /dev/null +++ b/app/crm/controller/BaseController.php @@ -0,0 +1,51 @@ +scene($scene); + } + // 执行数据验证 + if (!$validator->check($data)) { + // 验证失败,抛出业务异常,异常信息为验证器返回的错误信息 + throw new BusinessException($validator->getError()); + } + // 验证成功,返回原始数据 + return $data; + } + + public function page($result){ + $current = $result->currentPage(); + $total = $result->total(); + $size = $result->perPage(); + $result = $result->toArray(); + $result['current'] = $current; + $result['total'] = $total; + $result['size'] = $size; + return $result; + } +} \ No newline at end of file diff --git a/app/crm/controller/CarTypeController.php b/app/crm/controller/CarTypeController.php new file mode 100644 index 0000000..6156876 --- /dev/null +++ b/app/crm/controller/CarTypeController.php @@ -0,0 +1,34 @@ +user['language'] != 'zh_CN'){ + foreach ($list as &$value) { + $value['name'] = $value['name_en']; + } + }elseif ($request->user['language'] == 'ru'){ + foreach ($list as &$value) { + $value['name'] = $value['name_ru']; + } + } + + return $this->success($list); + } + +} \ No newline at end of file diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php new file mode 100644 index 0000000..9844dc3 --- /dev/null +++ b/app/crm/controller/CustomerController.php @@ -0,0 +1,49 @@ +list($request->all()); + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function follow(Request $request) + { +// try { +// $result = (new CustomerService())->follow($request->all()); +// return $this->success($this->page($result)); +// } catch (\Throwable $e) { +// return $this->error($e->getMessage()); +// } + $page = $request->input('page', 1); + $perPage = $request->input('per_page', 10); + $where = []; + if($request->input('user_id')){ + $where[] = ['user_id', '=', $request->input('user_id')]; + } + $user = Customer::with('user')->where($where)->paginate($perPage, ['*'], 'page', $page); + $user = $user->toArray(); + foreach ($user['data'] as &$item) { + unset($item['password']); + } + return $this->success($user); + } +} \ No newline at end of file diff --git a/app/crm/controller/FollowController.php b/app/crm/controller/FollowController.php new file mode 100644 index 0000000..7f08e7d --- /dev/null +++ b/app/crm/controller/FollowController.php @@ -0,0 +1,127 @@ + $value) { + $grade[] = [ + 'status' => $key, + 'text' => $value, + ]; + } + return $this->success($grade); + } + + + /** + * 跟进列表 + * @param Request $request + * @return \support\Response + */ + public function list(Request $request) + { + try { + $data = $this->validate(new FollowValidate(), $request->get(), 'upwd_list'); + $result = (new FollowService())->list($request->all()); + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 跟进详情 + * @param Request $request + * @return \support\Response + */ + public function detail(Request $request) + { + //跟进id + $id = Util::getParam($request,'id'); + if(Util::isEmpty($id)){ + return $this->error('id'.trans('not_null')); + } + + $res = Follow::select('id','user_id','customer_id','type','picture','remark','audio','audio_text','lat','lng','address') + ->where('id',$id) + ->first(); + if($res){ + $res->toArray(); + }else{ + return $this->error('记录不存在'); + } + + $res = Follow::getFollow($res); + + return $this->success($res); + } + + /** + * 客户录入 + * @param Request $request + * @return \support\Response + */ + public function save(Request $request) + { + $customer_id = Util::getParam($request,'customer_id'); + + if(Util::isEmpty($customer_id)){ + return $this->error('customer_id'.trans('not_null')); + } + $type = Util::getParam($request,'type'); + if(Util::isEmpty($type)){ + return $this->error('type'.trans('not_null')); + } + $picture = Util::getParam($request,'picture'); + if(Util::isEmpty($picture)){ + return $this->error(trans('picture_not_null')); + } + + $data = [ + 'user_id'=>$request->user['id'], + 'customer_id'=>$customer_id, + 'type'=>$type, + 'picture'=>$picture, + 'remark'=>Util::getParam($request,'remark',''), + 'audio'=>Util::getParam($request,'audio',''), + 'audio_text'=>Util::getParam($request,'audio_text',''), + 'lat'=>Util::getParam($request,'lat',0), + 'lng'=>Util::getParam($request,'lng',0), + 'address'=>Util::getParam($request,'address'), + 'follow_time'=>strtotime(Util::getParam($request,'firstFollowtime',0)), + ]; + + $res = Follow::create($data); + + $customer = Customer::find($customer_id); + $customer->follow_last = strtotime($res->created_at); + $customer->follow_next = null; + $customer->warn = 0; + $customer->save(); + + if ($res) { + return $this->success($res,trans('save_success')); + } else { + return $this->error(trans('save_fail')); + } + } + +} \ No newline at end of file diff --git a/app/crm/controller/TagGroupController.php b/app/crm/controller/TagGroupController.php new file mode 100644 index 0000000..7e5dff1 --- /dev/null +++ b/app/crm/controller/TagGroupController.php @@ -0,0 +1,67 @@ +get(); + $languageArr = Constants::LANGUAGE; + $language = $request->user['language']; + foreach ($list as &$item) { + $item['names'] = unserialize($item['names']); + if (in_array($language, $languageArr)) { + $item['name'] = $item['names'][$language]; + } + foreach ($item['tags'] as &$tag) { + $tag['names'] = unserialize($tag['names']); + if (in_array($language, $languageArr)) { + $tag['name'] = $tag['names'][$language]; + } + } + } + + return $this->success($list); + } + + + /** + * 更新客户标签关系 + * @param Request $request + * @return \support\Response + */ + public function updatePivot(Request $request) + { + $customerId = Util::getParam($request, 'customer_id'); + $groupId = Util::getParam($request, 'group_id'); + $tagId = Util::getParam($request, 'tag_id'); + if (Util::isEmpty($customerId)) { + return $this->error( trans('customer_id') . trans('not_null')); + } + if (Util::isEmpty($groupId)) { + return $this->error( trans('tag_group_id') . trans('not_null')); + } + + if(empty($tagId)){ + $res = CustomerTag::where(['customer_id' => $customerId, 'group_id' => $groupId])->delete(); + }else{ + $res = CustomerTag::updateOrCreate(['customer_id' => $customerId, 'group_id' => $groupId], ['tag_id' => $tagId]); + } + + if($res){ + return $this->success($res, trans('success')); + }else{ + return $this->error( trans('fail')); + } + } +} \ No newline at end of file diff --git a/app/crm/logic/CustomerLogic.php b/app/crm/logic/CustomerLogic.php new file mode 100644 index 0000000..0e6b1a0 --- /dev/null +++ b/app/crm/logic/CustomerLogic.php @@ -0,0 +1,8 @@ +select('id','name','name_en','status')->where('status',0)->toArray(); + return $list; + } +} \ No newline at end of file diff --git a/app/crm/model/Channel.php b/app/crm/model/Channel.php new file mode 100644 index 0000000..59f9237 --- /dev/null +++ b/app/crm/model/Channel.php @@ -0,0 +1,48 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 +// 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'updated_at' => 'timestamp', // 指定序列化格式 + 'created_at' => 'timestamp', // 指定序列化格式 + ]; + + public static function getAll() + { + $list = self::get()->select('id','name','name_en','status')->where('status',0)->toArray(); + return $list; + } +} \ No newline at end of file diff --git a/app/crm/model/Country.php b/app/crm/model/Country.php new file mode 100644 index 0000000..29ba895 --- /dev/null +++ b/app/crm/model/Country.php @@ -0,0 +1,41 @@ +select('id','cn_addr','en_addr')->get()->toArray(); + return $list; + } +} \ No newline at end of file diff --git a/app/crm/model/Customer.php b/app/crm/model/Customer.php new file mode 100644 index 0000000..7db2f1e --- /dev/null +++ b/app/crm/model/Customer.php @@ -0,0 +1,135 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 + ]; + + /** + * 重新格式化时间 + * @param DateTimeInterface $date + * @return string + */ + protected function serializeDate(DateTimeInterface $date): string + { + return $date->format(Carbon::parse($date)->toDateTimeString()); + } + + /** + * @param $customer + * @param $request + * @param $tag 是否返回客户标签 + * @return mixed + */ + public static function getCustomer($customer, $request, $tag = false) + { + $language = $request->user['language'] ?: 'en'; + $customer['grade_txt'] = Util::language($language, Constants::GRADE_EN[$customer['grade']], Constants::GRADE_ZH[$customer['grade']], Constants::GRADE_RU[$customer['grade']]); + $customer['magnitude_txt'] = Util::language($language, Constants::MAGNITUDE_EN[$customer['magnitude']], Constants::MAGNITUDE[$customer['magnitude']], Constants::MAGNITUDE_RU[$customer['magnitude']]); + $customer['sex_txt'] = Util::language($language, Constants::SEX_EN[$customer['sex']], Constants::SEX[$customer['sex']], Constants::SEX_RU[$customer['sex']]); + + if ($customer['grade']<=2) { + $customer['follow'] = 0; + $customer['follow_txt'] = Util::language($language,'Following','跟进中','Продолжение'); + }elseif ($customer['grade']<5) { + $customer['follow'] = 1; + $customer['follow_txt'] = Util::language($language,'transacted','已成交','Транзакция завершена'); + }else{ + $customer['follow'] = 2; + $customer['follow_txt'] = Util::language($language,'lost','已流失','Потерянный'); + } + + $where = [ + 'customer_id' => $customer['id'] + ]; + $begin_follow = Follow::select('id','created_at','updated_at') + ->where($where) + ->min('created_at'); + + + $customer['begin_follow'] = Util::timeTotDate($begin_follow); +// $customer['created_at'] = Util::timeTotDate($customer['created_at']); +// $customer['updated_at'] = Util::timeTotDate($customer['updated_at']); + + $customer = Util::formatDate($customer,['follow_last']); + $nameLang = $language=='en'?'name_en':($language=='ru'?'name_ru':'name'); + $countryLang = $language=='en'?'en_addr':'cn_addr'; + $customer['car_type_name'] = empty($customer['car_type_id'])?'':CarType::where(['id'=>$customer['car_type_id']])->value($nameLang); + $customer['country_name'] = empty($customer['country_id'])?'':Country::where(['id'=>$customer['country_id']])->value($countryLang); + $customer['channel_name'] = empty($customer['channel_id'])?'':Channel::where(['id'=>$customer['channel_id']])->value($nameLang); +// $customer['last_follow'] = $last_follow; + + if ($tag) { + foreach ($customer['tags'] as &$tag) { + $tag['names'] = unserialize($tag['names']); + $tag['name'] = $tag['names'][$language]; + } + } + + return $customer; + } + + /** + * 客户的标签 + */ + public function tags() + { + return $this->belongsToMany(Tag::class, 'customer_tag', 'customer_id', 'tag_id') + ->withTimestamps(); // 如果需要记录关联时间 + } + + + // 定义与 变更历史 的一对多关系 + public function his() + { + return $this->hasMany(CustomerHis::class, 'customer_id', 'id'); + } + + // 定义反向多对一关联 + public function user() + { + return $this->belongsTo(User::class, 'user_id', 'id'); + } +} \ No newline at end of file diff --git a/app/crm/model/CustomerHis.php b/app/crm/model/CustomerHis.php new file mode 100644 index 0000000..b7039dd --- /dev/null +++ b/app/crm/model/CustomerHis.php @@ -0,0 +1,68 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 +// 'created_at' => 'timestamp', // 指定序列化格式 + ]; + + /** + * 重新格式化时间 + * @param DateTimeInterface $date + * @return string + */ + protected function serializeDate(DateTimeInterface $date): string + { + return $date->format(Carbon::parse($date)->toDateTimeString()); + } + + // 定义反向多对一关联 + public function customer() + { + return $this->belongsTo(Customer::class, 'customer_id', 'id'); + } + + // 禁用 updated_at 的自动更新 + public function setUpdatedAt($value) + { + return $this; // 不做任何操作 + } +} \ No newline at end of file diff --git a/app/crm/model/CustomerTag.php b/app/crm/model/CustomerTag.php new file mode 100644 index 0000000..b6a26bf --- /dev/null +++ b/app/crm/model/CustomerTag.php @@ -0,0 +1,36 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'follow_time' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 +// 'updated_at' => 'timestamp', // 指定序列化格式 +// 'created_at' => 'timestamp', // 指定序列化格式 + ]; + + public static function getFollow($follow,$domain = 'https://vehicle-album.oss-cn-beijing.aliyuncs.com') + { + $follow['type_txt'] = Constants::FOLLOW[$follow['type']]; + $follow['firstFollowtime'] = $follow['follow_time']; + $ossservice = new OssService(); + $oss_picture = $ossservice->generatePrivateUrl($follow['picture'],86400); + $follow['oss_picture'] = $oss_picture; +// $follow = Util::formatDate($follow); + return $follow; + } +} \ No newline at end of file diff --git a/app/crm/model/Tag.php b/app/crm/model/Tag.php new file mode 100644 index 0000000..4b00471 --- /dev/null +++ b/app/crm/model/Tag.php @@ -0,0 +1,55 @@ +belongsTo(TagGroup::class, 'group_id', 'id'); + } + + public function customers() + { + return $this->belongsToMany(Customer::class, 'customer_tag', 'tag_id', 'customer_id'); + } +} \ No newline at end of file diff --git a/app/crm/model/TagGroup.php b/app/crm/model/TagGroup.php new file mode 100644 index 0000000..9c2e419 --- /dev/null +++ b/app/crm/model/TagGroup.php @@ -0,0 +1,50 @@ +hasMany(Tag::class, 'group_id', 'id'); + } +} \ No newline at end of file diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php new file mode 100644 index 0000000..d21d460 --- /dev/null +++ b/app/crm/service/CustomerService.php @@ -0,0 +1,27 @@ +input('page', 1); +// $perPage = $request->input('per_page', 10); + $perPage = $params['size'] ?? 10; + $page = $params['current'] ?? 1; + $where = []; + $user_id =$params['user_id']; + if(empty($user_id) || $user_id == 0){ + $where[] = ['user_id', '=', $user_id]; + } + $user = Customer::with('user')->where($where)->paginate($perPage, ['*'], 'page', $page); + $user = $user->toArray(); + foreach ($user['data'] as &$item) { + unset($item['password']); + } + return $user; + } +} \ No newline at end of file diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php new file mode 100644 index 0000000..b870416 --- /dev/null +++ b/app/crm/service/FollowService.php @@ -0,0 +1,53 @@ +input('page', 1); +// $perPage = $request->input('per_page', 10); + + //客户id + $customer_id =$param['customer_id']; + + $where = []; + $where['customer_id'] = $customer_id; + + //用户角色 + $userId = $request->user['id']; + if(Util::isEmpty($userId)){ + return $this->error(trans('user_none')); + } + if($request->user['role']!=1){ + $where['user_id'] = $userId; + } + + //跟进id + $id = Util::getParam($request,'id'); + if(!Util::isEmpty($id)){ + $where['id'] = $id; + } + + // 执行分页查询 + $data = Follow::where($where) + ->orderBy('id', 'desc')->get(); +// ->paginate($perPage, ['*'], 'page', $page); + + +// $host = $request->host(); + $url = 'http:'.$request->url(); + foreach ($data as &$value){ + $value = Follow::getFollow($value); + } + + return $this->success($data,trans('success')); + } +} \ No newline at end of file diff --git a/app/crm/utils/Util.php b/app/crm/utils/Util.php new file mode 100644 index 0000000..db6743b --- /dev/null +++ b/app/crm/utils/Util.php @@ -0,0 +1,121 @@ +input($param,$default); + if(empty($param) && $default!==''){ + $param = $default; + } + return $param; + } + + /** + * 失败 + */ + public static function fail($data = [],$message = '', $code = -1) + { + $result = [ + 'code' => $code, + 'message' => $message, + 'data' => $data + ]; + return json($result); + } + + /** + * 成功 + */ + public static function success($data = [],$message = '', $code = 0) + { + $result = [ + 'code' => $code, + 'message' => $message, + 'data' => $data + ]; + return json($result); + } + + /** + * 分页查询返回 + */ + public static function page($data,$message = '', $code = 0) + { + $result = [ + 'code' => $code, + 'message' => $message + ]; + + $result['data'] = [ + 'list' => $data->items(), // 当前页数据 + 'total' => $data->total(), // 总记录数 + 'current_page' => $data->currentPage(), + 'per_page' => $data->perPage(), + 'last_page' => $data->lastPage() + ]; + return json($result); + } + + /** + * 日期格式转化 + */ + public static function timeTotDate($time = 0, $format = 'Y-m-d H:i:s') + { + if (empty($time)) { + return ''; + } + return date($format, $time); + } + + /** + * 格式化创建时间 + */ + public static function formatDate($value,$key = ['created_at','updated_at']) + { + foreach ($key as $k){ + $value[$k]= self::timeTotDate($value[$k]); + } + return $value; + } + + /** + * 格式化创建时间 + */ + public static function language($language,$en,$zh_CN,$ru) + { + if ($language == 'zh_CN'){ + return $zh_CN; + }elseif ($language == 'ru'){ + return $ru; + } + return $en; + } + +} \ No newline at end of file diff --git a/app/crm/validate/FollowValidate.php b/app/crm/validate/FollowValidate.php new file mode 100644 index 0000000..2ed6e71 --- /dev/null +++ b/app/crm/validate/FollowValidate.php @@ -0,0 +1,21 @@ + 'require', + ]; + + protected $message = [ + 'customer_id.require' => '用户id不能为空', + ]; + + protected $scene = [ + 'upwd_list' => ['customer_id'], + ]; +} \ No newline at end of file diff --git a/config/constants/Constants.php b/config/constants/Constants.php index 1e77a2b..6797078 100644 --- a/config/constants/Constants.php +++ b/config/constants/Constants.php @@ -34,4 +34,104 @@ class Constants self::TICKET_STATUS['REJECTED'] => 'Закрыть', ]; + //客户分级 USTOMER_GRADE + + public const GRADE = [ + 'INITIAL' => 0, // 初步接触 + 'COMMUNICATE' => 1, // 需求沟通 + 'PLAN' => 2, // 方案推进 + 'DEAL' => 3, // 成交阶段 + 'SALES' => 4, // 售后阶段 + 'LOSS' => 5, // 停滞或流失 + ]; + + public const GRADE_ZH = [ + self::GRADE['INITIAL'] => '初步接触', + self::GRADE['COMMUNICATE'] => '需求沟通', + self::GRADE['PLAN'] => '方案推进', + self::GRADE['DEAL'] => '成交阶段', + self::GRADE['SALES'] => '售后阶段', + self::GRADE['LOSS'] => '停滞或流失', + ]; + public const GRADE_EN = [ + self::GRADE['INITIAL'] => 'Initial contact', + self::GRADE['COMMUNICATE'] => 'Demand Chat', + self::GRADE['PLAN'] => 'promotion of the plan', + self::GRADE['DEAL'] => 'Transaction Stage', + self::GRADE['SALES'] => 'After Sales', + self::GRADE['LOSS'] => 'Stagnation or loss', + ]; + public const GRADE_RU = [ + self::GRADE['INITIAL'] => 'Первоначальные контакты', + self::GRADE['COMMUNICATE'] => 'Потребности в общении', + self::GRADE['PLAN'] => 'Продвижение программы', + self::GRADE['DEAL'] => 'Этап сделки', + self::GRADE['SALES'] => 'Послепродажный этап', + self::GRADE['LOSS'] => 'Застой или потеря', + ]; + + //客户重要性 + public const CUSTOMER_MAGNITUDE_INITIAL = 0; // 金牌商机 + public const CUSTOMER_MAGNITUDE_COMMUNICATE = 1; // 银牌商机 + public const CUSTOMER_MAGNITUDE_PLAN = 2; // 铜牌商机 + + public const MAGNITUDE = [ + self::CUSTOMER_MAGNITUDE_INITIAL => '金牌商机', + self::CUSTOMER_MAGNITUDE_COMMUNICATE => '银牌商机', + self::CUSTOMER_MAGNITUDE_PLAN => '铜牌商机', + ]; + public const MAGNITUDE_EN = [ + self::CUSTOMER_MAGNITUDE_INITIAL => 'Gold Medal', + self::CUSTOMER_MAGNITUDE_COMMUNICATE => 'Silver Medal', + self::CUSTOMER_MAGNITUDE_PLAN => 'Bronze Medal', + ]; + + public const MAGNITUDE_RU = [ + self::CUSTOMER_MAGNITUDE_INITIAL => 'Золотой бизнес', + self::CUSTOMER_MAGNITUDE_COMMUNICATE => 'Серебряный бизнес', + self::CUSTOMER_MAGNITUDE_PLAN => 'Бронзовый бизнес.', + ]; + + + //跟进类型 + public const CUSTOMER_FOLLOW_VISIT = 0; // 线下拜访 + public const CUSTOMER_FOLLOW_UP = 1; // 线上跟进 + + public const FOLLOW = [ + self::CUSTOMER_FOLLOW_VISIT => '线下拜访', + self::CUSTOMER_FOLLOW_UP => '线上跟进', + ]; + public const FOLLOW_EN = [ + self::CUSTOMER_FOLLOW_VISIT => 'Visit', + self::CUSTOMER_FOLLOW_UP => 'On line', + ]; + + //跟进类型 + public const CUSTOMER_SEX_UNKNOWN = 0; // 未知 + public const CUSTOMER_SEX_MAN = 1; // 男 + public const CUSTOMER_SEX_WOMAN = 2; // 女 + + public const SEX = [ + self::CUSTOMER_SEX_UNKNOWN => '未知', + self::CUSTOMER_SEX_MAN => '男', + self::CUSTOMER_SEX_WOMAN => '女', + ]; + public const SEX_EN = [ + self::CUSTOMER_SEX_UNKNOWN => 'unknown', + self::CUSTOMER_SEX_MAN => 'man', + self::CUSTOMER_SEX_WOMAN => 'woman', + ]; + + public const SEX_RU = [ + self::CUSTOMER_SEX_UNKNOWN => 'неизвестный', + self::CUSTOMER_SEX_MAN => 'мужской', + self::CUSTOMER_SEX_WOMAN => 'женский', + ]; + + public const SIGN = [ + ['id' => 'xinyouhe', 'name' => '新友合'], + ['id' => 'mbaf', 'name' => 'MAGIC BRIDGE AUTO FZE'] + ]; + + public const LANGUAGE = ['en', 'zh_CN','ru']; } \ No newline at end of file From 396e92ac1b19b06536e5c499097956e6fd8f648e Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 9 Jul 2025 17:58:30 +0800 Subject: [PATCH 023/120] =?UTF-8?q?=E6=96=B0=E5=A2=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/AuthController.php | 23 +- app/common/controller/BaseController.php | 9 + app/common/logic/AuthLogic.php | 451 +++++++-------- app/common/logic/AuthLogic_bak.php | 667 +++++++++++++++-------- app/common/logic/AuthLogic_bak2.php | 416 -------------- app/common/model/User.php | 3 +- app/common/model/UserFeishuApp.php | 69 +++ app/common/service/AuthService.php | 5 +- app/common/service/BasicDataService.php | 150 +++-- app/common/traits/ResponseTrait.php | 4 +- app/common/utils/Feishu.php | 16 +- app/common/validate/UserValidate.php | 13 +- app/controller/IndexController.php | 72 +-- app/functions.php | 38 ++ app/middleware/AuthCheck.php | 46 ++ app/middleware/BasicData.php | 42 ++ app/middleware/BasicDataMiddleware.php | 30 - app/view/index/qrcode.html | 46 ++ config/middleware.php | 4 +- config/route.php | 11 +- config/whitelist.php | 9 + public/mzl.html | 64 +++ 22 files changed, 1120 insertions(+), 1068 deletions(-) delete mode 100644 app/common/logic/AuthLogic_bak2.php create mode 100644 app/common/model/UserFeishuApp.php create mode 100644 app/middleware/AuthCheck.php create mode 100644 app/middleware/BasicData.php delete mode 100644 app/middleware/BasicDataMiddleware.php create mode 100644 app/view/index/qrcode.html create mode 100644 config/whitelist.php create mode 100644 public/mzl.html diff --git a/app/common/controller/AuthController.php b/app/common/controller/AuthController.php index c03c7f6..4129ee0 100644 --- a/app/common/controller/AuthController.php +++ b/app/common/controller/AuthController.php @@ -9,6 +9,23 @@ use support\Request; class AuthController extends BaseController { + /** + * 不需要头信息的方法 + */ + protected $noNeedHeader = ['feishuAuth']; + + + // 调试 + public function feishuAuth(Request $request) + { + try { + $code = $request->all(); + return $this->success($code); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + public function feishuScope(Request $request) { try { @@ -22,7 +39,7 @@ class AuthController extends BaseController public function login(Request $request) { try { - $data = $this->validate(new UserValidate(), $request->post(), 'upwd_login'); + $data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_login'); $result = (new AuthService())->login($data); return $this->success($result, '登录成功'); } catch (\Throwable $e) { @@ -33,11 +50,11 @@ class AuthController extends BaseController public function register(Request $request) { try { - $data = $this->validate(new UserValidate(), $request->post(), 'upwd_register'); + $data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_register'); $result = (new AuthService())->register($data); return $this->success($result, '注册成功'); } catch (\Throwable $e) { - return $this->error($e->getMessage().'|'.$e->getFile().'|'.$e->getLine()); + return $this->error($e->getMessage()); } } diff --git a/app/common/controller/BaseController.php b/app/common/controller/BaseController.php index f399906..0fefb82 100644 --- a/app/common/controller/BaseController.php +++ b/app/common/controller/BaseController.php @@ -5,6 +5,7 @@ namespace app\common\controller; use support\exception\BusinessException; use app\common\traits\ResponseTrait; use think\Validate; +use support\Request; abstract class BaseController { @@ -35,4 +36,12 @@ abstract class BaseController // 验证成功,返回原始数据 return $data; } + + /** + * 参数处理 + */ + public function requestData(Request $request) + { + return $request->all() + $request->basic_data; + } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index 0270f17..3c84694 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -7,7 +7,9 @@ use app\common\model\CorpFeishuApp; use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; +use app\common\model\UserFeishuApp; use app\common\model\UserSn; +use app\common\utils\Feishu; use app\common\utils\Jwt; use support\Redis; use support\exception\BusinessException; @@ -21,47 +23,27 @@ class AuthLogic public function uPwdLogin(array $data): array { $user = $this->validateUser($data['account'], $data['password']); - $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** * 飞书登录 + * 如果用户不存在则新增用户信息 */ public function feishuLogin(array $data): array { - $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); + $feishuUserInfo = $this->getFeishuUserInfo($data); $user = $this->getOrCreateUser($feishuUserInfo); $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + if ($user->isLogin) { + $this->validateSystemPermission($user, $system); + } - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** @@ -71,25 +53,15 @@ class AuthLogic { $userId = $this->validateQrCodeToken($data['qr_code_token']); $user = User::find($userId); + if (!$user || $user->status != 1) { throw new BusinessException('用户无效', 403); } $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** @@ -104,29 +76,143 @@ class AuthLogic // 对密码进行哈希处理 $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + // 获取企业ID + $corpInfo = $this->validateCorp($data['corp_abbr']); + $data['corp_id'] = $corpInfo->id; + + // 处理账户 + $data = $data + normalizeAccount($data['account']); + // 创建用户记录 $user = $this->createUser($data); // 更新用户编号的用户 ID $this->updateUserId($user->id, $sn->user_sn); - // 注册后如果直接登录(返回token) $system = $this->validateSystem($data['system_code']); + return $this->generateTokensAndReturn($user, $system, $data); + } + + /** ----------- 内部方法 ------------- **/ + + /** + * 查找或创建飞书用户 + */ + private function getOrCreateUser(array $feishuUserInfo) + { + $userFeishuApp = UserFeishuApp::firstOrCreate([ + 'name' => $feishuUserInfo['en_name'], + 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'] + ]); + + if ($userFeishuApp->uid) { + $user = User::find($userFeishuApp->uid); + $user->isLogin = true; + return $user; + } else { + $user = User::where('phone', $userFeishuApp->mobile)->first(); + $user->isLogin = true; + if (!$user) { + $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); + $user->isLogin = false; + } + $userFeishuApp->uid = $user->id; + $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; + if (!$userFeishuApp->save()) { + throw new BusinessException('绑定用户飞书记录失败'); + } + return $user; + } + } + + /** + * 过滤和格式化手机号 + */ + private function sanitizePhone($mobile) + { + return isset($mobile) ? str_replace('+86', '', $mobile) : ''; + } + + /** + * 创建飞书用户 + */ + private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) + { + // 1. 获取适用的用户编号 + $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$userSn) { + throw new BusinessException('未找到适用编号'); + } + + // 2. 创建用户 + $user = new User(); + $user->sn = $userSn->user_sn; + $user->name = $feishuUserInfo['en_name']; + $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; + $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); + $user->phone = $userFeishuApp->mobile; + $user->email = ''; + $user->system_code = $feishuUserInfo['system_code']; + $user->corp_id = $feishuUserInfo['corp_id']; + + if (!$user->save()) { + throw new BusinessException('用户创建失败'); + } + + // 更新使用状态 + $this->updateUserId($user->id, $userSn->user_sn); + + return $user; + } + + /** + * 生成并记录token,返回包含token的响应数据 + */ + private function generateTokensAndReturn(User $user, Systems $system, array $data): array + { $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + + // 记录 token 到 Redis 和 MySQL $this->recordUserToken($user->id, $token, $data['system_code']); return [ - 'user' => $user, 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, + 'system_code' => $data['system_code'], + 'corp_abbr' => $data['corp_abbr'], + 'login_type' => $data['login_type'] + // 'payload' => $payload, ]; } - public function updateUserId($userId, $userSn) + /** + * 获取飞书用户信息 + */ + private function getFeishuUserInfo(array $data) + { + $certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']); + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + $userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']); + $userInfo = $feishu->userInfo($userAccessToken['access_token']); + + if ($userInfo['code'] != 0) { + throw new BusinessException('授权失败,请重试'); + } + + return $userInfo['data'] + $data + $certificate; + } + + /** + * 更新用户编号 + */ + private function updateUserId($userId, $userSn) { $updated = UserSn::where('user_sn', $userSn) ->update(['user_id' => $userId]); @@ -138,81 +224,6 @@ class AuthLogic return $updated; } - /** - * 获取可用的用户编号 - * - * @return \app\common\model\UserSn 用户编号模型实例 - * @throws BusinessException 当未找到适用编号时抛出 - */ - private function getAvailableUserSn(): \app\common\model\UserSn - { - $userSnModel = new UserSn(); - $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$sn) { - Log::error('用户注册时未找到适用编号'); - throw new BusinessException('未找到适用编号'); - } - return $sn; - } - - /** - * 创建用户记录 - * - * @param array $data 包含用户信息的数组 - * @return \app\common\model\User 用户模型实例 - */ - private function createUser(array $data): \app\common\model\User - { - try { - return User::create($data); - } catch (\Throwable $e) { - Log::error('用户注册时创建用户记录失败', [ - 'data' => $data, - 'error' => $e->getMessage() - ]); - throw new BusinessException('用户注册失败,请稍后重试', 500); - } - } - - /** - * 刷新token - * @param string $refreshToken - * @param string|null $systemCode 可选,指定系统 - * @return array - */ - public function refreshToken(string $refreshToken, ?string $systemCode = null): array - { - if (!$systemCode) throw new BusinessException('缺少system_code', 422); - - // 校验refresh_token - $userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}"); - if (!$userId) { - throw new BusinessException('refresh_token无效或已过期', 401); - } - $validToken = Redis::get("refresh_token:{$userId}:{$systemCode}"); - if ($validToken !== $refreshToken) { - throw new BusinessException('refresh_token无效或已过期', 401); - } - - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = $this->validateSystem($systemCode); - $this->validateSystemPermission($user, $system); - - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $this->recordUserToken($user->id, $token, $systemCode); - - return [ - 'access_token' => $token, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - /** * 记录用户token到 Redis 和 MySQL */ @@ -256,7 +267,6 @@ class AuthLogic } } - /** * 校验token有效性 */ @@ -280,78 +290,25 @@ class AuthLogic } /** - * 登出/失效token + * 获取飞书应用信息 */ - public function logout(?string $token, ?string $systemCode = null) + public function feishuAppInfo(string $corpAbbr, string $systemCode) { - if ($token) { - Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - $aud = $payload['aud'] ?? null; - $sc = $systemCode ?? $aud; - if ($userId && $sc) { - Redis::sRem("user_tokens:{$userId}:{$sc}", $token); - } - Redis::del("user_token_meta:{$token}"); - UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } + $corpInfo = $this->validateCorp($corpAbbr); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); + return [ + 'app_id' => $fsAppInfo->app_id, + 'app_secret' => $fsAppInfo->app_secret, + 'r_url' => $fsAppInfo->r_url, + 'scope' => $fsAppInfo->scopes, + 'state' => $fsAppInfo->state, + 'abbr' => $corpAbbr, + 'corp_id' => $corpInfo->id + ]; } /** - * 强制拉黑token - */ - public function blockToken(string $token, ?string $systemCode = null) - { - if (!$token) { - throw new BusinessException('token不能为空', 422); - } - Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - $aud = $payload['aud'] ?? null; - $sc = $systemCode ?? $aud; - if ($userId && $sc) { - Redis::sRem("user_tokens:{$userId}:{$sc}", $token); - } - Redis::del("user_token_meta:{$token}"); - UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } - - /** - * 获取用户所有活跃token及元信息(按system_code) - */ - public function getUserTokens(int $userId, string $systemCode): array - { - $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; - $result = []; - foreach ($tokens as $token) { - $meta = Redis::hGetAll("user_token_meta:{$token}"); - $result[] = [ - 'token' => $token, - 'meta' => $meta, - ]; - } - return $result; - } - - /** - * 强制某用户所有token全部下线 - */ - public function blockAllTokensOfUser(int $userId, string $systemCode) - { - $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; - foreach ($tokens as $token) { - $this->blockToken($token, $systemCode); - } - } - - /** ----------- 内部方法 ------------- **/ - - /** - * 用户验证 + * 验证用户信息 */ private function validateUser(string $account, string $password) { @@ -366,7 +323,7 @@ class AuthLogic } /** - * 系统验证 + * 验证系统信息 */ private function validateSystem(string $systemCode) { @@ -378,7 +335,7 @@ class AuthLogic } /** - * 公司验证 + * 验证公司信息 */ private function validateCorp(string $corpAbbr) { @@ -390,7 +347,7 @@ class AuthLogic } /** - * 飞书应用信息 + * 验证飞书应用信息 */ private function validateFeishuApp(int $corpId, string $systemCode) { @@ -402,7 +359,7 @@ class AuthLogic } /** - * 用户系统权限校验 + * 验证用户系统权限 */ private function validateSystemPermission(User $user, Systems $system) { @@ -414,6 +371,54 @@ class AuthLogic } } + /** + * 验证扫码令牌 + */ + private function validateQrCodeToken(string $qrCodeToken) + { + $userId = Redis::get("qr_code_token:{$qrCodeToken}"); + if (!$userId) { + throw new BusinessException('扫码令牌无效或已过期', 401); + } + return $userId; + } + + /** + * 获取可用的用户编号 + * + * @return \app\common\model\UserSn 用户编号模型实例 + * @throws BusinessException 当未找到适用编号时抛出 + */ + private function getAvailableUserSn(): \app\common\model\UserSn + { + $userSnModel = new UserSn(); + $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$sn) { + Log::error('用户注册时未找到适用编号'); + throw new BusinessException('未找到适用编号'); + } + return $sn; + } + + /** + * 创建用户记录 + * + * @param array $data 包含用户信息的数组 + * @return \app\common\model\User 用户模型实例 + */ + private function createUser(array $data): \app\common\model\User + { + try { + return User::create($data); + } catch (\Throwable $e) { + Log::error('用户注册时创建用户记录失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('用户注册失败,请稍后重试', 500); + } + } + /** * JWT payload 构建 * 可扩展 payload 字段 @@ -458,70 +463,4 @@ class AuthLogic Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration')); return $refreshToken; } - - /** - * 验证扫码令牌 - */ - private function validateQrCodeToken(string $qrCodeToken) - { - $userId = Redis::get("qr_code_token:{$qrCodeToken}"); - if (!$userId) { - throw new BusinessException('扫码令牌无效或已过期', 401); - } - return $userId; - } - - /** - * 调用飞书API获取用户信息(示例) - */ - private function getFeishuUserInfo(string $code) - { - // // 获取凭证 - // $certificate = certificate($param['state']); - // // 获取token - // $class = new Feishu($certificate['appId'], $certificate['appSecre_s'], $param['state']); - // $userAccessToken = $class->userAccessToken($param['code'], $certificate['rUrl']); - // // 获取用户信息 - // $rst = $class->userInfo($userAccessToken['access_token']); - // if ($rst['code'] != 0) { - // throw new BusinessException('授权失败,请重试'); - // } - // 实际实现需要调用飞书 API - return [ - 'user_id' => 'feishu_user_123', - 'name' => '飞书用户', - ]; - } - - /** - * 查找或创建飞书用户(示例) - */ - private function getOrCreateUser(array $feishuUserInfo) - { - $user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first(); - if (!$user) { - $user = new User(); - $user->feishu_user_id = $feishuUserInfo['user_id']; - $user->name = $feishuUserInfo['name']; - $user->status = 1; - $user->save(); - } - return $user; - } - - /** - * 飞书应用信息 - */ - public function feishuAppInfo(string $corpAbbr, string $systemCode) - { - $corpInfo = $this->validateCorp($corpAbbr); - $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); - return [ - 'app_id' => $fsAppInfo->app_id, - 'app_secret' => $fsAppInfo->app_secret, - 'r_url' => $fsAppInfo->r_url, - 'scope' => $fsAppInfo->scopes, - 'state' => $fsAppInfo->state - ]; - } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic_bak.php b/app/common/logic/AuthLogic_bak.php index d33cd0e..0e68093 100644 --- a/app/common/logic/AuthLogic_bak.php +++ b/app/common/logic/AuthLogic_bak.php @@ -2,357 +2,592 @@ namespace app\common\logic; +use app\common\model\Corp; +use app\common\model\CorpFeishuApp; +use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; +use app\common\model\UserFeishuApp; use app\common\model\UserSn; +use app\common\utils\Feishu; use app\common\utils\Jwt; -use support\Cache; +use support\Redis; use support\exception\BusinessException; +use support\Log; class AuthLogic_bak { - /** - * 账号密码登录方法 - * - * 该方法接收包含账号、密码和系统编码的数据数组,验证用户信息和系统信息, - * 检查用户对系统的权限,生成访问令牌和刷新令牌并返回。 - * - * @param array $data 包含登录信息的数组,键名为 'account'(账号)、'password'(密码)、'system_code'(系统编码) - * @return array 包含访问令牌、刷新令牌和令牌过期时间的数组 + * 账号密码登录 */ public function uPwdLogin(array $data): array { - // 验证用户账号和密码,返回用户对象 - $user = $this->validateUser($data['account'], $data['password']); - // 验证系统编码,返回系统对象 - $system = $this->validateSystem($data['system_code']); - // 验证用户对该系统是否有访问权限 - $this->validateSystemPermission($user, $system); - // 准备 JWT 令牌的负载数据 - $payload = $this->preparePayload($user, $system); - // 根据负载数据生成访问令牌 - $token = $this->generateAccessToken($payload); - // 根据用户 ID 生成刷新令牌 - $refreshToken = $this->generateRefreshToken($user->id); + $user = $this->validateUser($data['account'], $data['password']); + + $system = $this->validateSystem($data['system_code']); + $this->validateSystemPermission($user, $system); + $payload = $this->buildJwtPayload($user, $system); + $token = $this->generateAccessToken($payload); + $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + + // 记录 token 到 Redis 和 MySQL + $this->recordUserToken($user->id, $token, $data['system_code']); - // 返回包含访问令牌、刷新令牌和令牌过期时间的数组 return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, ]; } - /** * 飞书登录 - * - * @param array $data - * @return array + * 如果用户不存在则新增用户信息 */ - public function feishuLogin(array $data): array + public function feishuLogin(array $data, bool $isLogin = true): array { - $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); + $feishuUserInfo = $this->getFeishuUserInfo($data); $user = $this->getOrCreateUser($feishuUserInfo); $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->preparePayload($user, $system); + if ($user->isLogin === true) { + $this->validateSystemPermission($user, $system); + } + $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); + $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + + // 记录 token 到 Redis 和 MySQL + $this->recordUserToken($user->id, $token, $data['system_code']); return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, ]; } /** * 扫码登录 - * - * @param array $data - * @return array */ public function qrCodeLogin(array $data): array { $userId = $this->validateQrCodeToken($data['qr_code_token']); $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new \Exception('用户无效', 403); + throw new BusinessException('用户无效', 403); } $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); - $payload = $this->preparePayload($user, $system); + $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); + $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + + // 记录 token 到 Redis 和 MySQL + $this->recordUserToken($user->id, $token, $data['system_code']); return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, ]; } /** - * 验证用户信息 + * 注册 + */ + public function register(array $data) + { + // 获取可用用户编号 + $sn = $this->getAvailableUserSn(); + $data['sn'] = $sn->user_sn; + + // 对密码进行哈希处理 + $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + + // 获取企业ID + $corpInfo = $this->validateCorp($data['corp_abbr']); + $data['corp_id'] = $corpInfo->id; + + // 创建用户记录 + $user = $this->createUser($data); + + // 更新用户编号的用户 ID + $this->updateUserId($user->id, $sn->user_sn); + + // 注册后如果直接登录(返回token) + $system = $this->validateSystem($data['system_code']); + $payload = $this->buildJwtPayload($user, $system); + $token = $this->generateAccessToken($payload); + $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + $this->recordUserToken($user->id, $token, $data['system_code']); + + return [ + 'user' => $user, + 'access_token' => $token, + 'refresh_token' => $refreshToken, + 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, + ]; + } + + public function updateUserId($userId, $userSn) + { + $updated = UserSn::where('user_sn', $userSn) + ->update(['user_id' => $userId]); + + if ($updated === 0) { + throw new BusinessException('用户编号不存在或更新失败'); + } + + return $updated; + } + + /** + * 获取可用的用户编号 * - * @param string $account - * @param string $password - * @return User|null + * @return \app\common\model\UserSn 用户编号模型实例 + * @throws BusinessException 当未找到适用编号时抛出 + */ + private function getAvailableUserSn(): \app\common\model\UserSn + { + $userSnModel = new UserSn(); + $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$sn) { + Log::error('用户注册时未找到适用编号'); + throw new BusinessException('未找到适用编号'); + } + return $sn; + } + + /** + * 创建用户记录 + * + * @param array $data 包含用户信息的数组 + * @return \app\common\model\User 用户模型实例 + */ + private function createUser(array $data): \app\common\model\User + { + try { + return User::create($data); + } catch (\Throwable $e) { + Log::error('用户注册时创建用户记录失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('用户注册失败,请稍后重试', 500); + } + } + + /** + * 刷新token + * @param string $refreshToken + * @param string|null $systemCode 可选,指定系统 + * @return array + */ + public function refreshToken(string $refreshToken, ?string $systemCode = null): array + { + if (!$systemCode) throw new BusinessException('缺少system_code', 422); + + // 校验refresh_token + $userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}"); + if (!$userId) { + throw new BusinessException('refresh_token无效或已过期', 401); + } + $validToken = Redis::get("refresh_token:{$userId}:{$systemCode}"); + if ($validToken !== $refreshToken) { + throw new BusinessException('refresh_token无效或已过期', 401); + } + + $user = User::find($userId); + if (!$user || $user->status != 1) { + throw new BusinessException('用户无效', 403); + } + + $system = $this->validateSystem($systemCode); + $this->validateSystemPermission($user, $system); + + $payload = $this->buildJwtPayload($user, $system); + $token = $this->generateAccessToken($payload); + $this->recordUserToken($user->id, $token, $systemCode); + + return [ + 'access_token' => $token, + 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, + ]; + } + + /** + * 记录用户token到 Redis 和 MySQL + */ + protected function recordUserToken(int $userId, string $token, string $systemCode) + { + $request = request(); + $ip = $request ? $request->getRealIp() : ''; + $ua = $request ? $request->header('user-agent', '') : ''; + $now = time(); + $expireAt = $now + config('jwt.jwt_expiration'); + + // Redis Set 用户token集合 + Redis::sAdd("user_tokens:{$userId}:{$systemCode}", $token); + Redis::expire("user_tokens:{$userId}:{$systemCode}", config('jwt.jwt_expiration')); + + // Redis Hash token元信息 + $meta = [ + 'system_code' => $systemCode, + 'login_ip' => $ip, + 'user_agent' => $ua, + 'login_time' => $now, + 'expired_at' => $expireAt, + ]; + Redis::hMSet("user_token_meta:{$token}", $meta); + Redis::expire("user_token_meta:{$token}", config('jwt.jwt_expiration')); + + // 可选:MySQL 持久化 + try { + UserTokenLog::create([ + 'user_id' => $userId, + 'token' => $token, + 'system_code'=> $systemCode, + 'login_ip' => $ip, + 'user_agent' => $ua, + 'login_time' => $now, + 'expired_at' => $expireAt, + 'is_valid' => 1, + ]); + } catch (\Throwable $e) { + Log::error('Token log write failed: '.$e->getMessage()); + } + } + + + /** + * 校验token有效性 + */ + public function verifyToken(string $token, string $systemCode): array + { + if (!$systemCode) throw new BusinessException('缺少system_code', 422); + + $payload = (new Jwt())->verifyToken($token); + + // 校验黑名单 + if (Redis::has("token_blacklist:{$token}")) { + throw new BusinessException('token已失效', 403); + } + + // 强制校验aud(系统隔离) + if ($systemCode && $payload['aud'] != $systemCode && $payload['aud'] != ($payload['system_code'] ?? '')) { + throw new BusinessException('token不属于当前系统', 403); + } + + return (array)$payload; + } + + /** + * 登出/失效token + */ + public function logout(?string $token, ?string $systemCode = null) + { + if ($token) { + Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); + $payload = null; + try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} + $userId = $payload['uid'] ?? null; + $aud = $payload['aud'] ?? null; + $sc = $systemCode ?? $aud; + if ($userId && $sc) { + Redis::sRem("user_tokens:{$userId}:{$sc}", $token); + } + Redis::del("user_token_meta:{$token}"); + UserTokenLog::where('token', $token)->update(['is_valid'=>0]); + } + } + + /** + * 强制拉黑token + */ + public function blockToken(string $token, ?string $systemCode = null) + { + if (!$token) { + throw new BusinessException('token不能为空', 422); + } + Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); + $payload = null; + try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} + $userId = $payload['uid'] ?? null; + $aud = $payload['aud'] ?? null; + $sc = $systemCode ?? $aud; + if ($userId && $sc) { + Redis::sRem("user_tokens:{$userId}:{$sc}", $token); + } + Redis::del("user_token_meta:{$token}"); + UserTokenLog::where('token', $token)->update(['is_valid'=>0]); + } + + /** + * 获取用户所有活跃token及元信息(按system_code) + */ + public function getUserTokens(int $userId, string $systemCode): array + { + $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; + $result = []; + foreach ($tokens as $token) { + $meta = Redis::hGetAll("user_token_meta:{$token}"); + $result[] = [ + 'token' => $token, + 'meta' => $meta, + ]; + } + return $result; + } + + /** + * 强制某用户所有token全部下线 + */ + public function blockAllTokensOfUser(int $userId, string $systemCode) + { + $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; + foreach ($tokens as $token) { + $this->blockToken($token, $systemCode); + } + } + + /** ----------- 内部方法 ------------- **/ + + /** + * 用户验证 */ private function validateUser(string $account, string $password) { $user = User::byAccount($account)->first(); if (!$user || !password_verify($password, $user->password)) { - throw new \Exception('账号或密码错误', 401); + throw new BusinessException('账号或密码错误', 401); } - if ($user->status != 1) { - throw new \Exception('账号已禁用', 403); + throw new BusinessException('账号已禁用', 403); } - return $user; } /** - * 验证系统信息 - * - * @param string $systemCode - * @return Systems|null + * 系统验证 */ private function validateSystem(string $systemCode) { $system = Systems::where('code', $systemCode)->first(); if (!$system) { - throw new \Exception('系统不存在', 403); + throw new BusinessException('系统不存在', 403); } - return $system; } /** - * 验证用户系统权限 - * - * @param User $user - * @param Systems $system + * 公司验证 + */ + private function validateCorp(string $corpAbbr) + { + $corp = Corp::where('abbr', $corpAbbr)->first(); + if (!$corp) { + throw new BusinessException('公司不存在', 403); + } + return $corp; + } + + /** + * 飞书应用信息 + */ + private function validateFeishuApp(int $corpId, string $systemCode) + { + $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first(); + if (!$feishuAppInfo) { + throw new BusinessException('飞书应用不存在', 403); + } + return $feishuAppInfo; + } + + /** + * 用户系统权限校验 */ private function validateSystemPermission(User $user, Systems $system) { if ($system->is_role) { $systemIds = $user->systems->pluck('id')->toArray(); if (!in_array($system->id, $systemIds)) { - throw new \Exception('无该系统权限', 403); + throw new BusinessException('无该系统权限', 403); } } } /** - * 准备 JWT 负载 - * - * @param User $user - * @param Systems $system - * @return array + * JWT payload 构建 + * 可扩展 payload 字段 */ - private function preparePayload(User $user, Systems $system) + private function buildJwtPayload(User $user, ?Systems $system = null): array { - $payload = [ - 'uid' => $user->id, - 'sub' => 'user_'.$user->id, - 'aud' => $system->domain_url, - 'role_ids' => [], - 'system_ids' => [] + $now = time(); + $exp = $now + config('jwt.jwt_expiration'); + $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; + $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; + + return [ + 'sub' => 'user_' . $user->id, + 'aud' => $system->code, + 'iat' => $now, + 'nbf' => $now, + 'exp' => $exp, + 'typ' => 'access', + 'uid' => $user->id, + 'role_ids' => $role_ids, + 'system_ids' => $system_ids ]; - - if ($system->is_role) { - $systemIds = $user->systems->pluck('id')->toArray(); - $payload['role_ids'] = $user->roles->pluck('id')->toArray(); - $payload['system_ids'] = $systemIds; - } - - return $payload; } /** - * 生成访问令牌 - * - * @param array $payload - * @return string + * JWT签发 */ private function generateAccessToken(array $payload) { return (new Jwt())->generateToken($payload); } - // private function verifyRefreshToken(string $token) - // { - // return (new Jwt())->verifyToken($token); - // } - /** - * 生成刷新令牌 - * - * @param int $userId - * @return string + * 刷新令牌生成 */ - private function generateRefreshToken(int $userId) + private function generateRefreshToken(int $userId, string $systemCode) { $refreshToken = bin2hex(random_bytes(32)); - Cache::set("refresh_token:{$refreshToken}", $userId, config('jwt.jwt_refresh_token_expiration')); + // user_id+system_code只允许一个refresh_token生效 + Redis::set("refresh_token:{$userId}:{$systemCode}", $refreshToken, config('jwt.jwt_refresh_token_expiration')); + // 建立refresh_token到user_id+system_code映射,便于校验 + Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration')); return $refreshToken; } - /** - * 调用飞书 API 获取用户信息 - * - * @param string $code - * @return array - */ - private function getFeishuUserInfo(string $code) - { - // 实际实现需要调用飞书 API,这里只是示例 - return [ - 'user_id' => 'feishu_user_123', - 'name' => '飞书用户', - ]; - } - - /** - * 根据飞书用户信息查找或创建本地用户 - * - * @param array $feishuUserInfo - * @return User - */ - private function getOrCreateUser(array $feishuUserInfo) - { - // 实际实现需要根据飞书用户信息查找或创建本地用户,这里只是示例 - $user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first(); - if (!$user) { - $user = new User(); - $user->feishu_user_id = $feishuUserInfo['user_id']; - $user->name = $feishuUserInfo['name']; - $user->status = 1; - $user->save(); - } - return $user; - } - /** * 验证扫码令牌 - * - * @param string $qrCodeToken - * @return int */ private function validateQrCodeToken(string $qrCodeToken) { - // 实际实现需要验证扫码令牌,这里只是示例 - $userId = Cache::get("qr_code_token:{$qrCodeToken}"); + $userId = Redis::get("qr_code_token:{$qrCodeToken}"); if (!$userId) { - throw new \Exception('扫码令牌无效或已过期', 401); + throw new BusinessException('扫码令牌无效或已过期', 401); } return $userId; } /** - * 处理用户注册逻辑 - * - * @param array $data - * @return User + * 获取飞书用户信息 */ - public function register(array $data) + private function getFeishuUserInfo(array $data) { - $userSnModel = new UserSn(); - $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$sn) { + // 获取凭证 + $certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']); + // 获取token + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + $userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']); + // 获取用户信息 + $userInfo = $feishu->userInfo($userAccessToken['access_token']); + if ($userInfo['code'] != 0) { + throw new BusinessException('授权失败,请重试'); + } + return $userInfo['data'] + $data + $certificate; + } + + /** + * 查找或创建飞书用户 + */ + private function getOrCreateUser(array $feishuUserInfo) + { + $userFeishuApp = UserFeishuApp::firstOrCreate([ + 'name' => $feishuUserInfo['en_name'], + 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'] + ]); + + if ($userFeishuApp->uid) { + $user = User::find($userFeishuApp->uid); + $user->isLogin = true; + return $user; + } else { + $user = User::where('phone', $userFeishuApp->mobile)->first(); + $user->isLogin = true; + if (!$user) { + $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); + $user->isLogin = false; + } + $userFeishuApp->uid = $user->id; + $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; + if (!$userFeishuApp->save()) { + throw new BusinessException('绑定用户飞书记录失败'); + } + return $user; + } + } + + /** + * 过滤和格式化手机号 + */ + private function sanitizePhone($mobile) + { + return isset($mobile) ? str_replace('+86', '', $mobile) : ''; + } + + /** + * 创建飞书用户 + */ + private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) + { + // 1. 获取适用的用户编号 + $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$userSn) { throw new BusinessException('未找到适用编号'); } - $data['sn'] = $sn->user_sn; - $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); - $user = User::create($data); - return $this->updateUserId($user->id, $sn->user_sn); - } + // 2. 创建用户 + $user = new User(); + $user->sn = $userSn->user_sn; + $user->name = $feishuUserInfo['en_name']; + $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; + $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); + $user->phone = $userFeishuApp->mobile; + $user->email = ''; + $user->system_code = $feishuUserInfo['system_code']; + $user->corp_id = $feishuUserInfo['corp_id']; - public function updateUserId($userId, $userSn) - { - $userSn = User::where('user_sn', $userSn)->first(); - if (!$userSn) { - throw new BusinessException('用户编号不存在'); + if (!$user->save()) { + throw new BusinessException('用户创建失败'); } - return $userSn->update([ - 'user_id' => $userId - ]); + + // 更新使用状态 + $this->updateUserId($user->id, $userSn->user_sn); + + return $user; } + /** - * 校验token + * 飞书应用信息 */ - public function verifyToken(string $token): array + public function feishuAppInfo(string $corpAbbr, string $systemCode) { - $payload = (new Jwt())->verifyToken($token); - if (Cache::has("token_blacklist:{$token}")) { - throw new \Exception('token已失效', 403); - } - return (array)$payload; - } - - /** - * 刷新token - * @param string $refreshToken - * @return array - */ - public function refreshToken(string $refreshToken): array - { - $userId = Cache::get("refresh_token:{$refreshToken}"); - if (!$userId) { - throw new \Exception('refresh_token无效或已过期', 401); - } - - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new \Exception('用户无效', 403); - } - - // 获取角色ID、系统ID(和你登录时保持一致) - $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; - $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; - - $payload = [ - 'uid' => $user->id, - 'sub' => 'user_' . $user->id, - 'role_ids' => $role_ids, - 'system_ids' => $system_ids, - ]; - // 可补充aud、exp等字段,保持和登录生成payload一致 - - $token = (new Jwt())->generateToken($payload); - + $corpInfo = $this->validateCorp($corpAbbr); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); return [ - 'access_token' => $token, - 'expires_in' => config('jwt.jwt_expiration'), + 'app_id' => $fsAppInfo->app_id, + 'app_secret' => $fsAppInfo->app_secret, + 'r_url' => $fsAppInfo->r_url, + 'scope' => $fsAppInfo->scopes, + 'state' => $fsAppInfo->state, + 'abbr' => $corpAbbr, + 'corp_id' => $corpInfo->id ]; } - - /** - * 登出,将token加入黑名单 - */ - public function logout(?string $token) - { - if ($token) { - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - } - } - - - public function blockToken(string $token) - { - if (!$token) { - throw new \Exception('token不能为空', 422); - } - // 加入黑名单 - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic_bak2.php b/app/common/logic/AuthLogic_bak2.php deleted file mode 100644 index 56f94a7..0000000 --- a/app/common/logic/AuthLogic_bak2.php +++ /dev/null @@ -1,416 +0,0 @@ -validateUser($data['account'], $data['password']); - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); - - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, // 方便前端调试,可移除 - ]; - } - - /** - * 飞书登录 - */ - public function feishuLogin(array $data): array - { - $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); - $user = $this->getOrCreateUser($feishuUserInfo); - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 扫码登录 - */ - public function qrCodeLogin(array $data): array - { - $userId = $this->validateQrCodeToken($data['qr_code_token']); - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 注册 - */ - public function register(array $data) - { - $userSnModel = new UserSn(); - $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$sn) { - throw new BusinessException('未找到适用编号'); - } - $data['sn'] = $sn->user_sn; - $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); - $user = User::create($data); - $this->updateUserId($user->id, $sn->user_sn); - return $user; - } - - public function updateUserId($userId, $userSn) - { - $userSn = User::where('user_sn', $userSn)->first(); - if (!$userSn) { - throw new BusinessException('用户编号不存在'); - } - return $userSn->update([ - 'user_id' => $userId - ]); - } - - /** - * 刷新token - * @param string $refreshToken - * @param string|null $systemCode 可选,指定系统 - * @return array - */ - public function refreshToken(string $refreshToken, ?string $systemCode = null): array - { - $userId = Cache::get("refresh_token:{$refreshToken}"); - if (!$userId) { - throw new BusinessException('refresh_token无效或已过期', 401); - } - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = null; - if ($systemCode) { - $system = Systems::where('code', $systemCode)->first(); - if (!$system) { - throw new BusinessException('系统不存在', 403); - } - $this->validateSystemPermission($user, $system); - } - - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - - return [ - 'access_token' => $token, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 记录用户token到 Redis 和 MySQL - */ - protected function recordUserToken(int $userId, string $token) - { - $request = request(); - $ip = $request ? $request->getRealIp() : ''; - $ua = $request ? $request->header('user-agent', '') : ''; - $now = time(); - $expireAt = $now + config('jwt.jwt_expiration'); - - // Redis Set 用户token集合 - Cache::sAdd("user_tokens:{$userId}", $token); - Cache::expire("user_tokens:{$userId}", config('jwt.jwt_expiration')); - - // Redis Hash token元信息 - $meta = [ - 'login_ip' => $ip, - 'user_agent' => $ua, - 'login_time' => $now, - 'expired_at' => $expireAt, - ]; - Cache::hMSet("user_token_meta:{$token}", $meta); - Cache::expire("user_token_meta:{$token}", config('jwt.jwt_expiration')); - - // 可选:MySQL 持久化 - try { - UserTokenLog::create([ - 'user_id' => $userId, - 'token' => $token, - 'login_ip' => $ip, - 'user_agent' => $ua, - 'login_time' => $now, - 'expired_at' => $expireAt, - 'is_valid' => 1, - ]); - } catch (\Throwable $e) { - // 写库失败可忽略,但建议日志留存 - Log::error('Token log write failed: '.$e->getMessage()); - } - } - - - /** - * 校验token有效性 - */ - public function verifyToken(string $token): array - { - $payload = (new Jwt())->verifyToken($token); - if (Cache::has("token_blacklist:{$token}")) { - throw new BusinessException('token已失效', 403); - } - return (array)$payload; - } - - // /** - // * 登出/失效token - // */ - // public function logout(?string $token) - // { - // if ($token) { - // Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - // } - // } - - // /** - // * 强制拉黑token - // */ - // public function blockToken(string $token) - // { - // if (!$token) { - // throw new BusinessException('token不能为空', 422); - // } - // Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - // } - - public function logout(?string $token) - { - if ($token) { - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - // 1. 解析token查用户 - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - if ($userId) { - Cache::sRem("user_tokens:{$userId}", $token); - } - // 2. 删除meta - Cache::del("user_token_meta:{$token}"); - - // 3. 更新MySQL为失效 - \app\common\model\UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } - } - - public function blockToken(string $token) - { - if (!$token) { - throw new BusinessException('token不能为空', 422); - } - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - - // 1. 解析token查用户 - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - if ($userId) { - Cache::sRem("user_tokens:{$userId}", $token); - } - Cache::del("user_token_meta:{$token}"); - \app\common\model\UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } - - /** - * 获取用户所有活跃token及元信息 - */ - public function getUserTokens(int $userId): array - { - $tokens = Cache::sMembers("user_tokens:{$userId}") ?: []; - $result = []; - foreach ($tokens as $token) { - $meta = Cache::hGetAll("user_token_meta:{$token}"); - // 可选再查 MySQL 补充日志记录等 - $result[] = [ - 'token' => $token, - 'meta' => $meta, - ]; - } - return $result; - } - - /** - * 强制某用户所有token全部下线 - */ - public function blockAllTokensOfUser(int $userId) - { - $tokens = Cache::sMembers("user_tokens:{$userId}") ?: []; - foreach ($tokens as $token) { - $this->blockToken($token); - } - } - - /** ----------- 内部方法 ------------- **/ - - /** - * 用户验证 - */ - private function validateUser(string $account, string $password) - { - $user = User::byAccount($account)->first(); - if (!$user || !password_verify($password, $user->password)) { - throw new BusinessException('账号或密码错误', 401); - } - if ($user->status != 1) { - throw new BusinessException('账号已禁用', 403); - } - return $user; - } - - /** - * 系统验证 - */ - private function validateSystem(string $systemCode) - { - $system = Systems::where('code', $systemCode)->first(); - if (!$system) { - throw new BusinessException('系统不存在', 403); - } - return $system; - } - - /** - * 用户系统权限校验 - */ - private function validateSystemPermission(User $user, Systems $system) - { - if ($system->is_role) { - $systemIds = $user->systems->pluck('id')->toArray(); - if (!in_array($system->id, $systemIds)) { - throw new BusinessException('无该系统权限', 403); - } - } - } - - /** - * JWT payload 构建 - * 可扩展 payload 字段 - */ - private function buildJwtPayload(User $user, ?Systems $system = null): array - { - $now = time(); - $exp = $now + config('jwt.jwt_expiration'); - $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; - $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; - - return [ - 'sub' => 'user_' . $user->id, - 'aud' => $system ? $system->domain_url : '', - 'iat' => $now, - 'nbf' => $now, - 'exp' => $exp, - 'typ' => 'access', - 'uid' => $user->id, - 'role_ids' => $role_ids, - 'system_ids' => $system_ids - ]; - } - - /** - * JWT签发 - */ - private function generateAccessToken(array $payload) - { - return (new Jwt())->generateToken($payload); - } - - /** - * 刷新令牌生成 - */ - private function generateRefreshToken(int $userId) - { - $refreshToken = bin2hex(random_bytes(32)); - Cache::set("refresh_token:{$refreshToken}", $userId, config('jwt.jwt_refresh_token_expiration')); - return $refreshToken; - } - - /** - * 验证扫码令牌 - */ - private function validateQrCodeToken(string $qrCodeToken) - { - $userId = Cache::get("qr_code_token:{$qrCodeToken}"); - if (!$userId) { - throw new BusinessException('扫码令牌无效或已过期', 401); - } - return $userId; - } - - /** - * 调用飞书API获取用户信息(示例) - */ - private function getFeishuUserInfo(string $code) - { - // 实际实现需要调用飞书 API - return [ - 'user_id' => 'feishu_user_123', - 'name' => '飞书用户', - ]; - } - - /** - * 查找或创建飞书用户(示例) - */ - private function getOrCreateUser(array $feishuUserInfo) - { - $user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first(); - if (!$user) { - $user = new User(); - $user->feishu_user_id = $feishuUserInfo['user_id']; - $user->name = $feishuUserInfo['name']; - $user->status = 1; - $user->save(); - } - return $user; - } -} \ No newline at end of file diff --git a/app/common/model/User.php b/app/common/model/User.php index ddb7025..23e8b1c 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -58,7 +58,8 @@ class User extends Model 'phone', 'email', 'status', - 'system_code' + 'system_code', + 'corp_id' ]; // 用户所有角色 diff --git a/app/common/model/UserFeishuApp.php b/app/common/model/UserFeishuApp.php new file mode 100644 index 0000000..c64e8cd --- /dev/null +++ b/app/common/model/UserFeishuApp.php @@ -0,0 +1,69 @@ +feishuAppInfo($corpAbbr, $systemCcode); + $logic = new AuthLogic(); + $certificate = $logic->feishuAppInfo($corpAbbr, $systemCcode); + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + return ['auth_url' => $feishu->authorize($certificate)]; } /** diff --git a/app/common/service/BasicDataService.php b/app/common/service/BasicDataService.php index af63d7a..e77a4e0 100644 --- a/app/common/service/BasicDataService.php +++ b/app/common/service/BasicDataService.php @@ -9,110 +9,142 @@ use support\exception\BusinessException; class BasicDataService { + // 定义Redis键名前缀常量 + const LOGIN_TYPE_KEY = 'login_types'; + const SYSTEM_CODE_KEY = 'system_codes'; + const CORP_ABBR_KEY = 'corp_abbrs'; + /** * 检查并缓存独立的基础参数 */ public function checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr) { - // 构造缓存键 - $loginTypeKey = 'login_type_' . $loginType; - $systemCodeKey = 'system_code_' . $systemCode; - $corpAbbrKey = 'corp_abbr_' . $corpAbbr; - - // 检查缓存中是否有登录类型参数 - if (!$this->checkParamInCache($loginTypeKey, $loginType)) { - // 如果 Redis 没有缓存,执行数据库查询并存入缓存 - $this->cacheLoginType($loginTypeKey, $loginType); + if (!$loginType && !$systemCode && !$corpAbbr) { + throw new BusinessException(htmlpage()); } - // 检查缓存中是否有系统码参数 - if (!$this->checkParamInCache($systemCodeKey, $systemCode)) { - // 如果 Redis 没有缓存,执行数据库查询并存入缓存 - $this->cacheSystemCode($systemCodeKey, $systemCode); + // 检查并缓存登录类型 + if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) { + $this->cacheLoginTypes(); + if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) { + throw new BusinessException('无效的登录方式'); + } } - // 检查缓存中是否有公司标识参数 - if (!$this->checkParamInCache($corpAbbrKey, $corpAbbr)) { - // 如果 Redis 没有缓存,执行数据库查询并存入缓存 - $this->cacheCorpAbbr($corpAbbrKey, $corpAbbr); + // 检查并缓存系统码 + if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) { + $this->cacheSystemCodes(); + if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) { + throw new BusinessException('无效的系统码'); + } + } + + // 检查并缓存公司标识 + if (!$this->checkParamInCache(self::CORP_ABBR_KEY, $corpAbbr)) { + $this->cacheCorpAbbrs(); + if (!$this->checkParamInCache(self::CORP_ABBR_KEY, $corpAbbr)) { + throw new BusinessException('无效的公司标识'); + } } return true; } /** - * 检查参数是否在 Redis 缓存中 + * 检查参数是否在Redis集合中 */ private function checkParamInCache($cacheKey, $param) { - return Redis::get($cacheKey) === $param; + return Redis::sIsMember($cacheKey, $param); } /** - * 缓存登录类型 + * 缓存所有登录类型到集合 */ - private function cacheLoginType($cacheKey, $loginType) + private function cacheLoginTypes() { - // 假设我们从数据库或其他来源验证 loginType 的合法性 - if (!$this->validateLoginType($loginType)) { - throw new BusinessException('无效的登录方式'); + $cacheKey = self::LOGIN_TYPE_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; } - // 存入缓存,缓存有效期可以根据需求调整 - Redis::setex($cacheKey, 3600, $loginType); // 1小时有效期 - } - /** - * 缓存系统码 - */ - private function cacheSystemCode($cacheKey, $systemCode) - { - // 假设我们从数据库或其他来源验证 systemCode 的合法性 - if (!$this->validateSystemCode($systemCode)) { - throw new BusinessException('无效的系统码'); + $loginTypes = $this->getAllLoginTypes(); + if (empty($loginTypes)) { + throw new BusinessException('未获取到登录类型数据'); } - // 存入缓存,缓存有效期可以根据需求调整 - Redis::setex($cacheKey, 3600, $systemCode); // 1小时有效期 + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$loginTypes); + Redis::expire($cacheKey, 3600); // 1小时有效期 } /** - * 缓存公司标识 + * 缓存所有系统码到集合 */ - private function cacheCorpAbbr($cacheKey, $corpAbbr) + private function cacheSystemCodes() { - // 假设我们从数据库或其他来源验证 corpAbbr 的合法性 - if (!$this->validateCorpAbbr($corpAbbr)) { - throw new BusinessException('无效的公司标识'); + $cacheKey = self::SYSTEM_CODE_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; } - // 存入缓存,缓存有效期可以根据需求调整 - Redis::setex($cacheKey, 3600, $corpAbbr); // 1小时有效期 + + $systemCodes = $this->getAllSystemCodes(); + if (empty($systemCodes)) { + throw new BusinessException('未获取到系统码数据'); + } + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$systemCodes); + Redis::expire($cacheKey, 3600); // 1小时有效期 } /** - * 验证登录方式 + * 缓存所有公司标识到集合 */ - private function validateLoginType($loginType) + private function cacheCorpAbbrs() { - // 假设这里是验证登录方式的具体逻辑 - return in_array($loginType, ['upwd', 'feishu', 'qrcode']); + $cacheKey = self::CORP_ABBR_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; + } + + $corpAbbrs = $this->getAllCorpAbbrs(); + if (empty($corpAbbrs)) { + throw new BusinessException('未获取到公司标识数据'); + } + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$corpAbbrs); + Redis::expire($cacheKey, 3600); // 1小时有效期 } /** - * 验证系统码 + * 获取所有有效的登录类型 */ - private function validateSystemCode($systemCode) + private function getAllLoginTypes() { - // 假设这里是验证系统码的具体逻辑 - $system = Systems::where('code', $systemCode)->first(); - return $system !== null; + return ['upwd', 'feishu', 'qrcode']; // 示例数据 } /** - * 验证公司标识 + * 获取所有有效的系统码 */ - private function validateCorpAbbr($corpAbbr) + private function getAllSystemCodes() { - // 假设这里是验证公司标识的具体逻辑 - $company = Corp::where('abbr', $corpAbbr)->first(); - return $company !== null; + return Systems::pluck('code')->toArray(); } -} + + /** + * 获取所有有效的公司标识 + */ + private function getAllCorpAbbrs() + { + return Corp::pluck('abbr')->toArray(); + } +} \ No newline at end of file diff --git a/app/common/traits/ResponseTrait.php b/app/common/traits/ResponseTrait.php index f15dc92..a8adc90 100644 --- a/app/common/traits/ResponseTrait.php +++ b/app/common/traits/ResponseTrait.php @@ -43,13 +43,13 @@ trait ResponseTrait $data = [], array $header = ['Content-Type' => 'application/json'] ): Response { - return $this->result('error', $code, $httpStatus, $message, $data, $header); + return $this->result('fail', $code, $httpStatus, $message, $data, $header); } /** * 统一响应格式方法 * - * @param string $status 响应状态,例如 "success" 或 "error" + * @param string $status 响应状态,例如 "success" 或 "fail" * @param int $code 内部状态码 * @param int $httpStatus HTTP 状态码 * @param string $message 响应消息 diff --git a/app/common/utils/Feishu.php b/app/common/utils/Feishu.php index 5148db1..8428f1c 100644 --- a/app/common/utils/Feishu.php +++ b/app/common/utils/Feishu.php @@ -16,15 +16,15 @@ class Feishu protected $client = null; protected $options = []; protected $error = ''; - protected $gs = 'hesu'; + protected $abbr = 'hesu'; /** * 初始化 */ - public function __construct(string $appId = '', string $appSecret = '', string $gs = 'hesu') + public function __construct(string $appId = '', string $appSecret = '', string $abbr = 'hesu') { - if ($gs === 'mbaf') { + if ($abbr === 'mbaf') { $this->baseUri = 'https://open.larksuite.com/open-apis/'; } else { $this->baseUri = 'https://open.feishu.cn/open-apis/'; @@ -32,7 +32,7 @@ class Feishu $this->appId = $appId; $this->appSecret = $appSecret; - $this->gs = $gs; + $this->abbr = $abbr; $this->client = new Client([ 'base_uri' => $this->baseUri @@ -94,8 +94,8 @@ class Feishu public function authorize($certificate, $url = 'authen/v1/authorize') { $arr = [ - 'app_id' => $certificate['appId'], - 'redirect_uri' => $certificate['rUrl'], + 'app_id' => $certificate['app_id'], + 'redirect_uri' => $certificate['r_url'], 'scope' => $certificate['scope'], 'state' => $certificate['state'] ]; @@ -108,7 +108,7 @@ class Feishu */ public function tenantAccessToken() { - $tenantAccessToken = Cache::get($this->gs.'-tenantAccessToken'); + $tenantAccessToken = Cache::get($this->abbr.'-tenantAccessToken'); if (!$tenantAccessToken) { $this->options["json"] = [ "app_id" => $this->appId, @@ -117,7 +117,7 @@ class Feishu $rst = $this->requestResult('auth/v3/tenant_access_token/internal', 'POST'); if ($rst) { $tenantAccessToken = $rst['tenant_access_token']; - Cache::set($this->gs.'-tenantAccessToken', $tenantAccessToken, $rst['expire']); + Cache::set($this->abbr.'-tenantAccessToken', $tenantAccessToken, $rst['expire']); } else { throw new BusinessException('tenant_access_token 获取失败'); } diff --git a/app/common/validate/UserValidate.php b/app/common/validate/UserValidate.php index f161ba3..7db260d 100644 --- a/app/common/validate/UserValidate.php +++ b/app/common/validate/UserValidate.php @@ -20,6 +20,7 @@ class UserValidate extends Validate 'login_type' => 'require|in:upwd,feishu,qrcode', 'system_code' => 'require|checkSystemCode', 'corp_abbr' => 'require|checkCorpAbbr', + 'feishu_code' => 'require|string' ]; protected $message = [ @@ -44,13 +45,17 @@ class UserValidate extends Validate 'system_code.require' => '系统码不能为空', 'system_code.checkSystemCode' => '系统码不存在或不合法', 'corp_abbr.require' => '公司标识不能为空', - 'corp_abbr.checkCorpAbbr' => '公司标识不存在或不合法' + 'corp_abbr.checkCorpAbbr' => '公司标识不存在或不合法', + 'feishu_code.require' => '飞书code不能为空', + 'feishu_code.string' => '飞书code必须是字符串' ]; protected $scene = [ - 'upwd_register' => ['name', 'account', 'password', 'confirm_password', 'login_type', 'system_code'], - 'upwd_login' => ['account', 'password', 'login_type', 'system_code'], - 'feishu_auth' => ['corp_abbr', 'system_code', 'login_type'] + 'upwd_register' => ['name', 'account', 'password', 'confirm_password'], + 'feishu_register' => ['feishu_code'], + 'upwd_login' => ['account' => 'require|checkAccount', 'password'], + 'feishu_auth' => ['corp_abbr', 'system_code', 'login_type'], + 'feishu_login' => ['feishu_code'] ]; /** diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 2553493..2ea3432 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -6,79 +6,15 @@ use support\Request; class IndexController { + public function index(Request $request) { - return ' - - - - - Code - - - -
- -
-

🌞 hi~

-

    愿你代码如诗,逻辑清晰,Bug 自动远离!

-

        记得适当休息,喝杯好茶或咖啡,为灵感续航 ☕

-

            加油,永远相信美好的事情即将发生。 ✨

-
- - - - -'; + return htmlpage(); } public function view(Request $request) { - return view('index/view', ['name' => 'webman']); + return view('index/qrcode', ['name' => 'webman']); } public function json(Request $request) @@ -86,4 +22,4 @@ class IndexController return json(['code' => 0, 'msg' => 'ok']); } -} +} \ No newline at end of file diff --git a/app/functions.php b/app/functions.php index 5c9c58d..d20b843 100644 --- a/app/functions.php +++ b/app/functions.php @@ -2,3 +2,41 @@ /** * Here is your custom functions. */ + +/** + * 标准化账号参数 + * + * @param string $account 用户输入的账号(可能是手机号或邮箱) + * @return array 返回 ['phone' => $account] 或 ['email' => $account] + */ +function normalizeAccount(string $account): array +{ + // 使用FILTER_VALIDATE_EMAIL验证邮箱格式 + if (filter_var($account, FILTER_VALIDATE_EMAIL)) { + return ['email' => $account]; + } + + // 清理手机号中的非数字字符 + $cleanPhone = preg_replace('/[^0-9]/', '', $account); + + // 验证手机号格式(简单版:11位数字) + if (preg_match('/^1[3-9]\d{9}$/', $cleanPhone)) { + return ['phone' => $cleanPhone]; + } + + // 如果既不是邮箱也不是手机号,根据业务需求处理 + // 这里默认按手机号处理(可根据实际需求调整) + return ['phone' => $cleanPhone]; +} + +/** + * 公共html页面 + */ +function htmlpage() +{ + static $html; + if (!$html) { + $html = file_get_contents(public_path('mzl.html')); + } + return $html; +} \ No newline at end of file diff --git a/app/middleware/AuthCheck.php b/app/middleware/AuthCheck.php new file mode 100644 index 0000000..3e6eb8a --- /dev/null +++ b/app/middleware/AuthCheck.php @@ -0,0 +1,46 @@ +controller); + $noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? []; + + if (in_array($request->action, $noNeedLogin)) { + // 白名单 + return $handler($request); + } else { + try { + $authHeader = $request->header('Authorization'); + if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { + $body = [ + 'status' => 'fail', + 'code' => 401, + 'message' => '未授权', + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + $token = $matches[1]; + $request->user = (new Jwt())->verifyToken($token); + } catch (\Throwable $e) { + $body = [ + 'status' => 'fail', + 'code' => 401, + 'message' => $e->getMessage(), + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + return $handler($request); + } + } +} \ No newline at end of file diff --git a/app/middleware/BasicData.php b/app/middleware/BasicData.php new file mode 100644 index 0000000..472456e --- /dev/null +++ b/app/middleware/BasicData.php @@ -0,0 +1,42 @@ +controller); + $noNeedHeader = $controller->getDefaultProperties()['noNeedHeader'] ?? []; + + if (in_array($request->action, $noNeedHeader) || in_array($request->path(), config('whitelist.basi_cdata'))) { + // 白名单 + return $handler($request); + } else { + try { + $corpAbbr = $request->header('Fdesk-Corp-Abbr'); + $systemCode = $request->header('Fdesk-System-Code'); + $loginType = $request->header('Fdesk-Login-Type'); + $basicDataService = new BasicDataService(); + if ($basicDataService->checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)) { + $request->login_type = $loginType; + $request->system_code = $systemCode; + $request->corp_abbr = $corpAbbr; + $request->basic_data = [ + 'login_type' => $loginType, + 'system_code' => $systemCode, + 'corp_abbr' => $corpAbbr + ]; + } + return $handler($request); + } catch (\Throwable $th) { + throw $th; + } + } + } +} \ No newline at end of file diff --git a/app/middleware/BasicDataMiddleware.php b/app/middleware/BasicDataMiddleware.php deleted file mode 100644 index 6929ef8..0000000 --- a/app/middleware/BasicDataMiddleware.php +++ /dev/null @@ -1,30 +0,0 @@ -header('login_type'); - $systemCode = $request->header('system_code'); - $corpAbbr = $request->header('corp_abbr'); - $basicDataService = new BasicDataService(); - if ($basicDataService->checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)) { - $request->login_type = $loginType; - $request->system_code = $systemCode; - $request->corp_abbr = $corpAbbr; - } - return $next($request); - } catch (\Throwable $th) { - throw $th; - } - } - -} diff --git a/app/view/index/qrcode.html b/app/view/index/qrcode.html new file mode 100644 index 0000000..26d1918 --- /dev/null +++ b/app/view/index/qrcode.html @@ -0,0 +1,46 @@ + + + + + 飞书扫码登录示例 + + + + + +
+ + + + diff --git a/config/middleware.php b/config/middleware.php index 5512630..cf0335a 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -12,8 +12,10 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ + return [ '' => [ - \app\middleware\BasicDataMiddleware::class + \app\middleware\AccessControl::class, + \app\middleware\BasicData::class ] ]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index aaf9098..3b642cf 100644 --- a/config/route.php +++ b/config/route.php @@ -28,8 +28,13 @@ use Webman\Route; Route::group('/common', function () { Route::group('/auth', function () { - Route::post('/login', [app\common\controller\AuthController::class, 'login']); - Route::post('/register', [app\common\controller\AuthController::class, 'register']); - Route::post('/feishu/scope', [app\common\controller\AuthController::class, 'feishuScope']); + Route::any('/login', [app\common\controller\AuthController::class, 'login']); + Route::any('/register', [app\common\controller\AuthController::class, 'register']); + Route::any('/feishu/scope', [app\common\controller\AuthController::class, 'feishuScope']); + + + + // 调试 + Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); }); }); \ No newline at end of file diff --git a/config/whitelist.php b/config/whitelist.php new file mode 100644 index 0000000..8071847 --- /dev/null +++ b/config/whitelist.php @@ -0,0 +1,9 @@ + [ + '/', + '/index/index', + // '/index/view' + ] +]; \ No newline at end of file diff --git a/public/mzl.html b/public/mzl.html new file mode 100644 index 0000000..9eb4e4b --- /dev/null +++ b/public/mzl.html @@ -0,0 +1,64 @@ + + + + + Code + + + +
+ +
+

🌞 hi~

+

    愿你代码如诗,逻辑清晰,Bug 自动远离!

+

        记得适当休息,喝杯好茶或咖啡,为灵感续航 ☕

+

            加油,永远相信美好的事情即将发生。 ✨

+
+ + + + \ No newline at end of file From 37a844c01d9e76aff475ec32d19e13d576c137d0 Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 9 Jul 2025 19:09:16 +0800 Subject: [PATCH 024/120] =?UTF-8?q?=E5=90=88=E5=B9=B6devlop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UploadController.php | 122 +++++ app/common/service/OssService.php | 59 ++ app/functions.php | 22 + app/middleware/AccessControl.php | 1 + app/queue/translation/config/trans.php | 14 + app/queue/translation/jobs/TranslationJob.php | 64 +++ app/queue/translation/queue/Translation.php | 50 ++ .../service/TranslationService.php | 41 ++ .../translation/service/TranslatorClient.php | 38 ++ app/queue/utils/Translat.php | 21 + app/queue/utils/XunfeiFanyi.php | 134 +++++ .../controller/AttachmentController.php | 11 + app/ticket/controller/BaseController.php | 51 ++ app/ticket/controller/CategorieController.php | 64 +++ .../controller/ConversationController.php | 46 ++ app/ticket/controller/TicketController.php | 69 +++ app/ticket/logic/AttachmentLogic.php | 82 +++ app/ticket/logic/CategorieLogic.php | 66 +++ app/ticket/logic/ConversationLogic.php | 46 ++ app/ticket/logic/TicketLogic.php | 73 +++ app/ticket/model/Attachment.php | 66 +++ app/ticket/model/Categorie.php | 54 ++ app/ticket/model/CategorieTranslation.php | 35 ++ app/ticket/model/Conversation.php | 33 ++ app/ticket/model/Ticket.php | 77 +++ app/ticket/service/CategorieService.php | 97 ++++ app/ticket/service/ConversationService.php | 49 ++ app/ticket/service/TicketService.php | 122 +++++ composer.json | 6 +- composer.lock | 506 +++++++++++++++++- config/constants/Constants.php | 37 ++ config/plugin/shopwwi/filesystem/app.php | 116 ++++ config/plugin/webman/redis-queue/app.php | 4 + config/plugin/webman/redis-queue/command.php | 7 + config/plugin/webman/redis-queue/log.php | 32 ++ config/plugin/webman/redis-queue/process.php | 12 + config/plugin/webman/redis-queue/redis.php | 22 + config/route.php | 29 +- resource/translations/ar/messages.php | 5 + resource/translations/en/messages.php | 5 + resource/translations/ru/messages.php | 5 + resource/translations/zh/messages.php | 5 + 42 files changed, 2394 insertions(+), 4 deletions(-) create mode 100644 app/common/controller/UploadController.php create mode 100644 app/common/service/OssService.php create mode 100644 app/queue/translation/config/trans.php create mode 100644 app/queue/translation/jobs/TranslationJob.php create mode 100644 app/queue/translation/queue/Translation.php create mode 100644 app/queue/translation/service/TranslationService.php create mode 100644 app/queue/translation/service/TranslatorClient.php create mode 100644 app/queue/utils/Translat.php create mode 100644 app/queue/utils/XunfeiFanyi.php create mode 100644 app/ticket/controller/AttachmentController.php create mode 100644 app/ticket/controller/BaseController.php create mode 100644 app/ticket/controller/CategorieController.php create mode 100644 app/ticket/controller/ConversationController.php create mode 100644 app/ticket/controller/TicketController.php create mode 100644 app/ticket/logic/AttachmentLogic.php create mode 100644 app/ticket/logic/CategorieLogic.php create mode 100644 app/ticket/logic/ConversationLogic.php create mode 100644 app/ticket/logic/TicketLogic.php create mode 100644 app/ticket/model/Attachment.php create mode 100644 app/ticket/model/Categorie.php create mode 100644 app/ticket/model/CategorieTranslation.php create mode 100644 app/ticket/model/Conversation.php create mode 100644 app/ticket/model/Ticket.php create mode 100644 app/ticket/service/CategorieService.php create mode 100644 app/ticket/service/ConversationService.php create mode 100644 app/ticket/service/TicketService.php create mode 100644 config/constants/Constants.php create mode 100644 config/plugin/shopwwi/filesystem/app.php create mode 100644 config/plugin/webman/redis-queue/app.php create mode 100644 config/plugin/webman/redis-queue/command.php create mode 100644 config/plugin/webman/redis-queue/log.php create mode 100644 config/plugin/webman/redis-queue/process.php create mode 100644 config/plugin/webman/redis-queue/redis.php create mode 100644 resource/translations/ar/messages.php create mode 100644 resource/translations/en/messages.php create mode 100644 resource/translations/ru/messages.php create mode 100644 resource/translations/zh/messages.php diff --git a/app/common/controller/UploadController.php b/app/common/controller/UploadController.php new file mode 100644 index 0000000..9d8959d --- /dev/null +++ b/app/common/controller/UploadController.php @@ -0,0 +1,122 @@ +input('scene', 'image'); + $file = $request->file('file'); + if ($file && $file->isValid()) { + $pathUrl = '/uploads/'.$scene.'/'.date('Ymd').'/'.md5($file->getUploadName() . time()).'.'.$file->getUploadExtension(); + $fullPathUrl = public_path() . $pathUrl; + $file->move($fullPathUrl); + $data = [ + 'domain' => getenv('MAIN_DOMAIN'), + 'file_url' => $pathUrl, + 'full_url' => getenv('MAIN_DOMAIN') . $pathUrl, + 'scene' => $scene, + ]; + return $this->success($data, 'upload success'); + } + return $this->error('file not found'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + // 上传单文件附件 + public function uploadFile(Request $request) + { + try { + $file = $request->file('file'); + + if (!$file || !$file->isValid()) { + return $this->error('上传失败'); + } + $originalName = $file->getUploadName(); + + $result = Storage::adapter('oss') + ->path('vehicle_album/'.date('Ymd')) + ->size(1024*1024*5) + ->upload($file); + + $ossservice = new OssService(); + + $data = [ + 'original_name' => $originalName, + 'file_name' => $result->file_name, + 'file_url' => $ossservice->generatePrivateUrl($result->file_name) + ]; + + return $this->success($data); + }catch (\Throwable $e){ + return $this->error($e->getMessage()); + } + } + + // 上传单文件图片 + public function uploadImage(Request $request) + { + try { + $file = $request->file('image'); + + if (!$file || !$file->isValid()) { + return $this->error('上传失败'); + } + + $result = Storage::adapter('oss') + ->path('vehicle_album/'.date('Ymd')) + ->size(1024*1024*5) + ->extYes(['image/jpeg','image/png']) + ->extNo(['image/gif']) + ->upload($file); + + $ossservice = new OssService(); + + $data = [ + 'href' => $result->file_name, + 'alt' => '', + 'url' => $ossservice->generatePrivateUrl($result->file_name) + ]; + + return json(['errno' => 0, 'data' => $data]); +// return $this->success($data); + } catch (\Throwable $e) { + return json(['errno' => 1, 'message' => $e->getMessage()]); +// return $this->error($e->getMessage()); + } + } + + public function proxy(Request $request) + { + $filePath = $request->get('file'); // 原OSS文件路径 + try { + // 生成签名URL + $signedUrl = (new \app\common\service\OssService())->generatePrivateUrl($filePath); + + // 获取图片数据并返回 + $imageData = file_get_contents($signedUrl); + + // 根据文件类型设置Content-Type + $mime = mime_content_type('data://application/octet-stream;base64,'.base64_encode($imageData)); + + return response($imageData, 200, [ + 'Content-Type' => $mime, + 'Cache-Control' => 'public, max-age=600' // 客户端缓存10分钟 + ]); + } catch (\Exception $e) { + return response('Image not found '.$e->getMessage(), 404); + } + } +} \ No newline at end of file diff --git a/app/common/service/OssService.php b/app/common/service/OssService.php new file mode 100644 index 0000000..4d75b1f --- /dev/null +++ b/app/common/service/OssService.php @@ -0,0 +1,59 @@ +accessKeyId = getenv('OSS_ACCESSID'); + $this->accessKeySecret = getenv('OSS_ACCESSSECRET'); + $this->bucket = getenv('OSS_BUCKET'); + $this->endpoint = getenv('OSS_ENDPOINT'); + $this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint); + } + + /** + * 上传图片到 OSS + * @param string $filePath 本地文件路径 + * @param string $objectKey OSS上存储的对象名称 + * @return string 图片的 URL + */ + public function uploadImage($filePath, $objectKey) + { + try { + // 上传文件到 OSS + $this->ossClient->uploadFile($this->bucket, $objectKey, $filePath); + // 构造图片的私有 URL + $url = $this->generatePrivateUrl($objectKey); + return $url; + } catch (OssException $e) { + // 错误处理 + throw new \RuntimeException('OSS Error: ' . $e->getMessage()); + } catch (\Throwable $th) { + // 错误处理 + throw new BusinessException('Error: ' . $th->getMessage()); + } + } + + /** + * 生成图片的私有访问 URL + * @param string $objectKey + * @return string + */ + public function generatePrivateUrl($objectKey, $expireTime = 3600) + { + // 生成 OSS 私有 URL(有效期为 3600 秒) + return $this->ossClient->signUrl($this->bucket, $objectKey, $expireTime); + } +} \ No newline at end of file diff --git a/app/functions.php b/app/functions.php index d20b843..14d5e4a 100644 --- a/app/functions.php +++ b/app/functions.php @@ -3,6 +3,8 @@ * Here is your custom functions. */ +use support\exception\BusinessException; + /** * 标准化账号参数 * @@ -39,4 +41,24 @@ function htmlpage() $html = file_get_contents(public_path('mzl.html')); } return $html; +} + +/** + * 获取配置 + */ +function getConfig($filePath = null) +{ + // 构建文件路径 + $config_file = app_path($filePath); + // 检查文件是否存在 + if (!file_exists($config_file)) { + return []; + } + // 包含文件并获取返回的数组 + $config = include $config_file; + // 验证是否为数组 + if (!is_array($config)) { + return []; + } + return $config; } \ No newline at end of file diff --git a/app/middleware/AccessControl.php b/app/middleware/AccessControl.php index 6fa3822..2a93d79 100644 --- a/app/middleware/AccessControl.php +++ b/app/middleware/AccessControl.php @@ -1,4 +1,5 @@ [ + 'ticket_categorie' => [ + 'translation_model' => \app\ticket\model\CategorieTranslation::class, + 'foreign_key' => 'categorie_id', + ], + ], + + // 默认支持的目标语种列表 + 'locales' => ['zh','en','ru','ar'], +]; diff --git a/app/queue/translation/jobs/TranslationJob.php b/app/queue/translation/jobs/TranslationJob.php new file mode 100644 index 0000000..eb5e704 --- /dev/null +++ b/app/queue/translation/jobs/TranslationJob.php @@ -0,0 +1,64 @@ +config = getConfig('/queue/translation/config/trans.php'); + $this->payload = $payload; + } + + public function handle(): void + { + $cfgAll = $this->config['entities']; + $pt = $this->payload; + $type = $pt['entityType']; + $id = $pt['entityId']; + $locale = $pt['locale']; + $sourceLocale = $pt['sourceLocale']; + $sourceFields = $pt['sourceFields']; + $clientFields = $pt['clientFields']; + + if (! isset($cfgAll[$type])) { + Log::warning("TranslationJob: 未配置实体 {$type}"); + return; + } + + $cfg = $cfgAll[$type]; + $modelClass = $cfg['translation_model']; + $foreignKey = $cfg['foreign_key']; + + // 决定采用前端提供、源语种还是第三方翻译 + if (! empty($clientFields)) { + $fields = $clientFields; + } elseif ($locale === $sourceLocale) { + $fields = $sourceFields; + } else { + $translator = new TranslatorClient(); + $fields = []; + foreach ($sourceFields as $key => $text) { + $fields[$key] = $translator->translate($text, $sourceLocale, $locale); + } + } + + // 写库 + try { + $modelClass::updateOrCreate( + [ + $foreignKey => $id, + 'locale' => $locale, + ], + array_merge($fields, ['locale' => $locale]) + ); + } catch (\Throwable $e) { + Log::error("TranslationJob 保存失败: {$type}#{$id} locale={$locale}: ".$e->getMessage()); + } + } +} diff --git a/app/queue/translation/queue/Translation.php b/app/queue/translation/queue/Translation.php new file mode 100644 index 0000000..0b5841a --- /dev/null +++ b/app/queue/translation/queue/Translation.php @@ -0,0 +1,50 @@ + 'tom@gmail.com', 'content' => 'hello'] + + $job = new TranslationJob($data); + $job->handle(); + } catch (\Throwable $e) { + Log::error("TranslationService 异步任务异常: ".$e->getMessage()); + } + } + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file diff --git a/app/queue/translation/service/TranslationService.php b/app/queue/translation/service/TranslationService.php new file mode 100644 index 0000000..3578d89 --- /dev/null +++ b/app/queue/translation/service/TranslationService.php @@ -0,0 +1,41 @@ +config = getConfig('/queue/translation/config/trans.php'); + } + + /** + * 异步排队所有语种的翻译任务 + * + * @param string $entityType 实体名,如 'vehicle' + * @param int $entityId 主表 ID + * @param string $sourceLocale 源语种,如 'zh' + * @param array $sourceFields 源语种字段 ['name'=>…,'condition_desc'=>…] + * @param array $clientFields 前端已传翻译 ['en'=>[...], …] + */ + public function queueTranslations(string $entityType, int $entityId, string $sourceLocale, array $sourceFields, array $clientFields = []): void + { + foreach ($this->config['locales'] as $locale) { + $payload = [ + 'entityType' => $entityType, + 'entityId' => $entityId, + 'locale' => $locale, + 'sourceLocale' => $sourceLocale, + 'sourceFields' => $sourceFields, + 'clientFields' => $clientFields[$locale] ?? [], + ]; + Redis::send('transTask', $payload); + } + } +} \ No newline at end of file diff --git a/app/queue/translation/service/TranslatorClient.php b/app/queue/translation/service/TranslatorClient.php new file mode 100644 index 0000000..3abc1df --- /dev/null +++ b/app/queue/translation/service/TranslatorClient.php @@ -0,0 +1,38 @@ +endpoint = env('TRANSLATION_API_URL'); + // $this->apiKey = env('TRANSLATION_API_KEY'); + // } + + /** + * 翻译文本 + * @throws \RuntimeException + */ + public function translate(string $text, string $from = 'cn', string $to = 'ru'): string + { + try { + $xunfei = new XunfeiFanyi(); + $rst_dst = $xunfei->fanyi($text, $from, $to); + if ($rst_dst) { + return $rst_dst["trans_result"]["dst"]; + } else { + return ''; + } + } catch (\Throwable $e) { + Log::error("TranslatorClient 错误: ".$e->getMessage()); + throw new \RuntimeException('翻译服务调用失败'); + } + } +} diff --git a/app/queue/utils/Translat.php b/app/queue/utils/Translat.php new file mode 100644 index 0000000..3000756 --- /dev/null +++ b/app/queue/utils/Translat.php @@ -0,0 +1,21 @@ + $header,'body' => $response); + return $data; + } + + + public function xfyun($text = "中华人民共和国于1949年成立", $from = "cn", $to = "en") + { + //在控制台-我的应用-机器翻译获取 + $app_id = "3d46198c"; + //在控制台-我的应用-机器翻译获取 + $api_sec = "ZWY2MmUyNjJjYmFkOTFlYTVmYWVhMGYy"; + //在控制台-我的应用-机器翻译获取 + $api_key = "b6d805f240176ecaea85130cb5f2bca1"; + // 机器翻译接口地址 + $url = "https://ntrans.xfyun.cn/v2/ots"; + //body组装 + // $text = "中华人民共和国于1949年成立";//待翻译文本 + $body = json_encode($this->getBody($app_id, $from, $to, $text)); + + // 组装http请求头 + $date =gmdate('D, d M Y H:i:s') . ' GMT'; + + $digestBase64 = "SHA-256=".base64_encode(hash("sha256", $body, true)); + $builder = sprintf("host: %s +date: %s +POST /v2/ots HTTP/1.1 +digest: %s", "ntrans.xfyun.cn", $date, $digestBase64); + // echo($builder); + $sha = base64_encode(hash_hmac("sha256", $builder, $api_sec, true)); + + $authorization = sprintf("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", $api_key,"hmac-sha256", + "host date request-line digest", $sha); + + $header = [ + "Authorization: ".$authorization, + 'Content-Type: application/json', + 'Accept: application/json,version=1.0', + 'Host: ntrans.xfyun.cn', + 'Date: ' .$date, + 'Digest: '.$digestBase64 + ]; + $response = $this->tocurl($url, $header, $body); + + // var_dump($response['body']); + return $response; + } + + + public function getBody($app_id, $from, $to, $text) + { + $common_param = [ + 'app_id' => $app_id + ]; + + $business = [ + 'from' => $from, + 'to' => $to, + ]; + + $data = [ + "text" => base64_encode($text) + ]; + + return $body = [ + 'common' => $common_param, + 'business' => $business, + 'data' => $data + ]; + } + + // 翻译 + public function fanyi($text = "", $from = "", $to = "") + { + try { + $res = $this->xfyun($text, $from, $to); + $data = json_decode($res["body"], true); + if ($data["code"] === 0) { + return $data["data"]["result"]; + } else { + $this->error = $data["message"]; + return false; + } + } catch (\Throwable $th) { + $this->error = $th->getMessage(); + return false; + } + } + + // 异常 + public function error() + { + return $this->error; + } + +} \ No newline at end of file diff --git a/app/ticket/controller/AttachmentController.php b/app/ticket/controller/AttachmentController.php new file mode 100644 index 0000000..94e1724 --- /dev/null +++ b/app/ticket/controller/AttachmentController.php @@ -0,0 +1,11 @@ +scene($scene); + } + // 执行数据验证 + if (!$validator->check($data)) { + // 验证失败,抛出业务异常,异常信息为验证器返回的错误信息 + throw new BusinessException($validator->getError()); + } + // 验证成功,返回原始数据 + return $data; + } + + public function page($result){ + $current = $result->currentPage(); + $total = $result->total(); + $size = $result->perPage(); + $result = $result->toArray(); + $result['current'] = $current; + $result['total'] = $total; + $result['size'] = $size; + return $result; + } +} \ No newline at end of file diff --git a/app/ticket/controller/CategorieController.php b/app/ticket/controller/CategorieController.php new file mode 100644 index 0000000..84e8bd7 --- /dev/null +++ b/app/ticket/controller/CategorieController.php @@ -0,0 +1,64 @@ +list($request->all()); + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function getAll(Request $request) + { + try { + $result = (new CategorieService())->getAll($request->all()); + return $this->success($result); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function add(Request $request) + { + try { + return (new CategorieService())->add($request->all()) + ? $this->success() + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } + + public function edit(Request $request) + { + try { + $result = (new CategorieService())->edit($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } + + public function delete(Request $request) + { + try { + $result = (new CategorieService())->delete($request->input('id')); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } +} \ No newline at end of file diff --git a/app/ticket/controller/ConversationController.php b/app/ticket/controller/ConversationController.php new file mode 100644 index 0000000..0e1b7ec --- /dev/null +++ b/app/ticket/controller/ConversationController.php @@ -0,0 +1,46 @@ +input('ticket_id'); + if(empty($ticket_id)){ + return $this->error('ticket_id不能为空'); + } + $result = (new ConversationService())->list($request->all()); + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage().$e->getLine().$e->getFile()); + } + } + public function add(Request $request) + { + try { + $request->setPost('user_id', 1); + return (new ConversationService())->add($request->all()) + ? $this->success() + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } + public function edit(Request $request) + { + try { + $request->setPost('user_id', 1); + $result = (new ConversationService())->edit($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage().' '.$th->getLine().' '.$th->getFile()); + } + } +} \ No newline at end of file diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php new file mode 100644 index 0000000..782dc74 --- /dev/null +++ b/app/ticket/controller/TicketController.php @@ -0,0 +1,69 @@ +list($request->all()); + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + public function add(Request $request) + { + try { + $request->setPost('user_id', 1); + + return (new TicketService())->add($request->all()) + ? $this->success() + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage()); + } + } + + public function edit(Request $request) + { + try { + $request->setPost('user_id', 1); + $result = (new TicketService())->edit($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage()); + } + } + public function status(Request $request) + { + try { + $request->setPost('user_id', 1); + $result = (new TicketService())->status($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage()); + } + } + public function getStatus(Request $request) + { + try { + $result = (new TicketService())->getStatus($request->all()); + return $result + ? $this->success($result) + : $this->error(); + } catch (\Throwable $th) { + return $this->error($th->getMessage()); + } + } + +} diff --git a/app/ticket/logic/AttachmentLogic.php b/app/ticket/logic/AttachmentLogic.php new file mode 100644 index 0000000..b498f7e --- /dev/null +++ b/app/ticket/logic/AttachmentLogic.php @@ -0,0 +1,82 @@ +$v){ + $v['ticket_id'] = $ticket_id; + $v['user_id'] = $user_id; + $res = Attachment::create($v); + } + return $res; + } catch (\Throwable $e) { + Log::error('工单创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('工单创建失败,请稍后重试', 500); + } + } + + public function edit(array $data,int $ticket_id,int $user_id) + { + try { + $attachmentsId = []; + foreach ($data as $k=>$v){ + $v['ticket_id'] = $ticket_id; + $v['user_id'] = $user_id; + if(!empty($v['id'])){ //修改附件 + $attachmentId = $v['id']; + $attachmentsId[] = $attachmentId; +// $v_res = Attachment::update($attachmentId, $v); + $v_res = Attachment::updateOrCreate(['id' => $v['id']],$v); + if (!$v_res) { + throw new BusinessException('附件保存失败'); + } + } else { //新增附件 + unset($v['id']); + $v_res = Attachment::create($v); + if (!$v_res) { + throw new BusinessException('附件创建失败'); + } + $vId = $v_res->id; + $attachmentsId[] = $vId; + } + } + + //删除本次没有出现的附件 + $res = Attachment::whereNotIn('id', $attachmentsId)->where('ticket_id',$ticket_id)->delete(); + return $res; + } catch (\Throwable $e) { + Log::error('工单编辑(附件)失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('工单编辑失败,请稍后重试', 500); + } + } + + public static function generateSimpleTicketNo(): string + { + $date = date('Ymd'); // 日期 + $prefix = 'GD'; // 前缀 + $random = strtoupper(substr(md5(uniqid()), 0, 5)); // 5位随机字母数字 + + return $prefix . $date . '-' . $random; // 如 GD20240702-AB3X9Z + } +} \ No newline at end of file diff --git a/app/ticket/logic/CategorieLogic.php b/app/ticket/logic/CategorieLogic.php new file mode 100644 index 0000000..c11bd64 --- /dev/null +++ b/app/ticket/logic/CategorieLogic.php @@ -0,0 +1,66 @@ +where($where)->paginate($size, ['*'], 'page', $current); + } + + public function getAll($params) + { + $where = []; + $where[] = ['status', '=', 1]; + return Categorie::with('translation')->where($where)->get(); + } + + public function add(array $data) + { + try { + return Categorie::create($data); + } catch (\Throwable $e) { + Log::error('分类创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('分类创建失败,请稍后重试', 500); + } + } + + public function edit(array $data) + { + try { + $categorie = Categorie::updateOrCreate(['id' => $data['id']], $data); + return $categorie; + } catch (\Throwable $e) { + Log::error('分类编辑失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('分类编辑失败,请稍后重试', 500); + } + } + + public function delete(int $id) + { + try { + $categorie = Categorie::where(['id'=>$id])->delete(); + return $categorie; + } catch (\Throwable $e) { + Log::error('分类删除失败', [ + 'id' => $id, + 'error' => $e->getMessage() + ]); + throw new BusinessException('分类删除失败,请稍后重试', 500); + } + } +} \ No newline at end of file diff --git a/app/ticket/logic/ConversationLogic.php b/app/ticket/logic/ConversationLogic.php new file mode 100644 index 0000000..d47a709 --- /dev/null +++ b/app/ticket/logic/ConversationLogic.php @@ -0,0 +1,46 @@ +paginate($size, ['*'], 'page', $current); + } + public function add(array $data) + { + try { + return Conversation::create($data); + } catch (\Throwable $e) { + Log::error('会话创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('会话创建失败,请稍后重试', 500); + } + } + public function edit(array $data) + { + try { + $conversation = Conversation::updateOrCreate(['id' => $data['id']], $data); + return $conversation; + } catch (\Throwable $e) { + Log::error('会话编辑失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('会话编辑失败,请稍后重试', 500); + } + } +} \ No newline at end of file diff --git a/app/ticket/logic/TicketLogic.php b/app/ticket/logic/TicketLogic.php new file mode 100644 index 0000000..e22cdb7 --- /dev/null +++ b/app/ticket/logic/TicketLogic.php @@ -0,0 +1,73 @@ +with('categorie')->with('categorie.translation')->where($where)->orderBy('id','desc')->paginate($size, ['*'], 'page', $current); + } + public function add(array $data) + { + $data['status'] = 1; + $data['code'] = self::generateSimpleTicketNo(); + try { + return Ticket::create($data); + } catch (\Throwable $e) { + Log::error('工单创建失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('工单创建失败,请稍后重试', 500); + } + } + public function edit(array $data) + { + try { + unset($data['code']); + $ticket = Ticket::updateOrCreate(['id' => $data['id']],$data); + return $ticket; + } catch (\Throwable $e) { + Log::error('工单编辑失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('工单编辑失败,请稍后重试', 500); + } + } + + public function find(int $id) + { + $ticket = Ticket::find($id); + return $ticket; + } + + public static function generateSimpleTicketNo(): string + { + $date = date('Ymd'); // 日期 + $prefix = 'GD'; // 前缀 + $random = strtoupper(substr(md5(uniqid()), 0, 5)); // 5位随机字母数字 + + return $prefix . $date . '-' . $random; // 如 GD20240702-AB3X9Z + } +} \ No newline at end of file diff --git a/app/ticket/model/Attachment.php b/app/ticket/model/Attachment.php new file mode 100644 index 0000000..f5fc843 --- /dev/null +++ b/app/ticket/model/Attachment.php @@ -0,0 +1,66 @@ +belongsTo(Ticket::class, 'ticket_id', 'id'); + } +} diff --git a/app/ticket/model/Categorie.php b/app/ticket/model/Categorie.php new file mode 100644 index 0000000..610c693 --- /dev/null +++ b/app/ticket/model/Categorie.php @@ -0,0 +1,54 @@ + 'datetime:Y-m-d H:i:s', + 'created_at' => 'datetime:Y-m-d H:i:s', + ]; + protected $fillable = [ + 'name', + 'description', + ]; + + /** + * 单条翻译 + */ + public function translation() + { + return $this->hasOne(CategorieTranslation::class, 'categorie_id')->where('locale', locale()); + } + + /** + * 多条翻译 + */ + public function translations() + { + return $this->hasMany(CategorieTranslation::class, 'categorie_id'); + } + + public function ticket(){ + return $this->hasOne(Ticket::class, 'categorie_id', 'id'); + } +} \ No newline at end of file diff --git a/app/ticket/model/CategorieTranslation.php b/app/ticket/model/CategorieTranslation.php new file mode 100644 index 0000000..6660dea --- /dev/null +++ b/app/ticket/model/CategorieTranslation.php @@ -0,0 +1,35 @@ + 'datetime:Y-m-d H:i:s', + 'created_at' => 'datetime:Y-m-d H:i:s', + ]; + protected $fillable = [ + 'categorie_id', + 'locale', + 'name', + 'description', + ]; +} \ No newline at end of file diff --git a/app/ticket/model/Conversation.php b/app/ticket/model/Conversation.php new file mode 100644 index 0000000..6d63cb5 --- /dev/null +++ b/app/ticket/model/Conversation.php @@ -0,0 +1,33 @@ + 'datetime:Y-m-d H:i:s', + 'created_at' => 'datetime:Y-m-d H:i:s', + ]; + protected $fillable = [ + 'ticket_id', + 'user_id', + 'content', + ]; +} \ No newline at end of file diff --git a/app/ticket/model/Ticket.php b/app/ticket/model/Ticket.php new file mode 100644 index 0000000..cf71b2a --- /dev/null +++ b/app/ticket/model/Ticket.php @@ -0,0 +1,77 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 + ]; + + protected $fillable = [ + 'code', + 'categorie_id', + 'content', + 'sort', + 'status', + 'user_id' + ]; + + public function attachments(){ + return $this->hasMany(Attachment::class, 'ticket_id', 'id'); + } + + public function categorie(){ + return $this->belongsTo(Categorie::class, 'categorie_id', 'id'); + } +} diff --git a/app/ticket/service/CategorieService.php b/app/ticket/service/CategorieService.php new file mode 100644 index 0000000..d769a58 --- /dev/null +++ b/app/ticket/service/CategorieService.php @@ -0,0 +1,97 @@ +list($params); + $value=$result[0]->toArray(); + foreach ($result as $key => &$value) { + $value['name'] = isset($value['translation']) ? $value['translation']['name'] : ''; + unset($value['translation']); + } + unset($value); + + return $result; + } + + public function getAll($params) + { + $result = (new CategorieLogic())->getAll($params); + foreach ($result as $key => &$value) { + $value['name'] = isset($value['translation']) ? $value['translation']['name'] : ''; + unset($value['translation']); + } + unset($value); + + return $result; + } + + public $translatFields = ['name']; + + public function add(array $data) + { + try { + Db::beginTransaction(); + $sourceLocale = locale(); + $traitsourceFields = Translat::extractSourceFields($data, $this->translatFields); + + $result = (new CategorieLogic())->add($data); + + (new TranslationService())->queueTranslations( + 'ticket_categorie', + $result->id, + $sourceLocale, + $traitsourceFields + ); + + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + public function edit(array $data) + { + try { + Db::beginTransaction(); + $sourceLocale = locale(); + $traitsourceFields = Translat::extractSourceFields($data, $this->translatFields); + + $result = (new CategorieLogic())->edit($data); + + (new TranslationService())->queueTranslations( + 'ticket_categorie', + $data['id'], + $sourceLocale, + $traitsourceFields + ); + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + public function delete(int $id) + { + if(empty($id)){ + throw new BusinessException('id不能为空'); + } + try { + $result = (new CategorieLogic())->delete($id); + return $result; + } catch (\Throwable $e) { + throw $e; + } + } +} \ No newline at end of file diff --git a/app/ticket/service/ConversationService.php b/app/ticket/service/ConversationService.php new file mode 100644 index 0000000..1716b8f --- /dev/null +++ b/app/ticket/service/ConversationService.php @@ -0,0 +1,49 @@ +list($params); + + $status_fanyi = array_map(fn($v) => "{{{$v}}}", array_flip(Constants::TICKET_STATUS)); + foreach ($result as $key => $value) { + $result[$key]['content'] = str_replace($status_fanyi,$ticket_status,$value['content']); + } + return $result; + } + + public function add(array $data) + { + try { + return (new ConversationLogic())->add($data); + } catch (\Throwable $e) { + throw $e; + } + } + + public function edit(array $data) + { + try { + Db::beginTransaction(); + $result = (new ConversationLogic())->edit($data); + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } +} \ No newline at end of file diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php new file mode 100644 index 0000000..f5a0d72 --- /dev/null +++ b/app/ticket/service/TicketService.php @@ -0,0 +1,122 @@ +list($params); + foreach ($res as $k => &$v) { + $name = isset($v['categorie']['translation']) ? $v['categorie']['translation']['name'] : ''; + if(isset($res[$k]['categorie'])){ + $res[$k]['categorie']['name'] = $name; + } + if (isset($v['categorie']['translation'])){ + unset($res[$k]['categorie']['translation']); + } + foreach ($v['attachments'] as &$attachments) { + $attachments['storage_path_full'] = (new \app\common\service\OssService())->generatePrivateUrl($attachments['storage_path']); + } + } + unset($v); + unset($attachments); + + return $res; + } + + public function add(array $data) + { + try { + $attachment = $data['attachment']??[]; + $logic = new TicketLogic(); + $result = $logic->add($data); + if(!empty($attachment)){ + (new AttachmentLogic())->adds($attachment, $result->id, $data['user_id']); + } + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + + public function edit(array $data) + { + try { + Db::beginTransaction(); + $attachment = $data['attachment']??[]; + $logic = new TicketLogic(); + $result = $logic->edit($data); + + if(!empty($attachment)){ + (new AttachmentLogic())->edit($attachment, $result->id, $data['user_id']); + } + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + + public function status(array $data) + { + try { + Db::beginTransaction(); + $msg = $data['msg']??[]; + $ticket = (new TicketLogic())->find($data['id']); + if(empty($ticket)){ + throw new BusinessException('工单不存在'); + } + if($data['status'] <= $ticket->status){ + throw new BusinessException(trans('status_not_modified')); + } + +// if(!in_array($ticket->status, [Constants::TICKET_STATUS['PENDING'], Constants::TICKET_STATUS['CONFIRM']]) +// && empty($msg)){ +// throw new BusinessException('提交失败'); +// } + + $ticket->status = $data['status']; + $result = $ticket->save(); + + if(!empty($msg)){ + $conversation = [ + 'ticket_id' => $ticket->id, + 'user_id' => $data['user_id'], + 'content' => '状态更新为{{'.array_search($data['status'],Constants::TICKET_STATUS).'}}:'.$msg + ]; + $res = (new ConversationLogic())->add($conversation); + } + + Db::commit(); + return $result; + } catch (\Throwable $e) { + Db::rollback(); + throw $e; + } + } + + public function getStatus(array $data) + { + $locale = locale(); + $ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN; + + $status = []; + foreach ($ticket_status as $key => $value) { + $status[] = ['value' => $key, 'text' => $value]; + } + return $status; + } +} diff --git a/composer.json b/composer.json index 9e2dd39..ccf259e 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,11 @@ "vlucas/phpdotenv": "^5.6", "firebase/php-jwt": "^6.11", "league/oauth2-server": "^9.2", - "topthink/think-validate": "^3.0" + "topthink/think-validate": "^3.0", + "shopwwi/webman-filesystem": "1.1", + "shopwwi/filesystem-oss": "^1.0", + "webman/redis-queue": "^1.3", + "symfony/translation": "^7.3" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index e10e796..01843a4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,59 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eecf8b50860214e48bdcb8407fd40e14", + "content-hash": "acb636fcb8774558a04bd674f9f95f55", "packages": [ + { + "name": "aliyuncs/oss-sdk-php", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/aliyun/aliyun-oss-php-sdk.git", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "php-coveralls/php-coveralls": "*", + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "OSS\\": "src/OSS" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aliyuncs", + "homepage": "http://www.aliyun.com" + } + ], + "description": "Aliyun OSS SDK for PHP", + "homepage": "http://www.aliyun.com/product/oss/", + "support": { + "issues": "https://github.com/aliyun/aliyun-oss-php-sdk/issues", + "source": "https://github.com/aliyun/aliyun-oss-php-sdk/tree/v2.7.2" + }, + "time": "2024-10-28T10:41:12+00:00" + }, { "name": "brick/math", "version": "0.13.1", @@ -1997,6 +2048,212 @@ }, "time": "2024-09-04T16:06:53+00:00" }, + { + "name": "league/flysystem", + "version": "3.29.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0", + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.29.0" + }, + "time": "2024-09-29T11:59:11+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.29.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + }, + "time": "2024-08-09T21:24:39+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-01-28T23:22:08+00:00" + }, { "name": "league/oauth2-server", "version": "9.2.0", @@ -3267,6 +3524,124 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "shopwwi/filesystem-oss", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/shopwwi/filesystem-oss.git", + "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shopwwi/filesystem-oss/zipball/85cb385d1a5d11ca3c22bc485a7b18c588fe7645", + "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "aliyuncs/oss-sdk-php": "^2.4", + "league/flysystem": "^2.1|^3.0", + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Shopwwi\\FilesystemOss\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tycoon Song", + "email": "8988354@qq.com" + } + ], + "description": "AliYun OSS adapter for flysystem V2. aliyuncs/oss-sdk-php ~2.4", + "homepage": "https://github.com/phcent/filesystem-oss", + "keywords": [ + "Flysystem", + "aliyun-oss", + "flysystem-aliyun-oss" + ], + "support": { + "issues": "https://github.com/shopwwi/filesystem-oss/issues", + "source": "https://github.com/shopwwi/filesystem-oss/tree/v1.0.3" + }, + "time": "2022-10-01T03:00:18+00:00" + }, + { + "name": "shopwwi/webman-filesystem", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/shopwwi/webman-filesystem.git", + "reference": "26ac82515d3787943573c50d1a6f0ca339f0d3d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shopwwi/webman-filesystem/zipball/26ac82515d3787943573c50d1a6f0ca339f0d3d4", + "reference": "26ac82515d3787943573c50d1a6f0ca339f0d3d4", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "^2.3|^3.0", + "php": ">=7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Shopwwi\\WebmanFilesystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tycoon Song", + "email": "8988354@qq.com" + } + ], + "description": "Flysystem V2 adapter for the webman", + "keywords": [ + "cos", + "filesystem", + "oss", + "qiniu", + "webman" + ], + "support": { + "issues": "https://github.com/shopwwi/webman-filesystem/issues", + "source": "https://github.com/shopwwi/webman-filesystem/tree/v1.1.0" + }, + "time": "2024-01-21T08:03:36+00:00" + }, { "name": "symfony/cache", "version": "v7.3.0", @@ -4990,7 +5365,13 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/translation/zipball/4aba29076a29a3aa667e09b791e5f868973a8667", "reference": "4aba29076a29a3aa667e09b791e5f868973a8667", - "shasum": "" + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { "php": ">=8.2", @@ -5772,6 +6153,43 @@ }, "time": "2025-03-14T03:52:14+00:00" }, + { + "name": "webman/redis-queue", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/webman-php/redis-queue.git", + "reference": "6fd491b19acb006d60931724aa2577a046ccb612" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/6fd491b19acb006d60931724aa2577a046ccb612", + "reference": "6fd491b19acb006d60931724aa2577a046ccb612", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "workerman/redis-queue": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\RedisQueue\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "description": "Redis message queue plugin for webman.", + "support": { + "issues": "https://github.com/webman-php/redis-queue/issues", + "source": "https://github.com/webman-php/redis-queue/tree/v1.3.1" + }, + "time": "2024-02-28T07:21:52+00:00" + }, { "name": "workerman/coroutine", "version": "v1.1.3", @@ -5811,6 +6229,90 @@ }, "time": "2025-02-17T03:34:21+00:00" }, + { + "name": "workerman/redis", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/walkor/redis.git", + "reference": "82b398c5bdd5d683a3458aaf645b159b7b39b84e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/redis/zipball/82b398c5bdd5d683a3458aaf645b159b7b39b84e", + "reference": "82b398c5bdd5d683a3458aaf645b159b7b39b84e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7", + "workerman/workerman": "^4.1.0||^5.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\Redis\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/redis/issues", + "source": "https://github.com/walkor/redis/tree/v2.0.4" + }, + "time": "2025-04-05T07:45:12+00:00" + }, + { + "name": "workerman/redis-queue", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/redis-queue.git", + "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/redis-queue/zipball/75dbf7ed2ea228c45dc0df82c0fea35879b715d0", + "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0", + "workerman/redis": "^1.0||^2.0", + "workerman/workerman": ">=4.0.20" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\RedisQueue\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Message queue system written in PHP based on workerman and backed by Redis.", + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/redis-queue/issues", + "source": "https://github.com/walkor/redis-queue/tree/v1.2.1" + }, + "time": "2025-01-02T09:21:45+00:00" + }, { "name": "workerman/webman-framework", "version": "v2.1.2", diff --git a/config/constants/Constants.php b/config/constants/Constants.php new file mode 100644 index 0000000..1e77a2b --- /dev/null +++ b/config/constants/Constants.php @@ -0,0 +1,37 @@ + 1, // 待受理 + 'PROCESS' => 2, //处理中 + 'CONFIRM' => 3, // 待确认 + 'FINISH' => 4, // 已完成 + 'REJECTED' => 5, // 已驳回 + ]; + + public const TICKET_STATUS_ZH = [ + self::TICKET_STATUS['PENDING'] => '待受理', + self::TICKET_STATUS['PROCESS'] => '处理中', + self::TICKET_STATUS['CONFIRM'] => '待确认', + self::TICKET_STATUS['FINISH'] => '已完成', + self::TICKET_STATUS['REJECTED'] => '关闭', + ]; + public const TICKET_STATUS_EN = [ + self::TICKET_STATUS['PENDING'] => 'Pending acceptance', + self::TICKET_STATUS['PROCESS'] => 'Processing', + self::TICKET_STATUS['CONFIRM'] => 'To be confirmed', + self::TICKET_STATUS['FINISH'] => 'Completed', + self::TICKET_STATUS['REJECTED'] => 'close', + ]; + public const TICKET_STATUS_RU = [ + self::TICKET_STATUS['PENDING'] => 'Подлежит рассмотрению', + self::TICKET_STATUS['PROCESS'] => 'В процессе обработки', + self::TICKET_STATUS['CONFIRM'] => 'Подлежит подтверждению', + self::TICKET_STATUS['FINISH'] => 'Завершено', + self::TICKET_STATUS['REJECTED'] => 'Закрыть', + ]; + +} \ No newline at end of file diff --git a/config/plugin/shopwwi/filesystem/app.php b/config/plugin/shopwwi/filesystem/app.php new file mode 100644 index 0000000..f1c74b7 --- /dev/null +++ b/config/plugin/shopwwi/filesystem/app.php @@ -0,0 +1,116 @@ + true, + 'default' => 'local', + 'max_size' => 1024 * 1024 * 10, //单个文件大小10M + 'ext_yes' => [], //允许上传文件类型 为空则为允许所有 + 'ext_no' => [], // 不允许上传文件类型 为空则不限制 + 'storage' => [ + 'public' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class, + 'root' => public_path(), + 'url' => '//127.0.0.1:8787' // 静态文件访问域名 + ], + 'local' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class, + 'root' => runtime_path(), + 'url' => '//127.0.0.1:8787' // 静态文件访问域名 + ], + 'ftp' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class, + 'host' => 'ftp.example.com', + 'username' => 'username', + 'password' => 'password', + 'url' => '' // 静态文件访问域名 + // 'port' => 21, + // 'root' => '/path/to/root', + // 'passive' => true, + // 'ssl' => true, + // 'timeout' => 30, + // 'ignorePassiveAddress' => false, + // 'timestampsOnUnixListingsEnabled' => true, + ], + 'memory' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\MemoryAdapterFactory::class, + ], + 's3' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => false, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'minio' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => true, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'oss' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class, + 'accessId' => getenv('OSS_ACCESSID'), + 'accessSecret' => getenv('OSS_ACCESSSECRET'), + 'bucket' => getenv('OSS_BUCKET'), + 'endpoint' => getenv('OSS_ENDPOINT'), + 'url' => getenv('OSS_STATIC_URL'), // 静态文件访问域名 + // 'timeout' => 3600, + // 'connectTimeout' => 10, + // 'isCName' => false, + // 'token' => null, + // 'proxy' => null, + ], + 'qiniu' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class, + 'accessKey' => 'QINIU_ACCESS_KEY', + 'secretKey' => 'QINIU_SECRET_KEY', + 'bucket' => 'QINIU_BUCKET', + 'domain' => 'QINBIU_DOMAIN', + 'url' => '' // 静态文件访问域名 + ], + 'cos' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class, + 'region' => 'COS_REGION', + 'app_id' => 'COS_APPID', + 'secret_id' => 'COS_SECRET_ID', + 'secret_key' => 'COS_SECRET_KEY', + // 可选,如果 bucket 为私有访问请打开此项 + // 'signed_url' => false, + 'bucket' => 'COS_BUCKET', + 'read_from_cdn' => false, + 'url' => '' // 静态文件访问域名 + // 'timeout' => 60, + // 'connect_timeout' => 60, + // 'cdn' => '', + // 'scheme' => 'https', + ], + ], +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/app.php b/config/plugin/webman/redis-queue/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/redis-queue/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/command.php b/config/plugin/webman/redis-queue/command.php new file mode 100644 index 0000000..8bfe2a1 --- /dev/null +++ b/config/plugin/webman/redis-queue/command.php @@ -0,0 +1,7 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/redis-queue/queue.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ] +]; diff --git a/config/plugin/webman/redis-queue/process.php b/config/plugin/webman/redis-queue/process.php new file mode 100644 index 0000000..9fac733 --- /dev/null +++ b/config/plugin/webman/redis-queue/process.php @@ -0,0 +1,12 @@ + [ + 'handler' => Webman\RedisQueue\Process\Consumer::class, + 'count' => getenv('REDIS_QUEUE_PROCESS_COUNT'), // 可以设置多进程同时消费 + 'constructor' => [ + // 消费者类目录 + // 'consumer_dir' => app_path() . '/queue/redis' + 'consumer_dir' => app_path() . getenv('REDIS_QUEUE_CONSUMER_DIR') + ] + ] +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/redis.php b/config/plugin/webman/redis-queue/redis.php new file mode 100644 index 0000000..420914d --- /dev/null +++ b/config/plugin/webman/redis-queue/redis.php @@ -0,0 +1,22 @@ + [ +// 'host' => 'redis://127.0.0.1:6379', + 'host' => getenv('REDIS_QUEUE_HOST'), + 'options' => [ + 'auth' => getenv('REDIS_QUEUE_AUTH'), // 密码,字符串类型,可选参数 + 'db' => getenv('REDIS_QUEUE_DB'), // 数据库 + 'prefix' => getenv('REDIS_QUEUE_PREFIX'), // key 前缀 + 'max_attempts' => getenv('REDIS_QUEUE_MAX_ATTEMPTS'), // 消费失败后,重试次数 + 'retry_seconds' => getenv('REDIS_QUEUE_RETRY_SECONDS'), // 重试间隔,单位秒 + ], + // Connection pool, supports only Swoole or Swow drivers. + 'pool' => [ + 'max_connections' => 5, + 'min_connections' => 1, + 'wait_timeout' => 3, + 'idle_timeout' => 60, + 'heartbeat_interval' => 50, + ] + ], +]; diff --git a/config/route.php b/config/route.php index 3b642cf..db2c831 100644 --- a/config/route.php +++ b/config/route.php @@ -37,4 +37,31 @@ Route::group('/common', function () { // 调试 Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); }); -}); \ No newline at end of file +}); + + +Route::group('/ticket', function () { + //工单 + Route::any('/ticket/add', [app\ticket\controller\TicketController::class, 'add']); + Route::any('/ticket/edit', [app\ticket\controller\TicketController::class, 'edit']); + Route::any('/ticket/list', [app\ticket\controller\TicketController::class, 'index']); + Route::any('/ticket/status', [app\ticket\controller\TicketController::class, 'status']); + Route::any('/ticket/getStatus', [app\ticket\controller\TicketController::class, 'getStatus']); + + Route::any('/upload', [app\common\controller\UploadController::class, 'uploadFile']); + Route::any('/upload/image', [app\common\controller\UploadController::class, 'uploadImage']); + + //工单沟通记录 + Route::any('/conversation/add', [app\ticket\controller\ConversationController::class, 'add']); + Route::any('/conversation/edit', [app\ticket\controller\ConversationController::class, 'edit']); + Route::any('/conversation/list', [app\ticket\controller\ConversationController::class, 'index']); + + //工单分类 + Route::any('/categorie/add', [app\ticket\controller\CategorieController::class, 'add']); + Route::any('/categorie/edit', [app\ticket\controller\CategorieController::class, 'edit']); + Route::any('/categorie/list', [app\ticket\controller\CategorieController::class, 'index']); + Route::any('/categorie/getAll', [app\ticket\controller\CategorieController::class, 'getAll']); + Route::any('/categorie/delete', [app\ticket\controller\CategorieController::class, 'delete']); + +}); + diff --git a/resource/translations/ar/messages.php b/resource/translations/ar/messages.php new file mode 100644 index 0000000..a701f87 --- /dev/null +++ b/resource/translations/ar/messages.php @@ -0,0 +1,5 @@ + 'الدولة لا يمكن تعديلها عكسيا', +]; \ No newline at end of file diff --git a/resource/translations/en/messages.php b/resource/translations/en/messages.php new file mode 100644 index 0000000..c676eb2 --- /dev/null +++ b/resource/translations/en/messages.php @@ -0,0 +1,5 @@ + 'The status cannot be reversed for modification', +]; \ No newline at end of file diff --git a/resource/translations/ru/messages.php b/resource/translations/ru/messages.php new file mode 100644 index 0000000..96ab3a4 --- /dev/null +++ b/resource/translations/ru/messages.php @@ -0,0 +1,5 @@ + 'Статус не может быть изменен в обратном направлении', +]; \ No newline at end of file diff --git a/resource/translations/zh/messages.php b/resource/translations/zh/messages.php new file mode 100644 index 0000000..229d7ee --- /dev/null +++ b/resource/translations/zh/messages.php @@ -0,0 +1,5 @@ + '状态不能逆向修改', +]; \ No newline at end of file From f6323f251396807e4b5973add11f0816658a7e25 Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 9 Jul 2025 19:10:31 +0800 Subject: [PATCH 025/120] Merge branch 'develop' into feature/mzl_dev # Conflicts: # app/functions.php # config/middleware.php From 3d8d9f841a271e4e5f1b358b9ae92a914d18d33a Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 9 Jul 2025 19:11:57 +0800 Subject: [PATCH 026/120] Merge branch 'feature/mzl_dev' into develop # Conflicts: # app/functions.php # config/middleware.php --- app/common/controller/AuthController.php | 33 +- app/common/controller/BaseController.php | 11 +- app/common/logic/AuthLogic.php | 447 ++++++++------- app/common/logic/AuthLogic_bak.php | 667 +++++++++++++++-------- app/common/logic/AuthLogic_bak2.php | 416 -------------- app/common/model/Corp.php | 49 ++ app/common/model/CorpFeishuApp.php | 56 ++ app/common/model/User.php | 3 +- app/common/model/UserFeishuApp.php | 69 +++ app/common/service/AuthService.php | 12 + app/common/service/BasicDataService.php | 150 +++++ app/common/traits/ResponseTrait.php | 8 +- app/common/utils/Feishu.php | 16 +- app/common/validate/UserValidate.php | 26 +- app/controller/IndexController.php | 72 +-- app/functions.php | 38 ++ app/middleware/AuthCheck.php | 46 ++ app/middleware/BasicData.php | 42 ++ app/view/index/qrcode.html | 46 ++ config/middleware.php | 6 +- config/route.php | 10 +- config/whitelist.php | 9 + public/mzl.html | 64 +++ webman | 0 24 files changed, 1342 insertions(+), 954 deletions(-) delete mode 100644 app/common/logic/AuthLogic_bak2.php create mode 100644 app/common/model/Corp.php create mode 100644 app/common/model/CorpFeishuApp.php create mode 100644 app/common/model/UserFeishuApp.php create mode 100644 app/common/service/BasicDataService.php create mode 100644 app/middleware/AuthCheck.php create mode 100644 app/middleware/BasicData.php create mode 100644 app/view/index/qrcode.html create mode 100644 config/whitelist.php create mode 100644 public/mzl.html mode change 100644 => 100755 webman diff --git a/app/common/controller/AuthController.php b/app/common/controller/AuthController.php index df35e72..4129ee0 100644 --- a/app/common/controller/AuthController.php +++ b/app/common/controller/AuthController.php @@ -8,11 +8,38 @@ use support\Request; class AuthController extends BaseController { + + /** + * 不需要头信息的方法 + */ + protected $noNeedHeader = ['feishuAuth']; + + + // 调试 + public function feishuAuth(Request $request) + { + try { + $code = $request->all(); + return $this->success($code); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function feishuScope(Request $request) + { + try { + $result = (new AuthService())->feishuAuthorize($request->corp_abbr, $request->system_code); + return $this->success($result); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } public function login(Request $request) { try { - $data = $this->validate(new UserValidate(), $request->post(), 'upwd_login'); + $data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_login'); $result = (new AuthService())->login($data); return $this->success($result, '登录成功'); } catch (\Throwable $e) { @@ -23,11 +50,11 @@ class AuthController extends BaseController public function register(Request $request) { try { - $data = $this->validate(new UserValidate(), $request->post(), 'upwd_register'); + $data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_register'); $result = (new AuthService())->register($data); return $this->success($result, '注册成功'); } catch (\Throwable $e) { - return $this->error($e->getMessage().'|'.$e->getFile().'|'.$e->getLine()); + return $this->error($e->getMessage()); } } diff --git a/app/common/controller/BaseController.php b/app/common/controller/BaseController.php index 10b3e95..0fefb82 100644 --- a/app/common/controller/BaseController.php +++ b/app/common/controller/BaseController.php @@ -2,11 +2,10 @@ namespace app\common\controller; -use support\Request; -use support\Response; use support\exception\BusinessException; use app\common\traits\ResponseTrait; use think\Validate; +use support\Request; abstract class BaseController { @@ -37,4 +36,12 @@ abstract class BaseController // 验证成功,返回原始数据 return $data; } + + /** + * 参数处理 + */ + public function requestData(Request $request) + { + return $request->all() + $request->basic_data; + } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index de97262..3c84694 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -2,10 +2,14 @@ namespace app\common\logic; +use app\common\model\Corp; +use app\common\model\CorpFeishuApp; use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; +use app\common\model\UserFeishuApp; use app\common\model\UserSn; +use app\common\utils\Feishu; use app\common\utils\Jwt; use support\Redis; use support\exception\BusinessException; @@ -19,47 +23,27 @@ class AuthLogic public function uPwdLogin(array $data): array { $user = $this->validateUser($data['account'], $data['password']); - $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** * 飞书登录 + * 如果用户不存在则新增用户信息 */ public function feishuLogin(array $data): array { - $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); + $feishuUserInfo = $this->getFeishuUserInfo($data); $user = $this->getOrCreateUser($feishuUserInfo); $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + if ($user->isLogin) { + $this->validateSystemPermission($user, $system); + } - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** @@ -69,25 +53,15 @@ class AuthLogic { $userId = $this->validateQrCodeToken($data['qr_code_token']); $user = User::find($userId); + if (!$user || $user->status != 1) { throw new BusinessException('用户无效', 403); } $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; + return $this->generateTokensAndReturn($user, $system, $data); } /** @@ -102,29 +76,143 @@ class AuthLogic // 对密码进行哈希处理 $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + // 获取企业ID + $corpInfo = $this->validateCorp($data['corp_abbr']); + $data['corp_id'] = $corpInfo->id; + + // 处理账户 + $data = $data + normalizeAccount($data['account']); + // 创建用户记录 $user = $this->createUser($data); // 更新用户编号的用户 ID $this->updateUserId($user->id, $sn->user_sn); - // 注册后如果直接登录(返回token) $system = $this->validateSystem($data['system_code']); + return $this->generateTokensAndReturn($user, $system, $data); + } + + /** ----------- 内部方法 ------------- **/ + + /** + * 查找或创建飞书用户 + */ + private function getOrCreateUser(array $feishuUserInfo) + { + $userFeishuApp = UserFeishuApp::firstOrCreate([ + 'name' => $feishuUserInfo['en_name'], + 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'] + ]); + + if ($userFeishuApp->uid) { + $user = User::find($userFeishuApp->uid); + $user->isLogin = true; + return $user; + } else { + $user = User::where('phone', $userFeishuApp->mobile)->first(); + $user->isLogin = true; + if (!$user) { + $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); + $user->isLogin = false; + } + $userFeishuApp->uid = $user->id; + $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; + if (!$userFeishuApp->save()) { + throw new BusinessException('绑定用户飞书记录失败'); + } + return $user; + } + } + + /** + * 过滤和格式化手机号 + */ + private function sanitizePhone($mobile) + { + return isset($mobile) ? str_replace('+86', '', $mobile) : ''; + } + + /** + * 创建飞书用户 + */ + private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) + { + // 1. 获取适用的用户编号 + $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$userSn) { + throw new BusinessException('未找到适用编号'); + } + + // 2. 创建用户 + $user = new User(); + $user->sn = $userSn->user_sn; + $user->name = $feishuUserInfo['en_name']; + $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; + $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); + $user->phone = $userFeishuApp->mobile; + $user->email = ''; + $user->system_code = $feishuUserInfo['system_code']; + $user->corp_id = $feishuUserInfo['corp_id']; + + if (!$user->save()) { + throw new BusinessException('用户创建失败'); + } + + // 更新使用状态 + $this->updateUserId($user->id, $userSn->user_sn); + + return $user; + } + + /** + * 生成并记录token,返回包含token的响应数据 + */ + private function generateTokensAndReturn(User $user, Systems $system, array $data): array + { $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + + // 记录 token 到 Redis 和 MySQL $this->recordUserToken($user->id, $token, $data['system_code']); return [ - 'user' => $user, 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, + 'system_code' => $data['system_code'], + 'corp_abbr' => $data['corp_abbr'], + 'login_type' => $data['login_type'] + // 'payload' => $payload, ]; } - public function updateUserId($userId, $userSn) + /** + * 获取飞书用户信息 + */ + private function getFeishuUserInfo(array $data) + { + $certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']); + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + $userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']); + $userInfo = $feishu->userInfo($userAccessToken['access_token']); + + if ($userInfo['code'] != 0) { + throw new BusinessException('授权失败,请重试'); + } + + return $userInfo['data'] + $data + $certificate; + } + + /** + * 更新用户编号 + */ + private function updateUserId($userId, $userSn) { $updated = UserSn::where('user_sn', $userSn) ->update(['user_id' => $userId]); @@ -136,81 +224,6 @@ class AuthLogic return $updated; } - /** - * 获取可用的用户编号 - * - * @return \app\common\model\UserSn 用户编号模型实例 - * @throws BusinessException 当未找到适用编号时抛出 - */ - private function getAvailableUserSn(): \app\common\model\UserSn - { - $userSnModel = new UserSn(); - $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$sn) { - Log::error('用户注册时未找到适用编号'); - throw new BusinessException('未找到适用编号'); - } - return $sn; - } - - /** - * 创建用户记录 - * - * @param array $data 包含用户信息的数组 - * @return \app\common\model\User 用户模型实例 - */ - private function createUser(array $data): \app\common\model\User - { - try { - return User::create($data); - } catch (\Throwable $e) { - Log::error('用户注册时创建用户记录失败', [ - 'data' => $data, - 'error' => $e->getMessage() - ]); - throw new BusinessException('用户注册失败,请稍后重试', 500); - } - } - - /** - * 刷新token - * @param string $refreshToken - * @param string|null $systemCode 可选,指定系统 - * @return array - */ - public function refreshToken(string $refreshToken, ?string $systemCode = null): array - { - if (!$systemCode) throw new BusinessException('缺少system_code', 422); - - // 校验refresh_token - $userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}"); - if (!$userId) { - throw new BusinessException('refresh_token无效或已过期', 401); - } - $validToken = Redis::get("refresh_token:{$userId}:{$systemCode}"); - if ($validToken !== $refreshToken) { - throw new BusinessException('refresh_token无效或已过期', 401); - } - - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = $this->validateSystem($systemCode); - $this->validateSystemPermission($user, $system); - - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $this->recordUserToken($user->id, $token, $systemCode); - - return [ - 'access_token' => $token, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - /** * 记录用户token到 Redis 和 MySQL */ @@ -254,7 +267,6 @@ class AuthLogic } } - /** * 校验token有效性 */ @@ -278,78 +290,25 @@ class AuthLogic } /** - * 登出/失效token + * 获取飞书应用信息 */ - public function logout(?string $token, ?string $systemCode = null) + public function feishuAppInfo(string $corpAbbr, string $systemCode) { - if ($token) { - Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - $aud = $payload['aud'] ?? null; - $sc = $systemCode ?? $aud; - if ($userId && $sc) { - Redis::sRem("user_tokens:{$userId}:{$sc}", $token); - } - Redis::del("user_token_meta:{$token}"); - UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } + $corpInfo = $this->validateCorp($corpAbbr); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); + return [ + 'app_id' => $fsAppInfo->app_id, + 'app_secret' => $fsAppInfo->app_secret, + 'r_url' => $fsAppInfo->r_url, + 'scope' => $fsAppInfo->scopes, + 'state' => $fsAppInfo->state, + 'abbr' => $corpAbbr, + 'corp_id' => $corpInfo->id + ]; } /** - * 强制拉黑token - */ - public function blockToken(string $token, ?string $systemCode = null) - { - if (!$token) { - throw new BusinessException('token不能为空', 422); - } - Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - $aud = $payload['aud'] ?? null; - $sc = $systemCode ?? $aud; - if ($userId && $sc) { - Redis::sRem("user_tokens:{$userId}:{$sc}", $token); - } - Redis::del("user_token_meta:{$token}"); - UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } - - /** - * 获取用户所有活跃token及元信息(按system_code) - */ - public function getUserTokens(int $userId, string $systemCode): array - { - $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; - $result = []; - foreach ($tokens as $token) { - $meta = Redis::hGetAll("user_token_meta:{$token}"); - $result[] = [ - 'token' => $token, - 'meta' => $meta, - ]; - } - return $result; - } - - /** - * 强制某用户所有token全部下线 - */ - public function blockAllTokensOfUser(int $userId, string $systemCode) - { - $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; - foreach ($tokens as $token) { - $this->blockToken($token, $systemCode); - } - } - - /** ----------- 内部方法 ------------- **/ - - /** - * 用户验证 + * 验证用户信息 */ private function validateUser(string $account, string $password) { @@ -364,7 +323,7 @@ class AuthLogic } /** - * 系统验证 + * 验证系统信息 */ private function validateSystem(string $systemCode) { @@ -376,7 +335,31 @@ class AuthLogic } /** - * 用户系统权限校验 + * 验证公司信息 + */ + private function validateCorp(string $corpAbbr) + { + $corp = Corp::where('abbr', $corpAbbr)->first(); + if (!$corp) { + throw new BusinessException('公司不存在', 403); + } + return $corp; + } + + /** + * 验证飞书应用信息 + */ + private function validateFeishuApp(int $corpId, string $systemCode) + { + $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first(); + if (!$feishuAppInfo) { + throw new BusinessException('飞书应用不存在', 403); + } + return $feishuAppInfo; + } + + /** + * 验证用户系统权限 */ private function validateSystemPermission(User $user, Systems $system) { @@ -388,6 +371,54 @@ class AuthLogic } } + /** + * 验证扫码令牌 + */ + private function validateQrCodeToken(string $qrCodeToken) + { + $userId = Redis::get("qr_code_token:{$qrCodeToken}"); + if (!$userId) { + throw new BusinessException('扫码令牌无效或已过期', 401); + } + return $userId; + } + + /** + * 获取可用的用户编号 + * + * @return \app\common\model\UserSn 用户编号模型实例 + * @throws BusinessException 当未找到适用编号时抛出 + */ + private function getAvailableUserSn(): \app\common\model\UserSn + { + $userSnModel = new UserSn(); + $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$sn) { + Log::error('用户注册时未找到适用编号'); + throw new BusinessException('未找到适用编号'); + } + return $sn; + } + + /** + * 创建用户记录 + * + * @param array $data 包含用户信息的数组 + * @return \app\common\model\User 用户模型实例 + */ + private function createUser(array $data): \app\common\model\User + { + try { + return User::create($data); + } catch (\Throwable $e) { + Log::error('用户注册时创建用户记录失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('用户注册失败,请稍后重试', 500); + } + } + /** * JWT payload 构建 * 可扩展 payload 字段 @@ -432,44 +463,4 @@ class AuthLogic Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration')); return $refreshToken; } - - /** - * 验证扫码令牌 - */ - private function validateQrCodeToken(string $qrCodeToken) - { - $userId = Redis::get("qr_code_token:{$qrCodeToken}"); - if (!$userId) { - throw new BusinessException('扫码令牌无效或已过期', 401); - } - return $userId; - } - - /** - * 调用飞书API获取用户信息(示例) - */ - private function getFeishuUserInfo(string $code) - { - // 实际实现需要调用飞书 API - return [ - 'user_id' => 'feishu_user_123', - 'name' => '飞书用户', - ]; - } - - /** - * 查找或创建飞书用户(示例) - */ - private function getOrCreateUser(array $feishuUserInfo) - { - $user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first(); - if (!$user) { - $user = new User(); - $user->feishu_user_id = $feishuUserInfo['user_id']; - $user->name = $feishuUserInfo['name']; - $user->status = 1; - $user->save(); - } - return $user; - } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic_bak.php b/app/common/logic/AuthLogic_bak.php index d33cd0e..0e68093 100644 --- a/app/common/logic/AuthLogic_bak.php +++ b/app/common/logic/AuthLogic_bak.php @@ -2,357 +2,592 @@ namespace app\common\logic; +use app\common\model\Corp; +use app\common\model\CorpFeishuApp; +use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; +use app\common\model\UserFeishuApp; use app\common\model\UserSn; +use app\common\utils\Feishu; use app\common\utils\Jwt; -use support\Cache; +use support\Redis; use support\exception\BusinessException; +use support\Log; class AuthLogic_bak { - /** - * 账号密码登录方法 - * - * 该方法接收包含账号、密码和系统编码的数据数组,验证用户信息和系统信息, - * 检查用户对系统的权限,生成访问令牌和刷新令牌并返回。 - * - * @param array $data 包含登录信息的数组,键名为 'account'(账号)、'password'(密码)、'system_code'(系统编码) - * @return array 包含访问令牌、刷新令牌和令牌过期时间的数组 + * 账号密码登录 */ public function uPwdLogin(array $data): array { - // 验证用户账号和密码,返回用户对象 - $user = $this->validateUser($data['account'], $data['password']); - // 验证系统编码,返回系统对象 - $system = $this->validateSystem($data['system_code']); - // 验证用户对该系统是否有访问权限 - $this->validateSystemPermission($user, $system); - // 准备 JWT 令牌的负载数据 - $payload = $this->preparePayload($user, $system); - // 根据负载数据生成访问令牌 - $token = $this->generateAccessToken($payload); - // 根据用户 ID 生成刷新令牌 - $refreshToken = $this->generateRefreshToken($user->id); + $user = $this->validateUser($data['account'], $data['password']); + + $system = $this->validateSystem($data['system_code']); + $this->validateSystemPermission($user, $system); + $payload = $this->buildJwtPayload($user, $system); + $token = $this->generateAccessToken($payload); + $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + + // 记录 token 到 Redis 和 MySQL + $this->recordUserToken($user->id, $token, $data['system_code']); - // 返回包含访问令牌、刷新令牌和令牌过期时间的数组 return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, ]; } - /** * 飞书登录 - * - * @param array $data - * @return array + * 如果用户不存在则新增用户信息 */ - public function feishuLogin(array $data): array + public function feishuLogin(array $data, bool $isLogin = true): array { - $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); + $feishuUserInfo = $this->getFeishuUserInfo($data); $user = $this->getOrCreateUser($feishuUserInfo); $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->preparePayload($user, $system); + if ($user->isLogin === true) { + $this->validateSystemPermission($user, $system); + } + $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); + $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + + // 记录 token 到 Redis 和 MySQL + $this->recordUserToken($user->id, $token, $data['system_code']); return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, ]; } /** * 扫码登录 - * - * @param array $data - * @return array */ public function qrCodeLogin(array $data): array { $userId = $this->validateQrCodeToken($data['qr_code_token']); $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new \Exception('用户无效', 403); + throw new BusinessException('用户无效', 403); } $system = $this->validateSystem($data['system_code']); $this->validateSystemPermission($user, $system); - $payload = $this->preparePayload($user, $system); + $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); + $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + + // 记录 token 到 Redis 和 MySQL + $this->recordUserToken($user->id, $token, $data['system_code']); return [ 'access_token' => $token, 'refresh_token' => $refreshToken, 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, ]; } /** - * 验证用户信息 + * 注册 + */ + public function register(array $data) + { + // 获取可用用户编号 + $sn = $this->getAvailableUserSn(); + $data['sn'] = $sn->user_sn; + + // 对密码进行哈希处理 + $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + + // 获取企业ID + $corpInfo = $this->validateCorp($data['corp_abbr']); + $data['corp_id'] = $corpInfo->id; + + // 创建用户记录 + $user = $this->createUser($data); + + // 更新用户编号的用户 ID + $this->updateUserId($user->id, $sn->user_sn); + + // 注册后如果直接登录(返回token) + $system = $this->validateSystem($data['system_code']); + $payload = $this->buildJwtPayload($user, $system); + $token = $this->generateAccessToken($payload); + $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + $this->recordUserToken($user->id, $token, $data['system_code']); + + return [ + 'user' => $user, + 'access_token' => $token, + 'refresh_token' => $refreshToken, + 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, + ]; + } + + public function updateUserId($userId, $userSn) + { + $updated = UserSn::where('user_sn', $userSn) + ->update(['user_id' => $userId]); + + if ($updated === 0) { + throw new BusinessException('用户编号不存在或更新失败'); + } + + return $updated; + } + + /** + * 获取可用的用户编号 * - * @param string $account - * @param string $password - * @return User|null + * @return \app\common\model\UserSn 用户编号模型实例 + * @throws BusinessException 当未找到适用编号时抛出 + */ + private function getAvailableUserSn(): \app\common\model\UserSn + { + $userSnModel = new UserSn(); + $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$sn) { + Log::error('用户注册时未找到适用编号'); + throw new BusinessException('未找到适用编号'); + } + return $sn; + } + + /** + * 创建用户记录 + * + * @param array $data 包含用户信息的数组 + * @return \app\common\model\User 用户模型实例 + */ + private function createUser(array $data): \app\common\model\User + { + try { + return User::create($data); + } catch (\Throwable $e) { + Log::error('用户注册时创建用户记录失败', [ + 'data' => $data, + 'error' => $e->getMessage() + ]); + throw new BusinessException('用户注册失败,请稍后重试', 500); + } + } + + /** + * 刷新token + * @param string $refreshToken + * @param string|null $systemCode 可选,指定系统 + * @return array + */ + public function refreshToken(string $refreshToken, ?string $systemCode = null): array + { + if (!$systemCode) throw new BusinessException('缺少system_code', 422); + + // 校验refresh_token + $userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}"); + if (!$userId) { + throw new BusinessException('refresh_token无效或已过期', 401); + } + $validToken = Redis::get("refresh_token:{$userId}:{$systemCode}"); + if ($validToken !== $refreshToken) { + throw new BusinessException('refresh_token无效或已过期', 401); + } + + $user = User::find($userId); + if (!$user || $user->status != 1) { + throw new BusinessException('用户无效', 403); + } + + $system = $this->validateSystem($systemCode); + $this->validateSystemPermission($user, $system); + + $payload = $this->buildJwtPayload($user, $system); + $token = $this->generateAccessToken($payload); + $this->recordUserToken($user->id, $token, $systemCode); + + return [ + 'access_token' => $token, + 'expires_in' => config('jwt.jwt_expiration'), + 'payload' => $payload, + ]; + } + + /** + * 记录用户token到 Redis 和 MySQL + */ + protected function recordUserToken(int $userId, string $token, string $systemCode) + { + $request = request(); + $ip = $request ? $request->getRealIp() : ''; + $ua = $request ? $request->header('user-agent', '') : ''; + $now = time(); + $expireAt = $now + config('jwt.jwt_expiration'); + + // Redis Set 用户token集合 + Redis::sAdd("user_tokens:{$userId}:{$systemCode}", $token); + Redis::expire("user_tokens:{$userId}:{$systemCode}", config('jwt.jwt_expiration')); + + // Redis Hash token元信息 + $meta = [ + 'system_code' => $systemCode, + 'login_ip' => $ip, + 'user_agent' => $ua, + 'login_time' => $now, + 'expired_at' => $expireAt, + ]; + Redis::hMSet("user_token_meta:{$token}", $meta); + Redis::expire("user_token_meta:{$token}", config('jwt.jwt_expiration')); + + // 可选:MySQL 持久化 + try { + UserTokenLog::create([ + 'user_id' => $userId, + 'token' => $token, + 'system_code'=> $systemCode, + 'login_ip' => $ip, + 'user_agent' => $ua, + 'login_time' => $now, + 'expired_at' => $expireAt, + 'is_valid' => 1, + ]); + } catch (\Throwable $e) { + Log::error('Token log write failed: '.$e->getMessage()); + } + } + + + /** + * 校验token有效性 + */ + public function verifyToken(string $token, string $systemCode): array + { + if (!$systemCode) throw new BusinessException('缺少system_code', 422); + + $payload = (new Jwt())->verifyToken($token); + + // 校验黑名单 + if (Redis::has("token_blacklist:{$token}")) { + throw new BusinessException('token已失效', 403); + } + + // 强制校验aud(系统隔离) + if ($systemCode && $payload['aud'] != $systemCode && $payload['aud'] != ($payload['system_code'] ?? '')) { + throw new BusinessException('token不属于当前系统', 403); + } + + return (array)$payload; + } + + /** + * 登出/失效token + */ + public function logout(?string $token, ?string $systemCode = null) + { + if ($token) { + Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); + $payload = null; + try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} + $userId = $payload['uid'] ?? null; + $aud = $payload['aud'] ?? null; + $sc = $systemCode ?? $aud; + if ($userId && $sc) { + Redis::sRem("user_tokens:{$userId}:{$sc}", $token); + } + Redis::del("user_token_meta:{$token}"); + UserTokenLog::where('token', $token)->update(['is_valid'=>0]); + } + } + + /** + * 强制拉黑token + */ + public function blockToken(string $token, ?string $systemCode = null) + { + if (!$token) { + throw new BusinessException('token不能为空', 422); + } + Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); + $payload = null; + try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} + $userId = $payload['uid'] ?? null; + $aud = $payload['aud'] ?? null; + $sc = $systemCode ?? $aud; + if ($userId && $sc) { + Redis::sRem("user_tokens:{$userId}:{$sc}", $token); + } + Redis::del("user_token_meta:{$token}"); + UserTokenLog::where('token', $token)->update(['is_valid'=>0]); + } + + /** + * 获取用户所有活跃token及元信息(按system_code) + */ + public function getUserTokens(int $userId, string $systemCode): array + { + $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; + $result = []; + foreach ($tokens as $token) { + $meta = Redis::hGetAll("user_token_meta:{$token}"); + $result[] = [ + 'token' => $token, + 'meta' => $meta, + ]; + } + return $result; + } + + /** + * 强制某用户所有token全部下线 + */ + public function blockAllTokensOfUser(int $userId, string $systemCode) + { + $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; + foreach ($tokens as $token) { + $this->blockToken($token, $systemCode); + } + } + + /** ----------- 内部方法 ------------- **/ + + /** + * 用户验证 */ private function validateUser(string $account, string $password) { $user = User::byAccount($account)->first(); if (!$user || !password_verify($password, $user->password)) { - throw new \Exception('账号或密码错误', 401); + throw new BusinessException('账号或密码错误', 401); } - if ($user->status != 1) { - throw new \Exception('账号已禁用', 403); + throw new BusinessException('账号已禁用', 403); } - return $user; } /** - * 验证系统信息 - * - * @param string $systemCode - * @return Systems|null + * 系统验证 */ private function validateSystem(string $systemCode) { $system = Systems::where('code', $systemCode)->first(); if (!$system) { - throw new \Exception('系统不存在', 403); + throw new BusinessException('系统不存在', 403); } - return $system; } /** - * 验证用户系统权限 - * - * @param User $user - * @param Systems $system + * 公司验证 + */ + private function validateCorp(string $corpAbbr) + { + $corp = Corp::where('abbr', $corpAbbr)->first(); + if (!$corp) { + throw new BusinessException('公司不存在', 403); + } + return $corp; + } + + /** + * 飞书应用信息 + */ + private function validateFeishuApp(int $corpId, string $systemCode) + { + $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first(); + if (!$feishuAppInfo) { + throw new BusinessException('飞书应用不存在', 403); + } + return $feishuAppInfo; + } + + /** + * 用户系统权限校验 */ private function validateSystemPermission(User $user, Systems $system) { if ($system->is_role) { $systemIds = $user->systems->pluck('id')->toArray(); if (!in_array($system->id, $systemIds)) { - throw new \Exception('无该系统权限', 403); + throw new BusinessException('无该系统权限', 403); } } } /** - * 准备 JWT 负载 - * - * @param User $user - * @param Systems $system - * @return array + * JWT payload 构建 + * 可扩展 payload 字段 */ - private function preparePayload(User $user, Systems $system) + private function buildJwtPayload(User $user, ?Systems $system = null): array { - $payload = [ - 'uid' => $user->id, - 'sub' => 'user_'.$user->id, - 'aud' => $system->domain_url, - 'role_ids' => [], - 'system_ids' => [] + $now = time(); + $exp = $now + config('jwt.jwt_expiration'); + $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; + $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; + + return [ + 'sub' => 'user_' . $user->id, + 'aud' => $system->code, + 'iat' => $now, + 'nbf' => $now, + 'exp' => $exp, + 'typ' => 'access', + 'uid' => $user->id, + 'role_ids' => $role_ids, + 'system_ids' => $system_ids ]; - - if ($system->is_role) { - $systemIds = $user->systems->pluck('id')->toArray(); - $payload['role_ids'] = $user->roles->pluck('id')->toArray(); - $payload['system_ids'] = $systemIds; - } - - return $payload; } /** - * 生成访问令牌 - * - * @param array $payload - * @return string + * JWT签发 */ private function generateAccessToken(array $payload) { return (new Jwt())->generateToken($payload); } - // private function verifyRefreshToken(string $token) - // { - // return (new Jwt())->verifyToken($token); - // } - /** - * 生成刷新令牌 - * - * @param int $userId - * @return string + * 刷新令牌生成 */ - private function generateRefreshToken(int $userId) + private function generateRefreshToken(int $userId, string $systemCode) { $refreshToken = bin2hex(random_bytes(32)); - Cache::set("refresh_token:{$refreshToken}", $userId, config('jwt.jwt_refresh_token_expiration')); + // user_id+system_code只允许一个refresh_token生效 + Redis::set("refresh_token:{$userId}:{$systemCode}", $refreshToken, config('jwt.jwt_refresh_token_expiration')); + // 建立refresh_token到user_id+system_code映射,便于校验 + Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration')); return $refreshToken; } - /** - * 调用飞书 API 获取用户信息 - * - * @param string $code - * @return array - */ - private function getFeishuUserInfo(string $code) - { - // 实际实现需要调用飞书 API,这里只是示例 - return [ - 'user_id' => 'feishu_user_123', - 'name' => '飞书用户', - ]; - } - - /** - * 根据飞书用户信息查找或创建本地用户 - * - * @param array $feishuUserInfo - * @return User - */ - private function getOrCreateUser(array $feishuUserInfo) - { - // 实际实现需要根据飞书用户信息查找或创建本地用户,这里只是示例 - $user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first(); - if (!$user) { - $user = new User(); - $user->feishu_user_id = $feishuUserInfo['user_id']; - $user->name = $feishuUserInfo['name']; - $user->status = 1; - $user->save(); - } - return $user; - } - /** * 验证扫码令牌 - * - * @param string $qrCodeToken - * @return int */ private function validateQrCodeToken(string $qrCodeToken) { - // 实际实现需要验证扫码令牌,这里只是示例 - $userId = Cache::get("qr_code_token:{$qrCodeToken}"); + $userId = Redis::get("qr_code_token:{$qrCodeToken}"); if (!$userId) { - throw new \Exception('扫码令牌无效或已过期', 401); + throw new BusinessException('扫码令牌无效或已过期', 401); } return $userId; } /** - * 处理用户注册逻辑 - * - * @param array $data - * @return User + * 获取飞书用户信息 */ - public function register(array $data) + private function getFeishuUserInfo(array $data) { - $userSnModel = new UserSn(); - $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$sn) { + // 获取凭证 + $certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']); + // 获取token + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + $userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']); + // 获取用户信息 + $userInfo = $feishu->userInfo($userAccessToken['access_token']); + if ($userInfo['code'] != 0) { + throw new BusinessException('授权失败,请重试'); + } + return $userInfo['data'] + $data + $certificate; + } + + /** + * 查找或创建飞书用户 + */ + private function getOrCreateUser(array $feishuUserInfo) + { + $userFeishuApp = UserFeishuApp::firstOrCreate([ + 'name' => $feishuUserInfo['en_name'], + 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'] + ]); + + if ($userFeishuApp->uid) { + $user = User::find($userFeishuApp->uid); + $user->isLogin = true; + return $user; + } else { + $user = User::where('phone', $userFeishuApp->mobile)->first(); + $user->isLogin = true; + if (!$user) { + $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); + $user->isLogin = false; + } + $userFeishuApp->uid = $user->id; + $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; + if (!$userFeishuApp->save()) { + throw new BusinessException('绑定用户飞书记录失败'); + } + return $user; + } + } + + /** + * 过滤和格式化手机号 + */ + private function sanitizePhone($mobile) + { + return isset($mobile) ? str_replace('+86', '', $mobile) : ''; + } + + /** + * 创建飞书用户 + */ + private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) + { + // 1. 获取适用的用户编号 + $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); + if (!$userSn) { throw new BusinessException('未找到适用编号'); } - $data['sn'] = $sn->user_sn; - $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); - $user = User::create($data); - return $this->updateUserId($user->id, $sn->user_sn); - } + // 2. 创建用户 + $user = new User(); + $user->sn = $userSn->user_sn; + $user->name = $feishuUserInfo['en_name']; + $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; + $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); + $user->phone = $userFeishuApp->mobile; + $user->email = ''; + $user->system_code = $feishuUserInfo['system_code']; + $user->corp_id = $feishuUserInfo['corp_id']; - public function updateUserId($userId, $userSn) - { - $userSn = User::where('user_sn', $userSn)->first(); - if (!$userSn) { - throw new BusinessException('用户编号不存在'); + if (!$user->save()) { + throw new BusinessException('用户创建失败'); } - return $userSn->update([ - 'user_id' => $userId - ]); + + // 更新使用状态 + $this->updateUserId($user->id, $userSn->user_sn); + + return $user; } + /** - * 校验token + * 飞书应用信息 */ - public function verifyToken(string $token): array + public function feishuAppInfo(string $corpAbbr, string $systemCode) { - $payload = (new Jwt())->verifyToken($token); - if (Cache::has("token_blacklist:{$token}")) { - throw new \Exception('token已失效', 403); - } - return (array)$payload; - } - - /** - * 刷新token - * @param string $refreshToken - * @return array - */ - public function refreshToken(string $refreshToken): array - { - $userId = Cache::get("refresh_token:{$refreshToken}"); - if (!$userId) { - throw new \Exception('refresh_token无效或已过期', 401); - } - - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new \Exception('用户无效', 403); - } - - // 获取角色ID、系统ID(和你登录时保持一致) - $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; - $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; - - $payload = [ - 'uid' => $user->id, - 'sub' => 'user_' . $user->id, - 'role_ids' => $role_ids, - 'system_ids' => $system_ids, - ]; - // 可补充aud、exp等字段,保持和登录生成payload一致 - - $token = (new Jwt())->generateToken($payload); - + $corpInfo = $this->validateCorp($corpAbbr); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); return [ - 'access_token' => $token, - 'expires_in' => config('jwt.jwt_expiration'), + 'app_id' => $fsAppInfo->app_id, + 'app_secret' => $fsAppInfo->app_secret, + 'r_url' => $fsAppInfo->r_url, + 'scope' => $fsAppInfo->scopes, + 'state' => $fsAppInfo->state, + 'abbr' => $corpAbbr, + 'corp_id' => $corpInfo->id ]; } - - /** - * 登出,将token加入黑名单 - */ - public function logout(?string $token) - { - if ($token) { - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - } - } - - - public function blockToken(string $token) - { - if (!$token) { - throw new \Exception('token不能为空', 422); - } - // 加入黑名单 - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic_bak2.php b/app/common/logic/AuthLogic_bak2.php deleted file mode 100644 index 56f94a7..0000000 --- a/app/common/logic/AuthLogic_bak2.php +++ /dev/null @@ -1,416 +0,0 @@ -validateUser($data['account'], $data['password']); - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); - - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, // 方便前端调试,可移除 - ]; - } - - /** - * 飞书登录 - */ - public function feishuLogin(array $data): array - { - $feishuUserInfo = $this->getFeishuUserInfo($data['feishu_code']); - $user = $this->getOrCreateUser($feishuUserInfo); - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 扫码登录 - */ - public function qrCodeLogin(array $data): array - { - $userId = $this->validateQrCodeToken($data['qr_code_token']); - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 注册 - */ - public function register(array $data) - { - $userSnModel = new UserSn(); - $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$sn) { - throw new BusinessException('未找到适用编号'); - } - $data['sn'] = $sn->user_sn; - $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); - $user = User::create($data); - $this->updateUserId($user->id, $sn->user_sn); - return $user; - } - - public function updateUserId($userId, $userSn) - { - $userSn = User::where('user_sn', $userSn)->first(); - if (!$userSn) { - throw new BusinessException('用户编号不存在'); - } - return $userSn->update([ - 'user_id' => $userId - ]); - } - - /** - * 刷新token - * @param string $refreshToken - * @param string|null $systemCode 可选,指定系统 - * @return array - */ - public function refreshToken(string $refreshToken, ?string $systemCode = null): array - { - $userId = Cache::get("refresh_token:{$refreshToken}"); - if (!$userId) { - throw new BusinessException('refresh_token无效或已过期', 401); - } - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = null; - if ($systemCode) { - $system = Systems::where('code', $systemCode)->first(); - if (!$system) { - throw new BusinessException('系统不存在', 403); - } - $this->validateSystemPermission($user, $system); - } - - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - - return [ - 'access_token' => $token, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 记录用户token到 Redis 和 MySQL - */ - protected function recordUserToken(int $userId, string $token) - { - $request = request(); - $ip = $request ? $request->getRealIp() : ''; - $ua = $request ? $request->header('user-agent', '') : ''; - $now = time(); - $expireAt = $now + config('jwt.jwt_expiration'); - - // Redis Set 用户token集合 - Cache::sAdd("user_tokens:{$userId}", $token); - Cache::expire("user_tokens:{$userId}", config('jwt.jwt_expiration')); - - // Redis Hash token元信息 - $meta = [ - 'login_ip' => $ip, - 'user_agent' => $ua, - 'login_time' => $now, - 'expired_at' => $expireAt, - ]; - Cache::hMSet("user_token_meta:{$token}", $meta); - Cache::expire("user_token_meta:{$token}", config('jwt.jwt_expiration')); - - // 可选:MySQL 持久化 - try { - UserTokenLog::create([ - 'user_id' => $userId, - 'token' => $token, - 'login_ip' => $ip, - 'user_agent' => $ua, - 'login_time' => $now, - 'expired_at' => $expireAt, - 'is_valid' => 1, - ]); - } catch (\Throwable $e) { - // 写库失败可忽略,但建议日志留存 - Log::error('Token log write failed: '.$e->getMessage()); - } - } - - - /** - * 校验token有效性 - */ - public function verifyToken(string $token): array - { - $payload = (new Jwt())->verifyToken($token); - if (Cache::has("token_blacklist:{$token}")) { - throw new BusinessException('token已失效', 403); - } - return (array)$payload; - } - - // /** - // * 登出/失效token - // */ - // public function logout(?string $token) - // { - // if ($token) { - // Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - // } - // } - - // /** - // * 强制拉黑token - // */ - // public function blockToken(string $token) - // { - // if (!$token) { - // throw new BusinessException('token不能为空', 422); - // } - // Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - // } - - public function logout(?string $token) - { - if ($token) { - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - // 1. 解析token查用户 - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - if ($userId) { - Cache::sRem("user_tokens:{$userId}", $token); - } - // 2. 删除meta - Cache::del("user_token_meta:{$token}"); - - // 3. 更新MySQL为失效 - \app\common\model\UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } - } - - public function blockToken(string $token) - { - if (!$token) { - throw new BusinessException('token不能为空', 422); - } - Cache::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - - // 1. 解析token查用户 - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - if ($userId) { - Cache::sRem("user_tokens:{$userId}", $token); - } - Cache::del("user_token_meta:{$token}"); - \app\common\model\UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } - - /** - * 获取用户所有活跃token及元信息 - */ - public function getUserTokens(int $userId): array - { - $tokens = Cache::sMembers("user_tokens:{$userId}") ?: []; - $result = []; - foreach ($tokens as $token) { - $meta = Cache::hGetAll("user_token_meta:{$token}"); - // 可选再查 MySQL 补充日志记录等 - $result[] = [ - 'token' => $token, - 'meta' => $meta, - ]; - } - return $result; - } - - /** - * 强制某用户所有token全部下线 - */ - public function blockAllTokensOfUser(int $userId) - { - $tokens = Cache::sMembers("user_tokens:{$userId}") ?: []; - foreach ($tokens as $token) { - $this->blockToken($token); - } - } - - /** ----------- 内部方法 ------------- **/ - - /** - * 用户验证 - */ - private function validateUser(string $account, string $password) - { - $user = User::byAccount($account)->first(); - if (!$user || !password_verify($password, $user->password)) { - throw new BusinessException('账号或密码错误', 401); - } - if ($user->status != 1) { - throw new BusinessException('账号已禁用', 403); - } - return $user; - } - - /** - * 系统验证 - */ - private function validateSystem(string $systemCode) - { - $system = Systems::where('code', $systemCode)->first(); - if (!$system) { - throw new BusinessException('系统不存在', 403); - } - return $system; - } - - /** - * 用户系统权限校验 - */ - private function validateSystemPermission(User $user, Systems $system) - { - if ($system->is_role) { - $systemIds = $user->systems->pluck('id')->toArray(); - if (!in_array($system->id, $systemIds)) { - throw new BusinessException('无该系统权限', 403); - } - } - } - - /** - * JWT payload 构建 - * 可扩展 payload 字段 - */ - private function buildJwtPayload(User $user, ?Systems $system = null): array - { - $now = time(); - $exp = $now + config('jwt.jwt_expiration'); - $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; - $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; - - return [ - 'sub' => 'user_' . $user->id, - 'aud' => $system ? $system->domain_url : '', - 'iat' => $now, - 'nbf' => $now, - 'exp' => $exp, - 'typ' => 'access', - 'uid' => $user->id, - 'role_ids' => $role_ids, - 'system_ids' => $system_ids - ]; - } - - /** - * JWT签发 - */ - private function generateAccessToken(array $payload) - { - return (new Jwt())->generateToken($payload); - } - - /** - * 刷新令牌生成 - */ - private function generateRefreshToken(int $userId) - { - $refreshToken = bin2hex(random_bytes(32)); - Cache::set("refresh_token:{$refreshToken}", $userId, config('jwt.jwt_refresh_token_expiration')); - return $refreshToken; - } - - /** - * 验证扫码令牌 - */ - private function validateQrCodeToken(string $qrCodeToken) - { - $userId = Cache::get("qr_code_token:{$qrCodeToken}"); - if (!$userId) { - throw new BusinessException('扫码令牌无效或已过期', 401); - } - return $userId; - } - - /** - * 调用飞书API获取用户信息(示例) - */ - private function getFeishuUserInfo(string $code) - { - // 实际实现需要调用飞书 API - return [ - 'user_id' => 'feishu_user_123', - 'name' => '飞书用户', - ]; - } - - /** - * 查找或创建飞书用户(示例) - */ - private function getOrCreateUser(array $feishuUserInfo) - { - $user = User::where('feishu_user_id', $feishuUserInfo['user_id'])->first(); - if (!$user) { - $user = new User(); - $user->feishu_user_id = $feishuUserInfo['user_id']; - $user->name = $feishuUserInfo['name']; - $user->status = 1; - $user->save(); - } - return $user; - } -} \ No newline at end of file diff --git a/app/common/model/Corp.php b/app/common/model/Corp.php new file mode 100644 index 0000000..52c160e --- /dev/null +++ b/app/common/model/Corp.php @@ -0,0 +1,49 @@ +feishuAppInfo($corpAbbr, $systemCcode); + $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); + return ['auth_url' => $feishu->authorize($certificate)]; + } + /** * 登录,支持多类型 */ diff --git a/app/common/service/BasicDataService.php b/app/common/service/BasicDataService.php new file mode 100644 index 0000000..e77a4e0 --- /dev/null +++ b/app/common/service/BasicDataService.php @@ -0,0 +1,150 @@ +checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) { + $this->cacheLoginTypes(); + if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) { + throw new BusinessException('无效的登录方式'); + } + } + + // 检查并缓存系统码 + if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) { + $this->cacheSystemCodes(); + if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) { + throw new BusinessException('无效的系统码'); + } + } + + // 检查并缓存公司标识 + if (!$this->checkParamInCache(self::CORP_ABBR_KEY, $corpAbbr)) { + $this->cacheCorpAbbrs(); + if (!$this->checkParamInCache(self::CORP_ABBR_KEY, $corpAbbr)) { + throw new BusinessException('无效的公司标识'); + } + } + + return true; + } + + /** + * 检查参数是否在Redis集合中 + */ + private function checkParamInCache($cacheKey, $param) + { + return Redis::sIsMember($cacheKey, $param); + } + + /** + * 缓存所有登录类型到集合 + */ + private function cacheLoginTypes() + { + $cacheKey = self::LOGIN_TYPE_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; + } + + $loginTypes = $this->getAllLoginTypes(); + if (empty($loginTypes)) { + throw new BusinessException('未获取到登录类型数据'); + } + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$loginTypes); + Redis::expire($cacheKey, 3600); // 1小时有效期 + } + + /** + * 缓存所有系统码到集合 + */ + private function cacheSystemCodes() + { + $cacheKey = self::SYSTEM_CODE_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; + } + + $systemCodes = $this->getAllSystemCodes(); + if (empty($systemCodes)) { + throw new BusinessException('未获取到系统码数据'); + } + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$systemCodes); + Redis::expire($cacheKey, 3600); // 1小时有效期 + } + + /** + * 缓存所有公司标识到集合 + */ + private function cacheCorpAbbrs() + { + $cacheKey = self::CORP_ABBR_KEY; + + // 如果集合已存在,直接返回 + if (Redis::exists($cacheKey)) { + return; + } + + $corpAbbrs = $this->getAllCorpAbbrs(); + if (empty($corpAbbrs)) { + throw new BusinessException('未获取到公司标识数据'); + } + + // 批量添加值到集合 + Redis::sAdd($cacheKey, ...$corpAbbrs); + Redis::expire($cacheKey, 3600); // 1小时有效期 + } + + /** + * 获取所有有效的登录类型 + */ + private function getAllLoginTypes() + { + return ['upwd', 'feishu', 'qrcode']; // 示例数据 + } + + /** + * 获取所有有效的系统码 + */ + private function getAllSystemCodes() + { + return Systems::pluck('code')->toArray(); + } + + /** + * 获取所有有效的公司标识 + */ + private function getAllCorpAbbrs() + { + return Corp::pluck('abbr')->toArray(); + } +} \ No newline at end of file diff --git a/app/common/traits/ResponseTrait.php b/app/common/traits/ResponseTrait.php index 227fa99..a8adc90 100644 --- a/app/common/traits/ResponseTrait.php +++ b/app/common/traits/ResponseTrait.php @@ -19,7 +19,7 @@ trait ResponseTrait public function success( $data = [], string $message = '请求成功', - int $code = 0, + int $code = 200, int $httpStatus = 200, array $header = ['Content-Type' => 'application/json'] ): Response { @@ -38,18 +38,18 @@ trait ResponseTrait */ public function error( string $message = '请求失败', - int $code = 1, + int $code = 400, int $httpStatus = 400, $data = [], array $header = ['Content-Type' => 'application/json'] ): Response { - return $this->result('error', $code, $httpStatus, $message, $data, $header); + return $this->result('fail', $code, $httpStatus, $message, $data, $header); } /** * 统一响应格式方法 * - * @param string $status 响应状态,例如 "success" 或 "error" + * @param string $status 响应状态,例如 "success" 或 "fail" * @param int $code 内部状态码 * @param int $httpStatus HTTP 状态码 * @param string $message 响应消息 diff --git a/app/common/utils/Feishu.php b/app/common/utils/Feishu.php index 5148db1..8428f1c 100644 --- a/app/common/utils/Feishu.php +++ b/app/common/utils/Feishu.php @@ -16,15 +16,15 @@ class Feishu protected $client = null; protected $options = []; protected $error = ''; - protected $gs = 'hesu'; + protected $abbr = 'hesu'; /** * 初始化 */ - public function __construct(string $appId = '', string $appSecret = '', string $gs = 'hesu') + public function __construct(string $appId = '', string $appSecret = '', string $abbr = 'hesu') { - if ($gs === 'mbaf') { + if ($abbr === 'mbaf') { $this->baseUri = 'https://open.larksuite.com/open-apis/'; } else { $this->baseUri = 'https://open.feishu.cn/open-apis/'; @@ -32,7 +32,7 @@ class Feishu $this->appId = $appId; $this->appSecret = $appSecret; - $this->gs = $gs; + $this->abbr = $abbr; $this->client = new Client([ 'base_uri' => $this->baseUri @@ -94,8 +94,8 @@ class Feishu public function authorize($certificate, $url = 'authen/v1/authorize') { $arr = [ - 'app_id' => $certificate['appId'], - 'redirect_uri' => $certificate['rUrl'], + 'app_id' => $certificate['app_id'], + 'redirect_uri' => $certificate['r_url'], 'scope' => $certificate['scope'], 'state' => $certificate['state'] ]; @@ -108,7 +108,7 @@ class Feishu */ public function tenantAccessToken() { - $tenantAccessToken = Cache::get($this->gs.'-tenantAccessToken'); + $tenantAccessToken = Cache::get($this->abbr.'-tenantAccessToken'); if (!$tenantAccessToken) { $this->options["json"] = [ "app_id" => $this->appId, @@ -117,7 +117,7 @@ class Feishu $rst = $this->requestResult('auth/v3/tenant_access_token/internal', 'POST'); if ($rst) { $tenantAccessToken = $rst['tenant_access_token']; - Cache::set($this->gs.'-tenantAccessToken', $tenantAccessToken, $rst['expire']); + Cache::set($this->abbr.'-tenantAccessToken', $tenantAccessToken, $rst['expire']); } else { throw new BusinessException('tenant_access_token 获取失败'); } diff --git a/app/common/validate/UserValidate.php b/app/common/validate/UserValidate.php index 415ee0b..7db260d 100644 --- a/app/common/validate/UserValidate.php +++ b/app/common/validate/UserValidate.php @@ -2,6 +2,7 @@ namespace app\common\validate; +use app\common\model\Corp; use app\common\model\Systems; use app\common\model\User; use think\Validate; @@ -17,7 +18,9 @@ class UserValidate extends Validate 'phone' => 'checkMobile|checkPhoneUnique', 'email' => 'email|checkEmailUnique', 'login_type' => 'require|in:upwd,feishu,qrcode', - 'system_code' => 'require|checkSystemCode' + 'system_code' => 'require|checkSystemCode', + 'corp_abbr' => 'require|checkCorpAbbr', + 'feishu_code' => 'require|string' ]; protected $message = [ @@ -40,12 +43,19 @@ class UserValidate extends Validate 'login_type.require' => '请选择登录方式', 'login_type.in' => '登录方式不支持', 'system_code.require' => '系统码不能为空', - 'system_code.checkSystemCode' => '系统码不存在或不合法' + 'system_code.checkSystemCode' => '系统码不存在或不合法', + 'corp_abbr.require' => '公司标识不能为空', + 'corp_abbr.checkCorpAbbr' => '公司标识不存在或不合法', + 'feishu_code.require' => '飞书code不能为空', + 'feishu_code.string' => '飞书code必须是字符串' ]; protected $scene = [ - 'upwd_register' => ['name', 'account', 'password', 'confirm_password', 'login_type', 'system_code'], - 'upwd_login' => ['account', 'password', 'login_type', 'system_code'], + 'upwd_register' => ['name', 'account', 'password', 'confirm_password'], + 'feishu_register' => ['feishu_code'], + 'upwd_login' => ['account' => 'require|checkAccount', 'password'], + 'feishu_auth' => ['corp_abbr', 'system_code', 'login_type'], + 'feishu_login' => ['feishu_code'] ]; /** @@ -129,4 +139,12 @@ class UserValidate extends Validate { return Systems::where('code', $value)->exists(); } + + /** + * 验证 corp_abbr 企业标识是否存在 + */ + protected function checkCorpAbbr($value, $rule, $data, $field) + { + return Corp::where('abbr', $value)->exists(); + } } \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index e509b32..2ea3432 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -6,79 +6,15 @@ use support\Request; class IndexController { + public function index(Request $request) { - return ' - - - - - Code - - - -
- -
-

🌞 hi~

-

    愿你代码如诗,逻辑清晰,Bug 自动远离!

-

        记得适当休息,喝杯好茶或咖啡,为灵感续航 ☕

-

            加油,永远相信美好的事情即将发生。 ✨

-
- - - - -'; + return htmlpage(); } public function view(Request $request) { - return view('index/view', ['name' => 'webman']); + return view('index/qrcode', ['name' => 'webman']); } public function json(Request $request) @@ -86,4 +22,4 @@ class IndexController return json(['code' => 0, 'msg' => 'ok']); } -} +} \ No newline at end of file diff --git a/app/functions.php b/app/functions.php index c0c16fa..14d5e4a 100644 --- a/app/functions.php +++ b/app/functions.php @@ -4,6 +4,44 @@ */ use support\exception\BusinessException; + +/** + * 标准化账号参数 + * + * @param string $account 用户输入的账号(可能是手机号或邮箱) + * @return array 返回 ['phone' => $account] 或 ['email' => $account] + */ +function normalizeAccount(string $account): array +{ + // 使用FILTER_VALIDATE_EMAIL验证邮箱格式 + if (filter_var($account, FILTER_VALIDATE_EMAIL)) { + return ['email' => $account]; + } + + // 清理手机号中的非数字字符 + $cleanPhone = preg_replace('/[^0-9]/', '', $account); + + // 验证手机号格式(简单版:11位数字) + if (preg_match('/^1[3-9]\d{9}$/', $cleanPhone)) { + return ['phone' => $cleanPhone]; + } + + // 如果既不是邮箱也不是手机号,根据业务需求处理 + // 这里默认按手机号处理(可根据实际需求调整) + return ['phone' => $cleanPhone]; +} + +/** + * 公共html页面 + */ +function htmlpage() +{ + static $html; + if (!$html) { + $html = file_get_contents(public_path('mzl.html')); + } + return $html; +} /** * 获取配置 diff --git a/app/middleware/AuthCheck.php b/app/middleware/AuthCheck.php new file mode 100644 index 0000000..3e6eb8a --- /dev/null +++ b/app/middleware/AuthCheck.php @@ -0,0 +1,46 @@ +controller); + $noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? []; + + if (in_array($request->action, $noNeedLogin)) { + // 白名单 + return $handler($request); + } else { + try { + $authHeader = $request->header('Authorization'); + if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { + $body = [ + 'status' => 'fail', + 'code' => 401, + 'message' => '未授权', + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + $token = $matches[1]; + $request->user = (new Jwt())->verifyToken($token); + } catch (\Throwable $e) { + $body = [ + 'status' => 'fail', + 'code' => 401, + 'message' => $e->getMessage(), + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + return $handler($request); + } + } +} \ No newline at end of file diff --git a/app/middleware/BasicData.php b/app/middleware/BasicData.php new file mode 100644 index 0000000..472456e --- /dev/null +++ b/app/middleware/BasicData.php @@ -0,0 +1,42 @@ +controller); + $noNeedHeader = $controller->getDefaultProperties()['noNeedHeader'] ?? []; + + if (in_array($request->action, $noNeedHeader) || in_array($request->path(), config('whitelist.basi_cdata'))) { + // 白名单 + return $handler($request); + } else { + try { + $corpAbbr = $request->header('Fdesk-Corp-Abbr'); + $systemCode = $request->header('Fdesk-System-Code'); + $loginType = $request->header('Fdesk-Login-Type'); + $basicDataService = new BasicDataService(); + if ($basicDataService->checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)) { + $request->login_type = $loginType; + $request->system_code = $systemCode; + $request->corp_abbr = $corpAbbr; + $request->basic_data = [ + 'login_type' => $loginType, + 'system_code' => $systemCode, + 'corp_abbr' => $corpAbbr + ]; + } + return $handler($request); + } catch (\Throwable $th) { + throw $th; + } + } + } +} \ No newline at end of file diff --git a/app/view/index/qrcode.html b/app/view/index/qrcode.html new file mode 100644 index 0000000..26d1918 --- /dev/null +++ b/app/view/index/qrcode.html @@ -0,0 +1,46 @@ + + + + + 飞书扫码登录示例 + + + + + +
+ + + + diff --git a/config/middleware.php b/config/middleware.php index 01e8367..cf0335a 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -12,8 +12,10 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ + return [ '' => [ - app\middleware\AccessControl::class - ], + \app\middleware\AccessControl::class, + \app\middleware\BasicData::class + ] ]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index 6d7ce98..db2c831 100644 --- a/config/route.php +++ b/config/route.php @@ -28,8 +28,14 @@ use Webman\Route; Route::group('/common', function () { Route::group('/auth', function () { - Route::post('/login', [app\common\controller\AuthController::class, 'login']); - Route::post('/register', [app\common\controller\AuthController::class, 'register']); + Route::any('/login', [app\common\controller\AuthController::class, 'login']); + Route::any('/register', [app\common\controller\AuthController::class, 'register']); + Route::any('/feishu/scope', [app\common\controller\AuthController::class, 'feishuScope']); + + + + // 调试 + Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); }); }); diff --git a/config/whitelist.php b/config/whitelist.php new file mode 100644 index 0000000..8071847 --- /dev/null +++ b/config/whitelist.php @@ -0,0 +1,9 @@ + [ + '/', + '/index/index', + // '/index/view' + ] +]; \ No newline at end of file diff --git a/public/mzl.html b/public/mzl.html new file mode 100644 index 0000000..9eb4e4b --- /dev/null +++ b/public/mzl.html @@ -0,0 +1,64 @@ + + + + + Code + + + +
+ +
+

🌞 hi~

+

    愿你代码如诗,逻辑清晰,Bug 自动远离!

+

        记得适当休息,喝杯好茶或咖啡,为灵感续航 ☕

+

            加油,永远相信美好的事情即将发生。 ✨

+
+ + + + \ No newline at end of file diff --git a/webman b/webman old mode 100644 new mode 100755 From c325794bc1eebf21be8fda34fa38dfb8b2068ab2 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 10 Jul 2025 10:08:30 +0800 Subject: [PATCH 027/120] =?UTF-8?q?=E8=8E=B7=E5=8F=96userid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/controller/ConversationController.php | 4 ++-- app/ticket/controller/TicketController.php | 6 +++--- config/middleware.php | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/ticket/controller/ConversationController.php b/app/ticket/controller/ConversationController.php index 0e1b7ec..86d4fd7 100644 --- a/app/ticket/controller/ConversationController.php +++ b/app/ticket/controller/ConversationController.php @@ -23,7 +23,7 @@ class ConversationController extends BaseController public function add(Request $request) { try { - $request->setPost('user_id', 1); + $request->setPost('user_id', $request->user->uid); return (new ConversationService())->add($request->all()) ? $this->success() : $this->error(); @@ -34,7 +34,7 @@ class ConversationController extends BaseController public function edit(Request $request) { try { - $request->setPost('user_id', 1); + $request->setPost('user_id', $request->user->uid); $result = (new ConversationService())->edit($request->all()); return $result ? $this->success($result) diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php index 782dc74..95dc539 100644 --- a/app/ticket/controller/TicketController.php +++ b/app/ticket/controller/TicketController.php @@ -20,7 +20,7 @@ class TicketController extends BaseController public function add(Request $request) { try { - $request->setPost('user_id', 1); + $request->setPost('user_id', $request->user->uid); return (new TicketService())->add($request->all()) ? $this->success() @@ -33,7 +33,7 @@ class TicketController extends BaseController public function edit(Request $request) { try { - $request->setPost('user_id', 1); + $request->setPost('user_id', $request->user->uid); $result = (new TicketService())->edit($request->all()); return $result ? $this->success($result) @@ -45,7 +45,7 @@ class TicketController extends BaseController public function status(Request $request) { try { - $request->setPost('user_id', 1); + $request->setPost('user_id', $request->user->uid); $result = (new TicketService())->status($request->all()); return $result ? $this->success($result) diff --git a/config/middleware.php b/config/middleware.php index cf0335a..ae0ad6e 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -16,6 +16,7 @@ return [ '' => [ \app\middleware\AccessControl::class, - \app\middleware\BasicData::class + \app\middleware\BasicData::class, + \app\middleware\AuthCheck::class ] ]; \ No newline at end of file From 43d673ad9cce20a33d4ec7ef51f43fc67a56d114 Mon Sep 17 00:00:00 2001 From: mzl Date: Thu, 10 Jul 2025 14:06:01 +0800 Subject: [PATCH 028/120] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/AuthController_bak.php | 83 ------------ app/admin/controller/RoleController.php | 90 +++++++++++++ app/common/controller/AuthController.php | 20 +-- app/common/controller/BaseController.php | 5 +- app/common/controller/UserController.php | 25 ++++ app/common/logic/PermLogic.php | 78 +++++++++++ app/common/logic/RoleLogic.php | 93 ++++++++++++++ app/common/logic/UserLogic.php | 27 ++++ app/common/model/Role.php | 2 + app/common/service/PermService.php | 83 +++--------- app/common/service/PermService_bak.php | 109 ++++++++++++++++ app/common/service/RoleService.php | 55 ++++++++ app/common/service/UserService.php | 38 ++++-- app/common/traits/DataTransformer.php | 135 ++++++++++++++++++++ app/common/validate/RoleValidate.php | 31 +++++ app/controller/IndexController.php | 2 + app/functions.php | 8 ++ app/middleware/AuthCheck.php | 2 +- config/middleware.php | 3 +- config/route.php | 18 ++- 20 files changed, 738 insertions(+), 169 deletions(-) delete mode 100644 app/admin/controller/AuthController_bak.php create mode 100644 app/admin/controller/RoleController.php create mode 100644 app/common/controller/UserController.php create mode 100644 app/common/logic/PermLogic.php create mode 100644 app/common/logic/RoleLogic.php create mode 100644 app/common/logic/UserLogic.php create mode 100644 app/common/service/PermService_bak.php create mode 100644 app/common/service/RoleService.php create mode 100644 app/common/traits/DataTransformer.php create mode 100644 app/common/validate/RoleValidate.php diff --git a/app/admin/controller/AuthController_bak.php b/app/admin/controller/AuthController_bak.php deleted file mode 100644 index f6feeb4..0000000 --- a/app/admin/controller/AuthController_bak.php +++ /dev/null @@ -1,83 +0,0 @@ -authServer = $auth; - $this->resourceServer = $res; - } - - // 浏览器授权页 - public function authorize(ServerRequestInterface $request) - { - // 如果未登录,跳转登录页(自行实现) - // 登录后,确认授权 -> issue auth code -> 重定向回 client - - // …未登录重定向到登录页,登录后调用: - // $authRequest = $this->authServer->validateAuthorizationRequest($request); - // …用户同意授权… - // return $this->authServer->completeAuthorizationRequest($authRequest, new Psr7Response()); - } - - // 授权码换 Token - public function token(ServerRequestInterface $request) - { - return $this->authServer->respondToAccessTokenRequest($request, new Psr7Response()); - } - - // 获取用户信息 - public function userinfo(ServerRequestInterface $request) - { - $psr = $this->resourceServer->validateAuthenticatedRequest($request); - $userId = $psr->getAttribute('oauth_user_id'); - // 根据 $userId 查询用户表返回 - // return json(['id' => $userId, 'username' => 'demo']); - - // 用 Webman 的 support\Response 返回 JSON 更方便 - return json([ - 'id' => $userId, - 'username' => User::find($userId)->username, - ]); - } - - - public function info(Request $request) - { - $userId = $request->session()->get('user_id'); - if (!$userId) { - return json(['code'=>401,'msg'=>'未登录']); - } - - $user = User::with('roles')->find($userId); - - return json([ - 'code' => 0, - 'data' => [ - 'user' => [ - 'id' => $user->id, - 'username' => $user->username, - 'roles' => $user->roles->pluck('name')->all(), - ], - 'menus' => AuthService::getMenuTree($userId), - 'permissions' => AuthService::getPermissionCodes($userId), - ], - 'msg' => 'ok', - ]); - } -} diff --git a/app/admin/controller/RoleController.php b/app/admin/controller/RoleController.php new file mode 100644 index 0000000..232b38e --- /dev/null +++ b/app/admin/controller/RoleController.php @@ -0,0 +1,90 @@ +all(); + $result = (new RoleService())->getRoleList($params); + return $this->success($this->transformData($result), '角色列表获取成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage(), 1, $e->getCode()); + } + } + + /** + * 获取角色详情 + */ + public function detail(Request $request) + { + try { + $roleId = $request->get('role_id'); + if (!$roleId) { + return $this->error('缺少角色ID', 422); + } + $result = (new RoleService())->getRoleDetail($roleId); + return $this->success($result, '角色详情获取成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage(), 1, $e->getCode()); + } + } + + /** + * 增加角色 + */ + public function create(Request $request) + { + try { + $data = $this->validate(new RoleValidate(), $request->post(), 'role_create'); + $result = (new RoleService())->createRole($data); + return $this->success($result, '角色创建成功'); + } catch (\think\exception\ValidateException $e) { + return $this->error($e->getError(), 422); + } catch (\Throwable $e) { + return $this->error($e->getMessage(), 1, $e->getCode()); + } + } + + /** + * 修改角色 + */ + public function update(Request $request) + { + try { + $data = $this->validate(new RoleValidate(), $request->post(), 'role_update'); + $result = (new RoleService())->updateRole($data); + return $this->success($result, '角色更新成功'); + } catch (\think\exception\ValidateException $e) { + return $this->error($e->getError(), 422); + } catch (\Throwable $e) { + return $this->error($e->getMessage(), 1, $e->getCode()); + } + } + + /** + * 删除角色 + */ + public function delete(Request $request) + { + try { + $roleId = $request->get('role_id'); + if (!$roleId) { + return $this->error('缺少角色ID', 422); + } + (new RoleService())->deleteRole($roleId); + return $this->success([], '角色删除成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage(), 1, $e->getCode()); + } + } +} diff --git a/app/common/controller/AuthController.php b/app/common/controller/AuthController.php index 4129ee0..1375f84 100644 --- a/app/common/controller/AuthController.php +++ b/app/common/controller/AuthController.php @@ -14,17 +14,19 @@ class AuthController extends BaseController */ protected $noNeedHeader = ['feishuAuth']; + protected $noNeedLogin = ['feishuScope', 'login', 'register']; + // 调试 - public function feishuAuth(Request $request) - { - try { - $code = $request->all(); - return $this->success($code); - } catch (\Throwable $e) { - return $this->error($e->getMessage()); - } - } + // public function feishuAuth(Request $request) + // { + // try { + // $code = $request->all(); + // return $this->success($code); + // } catch (\Throwable $e) { + // return $this->error($e->getMessage()); + // } + // } public function feishuScope(Request $request) { diff --git a/app/common/controller/BaseController.php b/app/common/controller/BaseController.php index 0fefb82..2dbf636 100644 --- a/app/common/controller/BaseController.php +++ b/app/common/controller/BaseController.php @@ -4,12 +4,13 @@ namespace app\common\controller; use support\exception\BusinessException; use app\common\traits\ResponseTrait; +use app\common\traits\DataTransformer; use think\Validate; use support\Request; -abstract class BaseController +class BaseController { - use ResponseTrait; + use ResponseTrait, DataTransformer; protected mixed $validator = null; diff --git a/app/common/controller/UserController.php b/app/common/controller/UserController.php new file mode 100644 index 0000000..efab4a1 --- /dev/null +++ b/app/common/controller/UserController.php @@ -0,0 +1,25 @@ +user->uid; + $result = (new UserService())->getUserInfo($uid); + return $this->success($result, '获取用户信息'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + +} \ No newline at end of file diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php new file mode 100644 index 0000000..d8d917c --- /dev/null +++ b/app/common/logic/PermLogic.php @@ -0,0 +1,78 @@ +find($userId); + if (!$user) { + return collect(); + } + + $all = collect(); + foreach ($user->roles as $role) { + $all = $all->merge($role->resources); + } + $all = $all->merge($user->resources); + return $all->unique('id')->values(); + } + + /** + * 构建目录/菜单树,节点包含权限与打开方式 + */ + public function buildTree($resources, int $parentId = 0): array + { + $tree = []; + foreach ($resources as $node) { + if ($node->parent_id !== $parentId) continue; + + // 子节点 + $children = $this->buildTree($resources, $node->id); + + // 本级权限列表 + $authList = $resources + ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) + ->map(fn($r) => [ + 'id' => $r->id, + 'title' => $r->name, + 'auth_mark' => $r->code, + ])->values()->all(); + + $tree[] = [ + 'id' => $node->id, + 'name' => $node->name, + 'path' => $node->url, + 'meta' => [ + 'title' => $node->name, + 'icon' => $node->icon, + 'keepAlive' => true, + 'authList' => $authList, + 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' + ], + 'children' => $children, + ]; + } + return $tree; + } + + /** + * 获取用户所有权限code + */ + public function getPermissionCodes(int $userId): array + { + $resources = $this->getUserResources($userId); + return $resources + ->filter(fn($res) => $res->type === 'permission') + ->pluck('code') + ->unique() + ->values() + ->all(); + } +} \ No newline at end of file diff --git a/app/common/logic/RoleLogic.php b/app/common/logic/RoleLogic.php new file mode 100644 index 0000000..5cb3ae7 --- /dev/null +++ b/app/common/logic/RoleLogic.php @@ -0,0 +1,93 @@ +where('system_id', $params['system_id']); + } + if (isset($params['status'])) { + $query->where('status', $params['status']); + } + + // 添加分页 + $roles = $query->paginate($params['per_page'] ?? 15); + + return $roles; + } + + /** + * 获取角色详情 + */ + public function getRoleDetail(int $roleId) + { + $role = Role::find($roleId); + if (!$role) { + throw new BusinessException('角色不存在', 404); + } + + return $role; + } + + /** + * 创建角色 + */ + public function createRole(array $data) + { + $role = new Role(); + $role->fill($data); // 根据字段填充数据 + try { + $role->save(); + } catch (\Throwable $e) { + Log::error('创建角色失败:'.$e->getMessage()); + throw new BusinessException('创建角色失败', 500); + } + + return $role; + } + + /** + * 更新角色 + */ + public function updateRole(array $data) + { + $role = Role::find($data['id']); + if (!$role) { + throw new BusinessException('角色不存在', 404); + } + + $role->update($data); // 更新字段 + return $role; + } + + /** + * 删除角色 + */ + public function deleteRole(int $roleId) + { + $role = Role::find($roleId); + if (!$role) { + throw new BusinessException('角色不存在', 404); + } + + try { + $role->delete(); + } catch (\Throwable $e) { + Log::error('删除角色失败:'.$e->getMessage()); + throw new BusinessException('删除角色失败', 500); + } + } +} diff --git a/app/common/logic/UserLogic.php b/app/common/logic/UserLogic.php new file mode 100644 index 0000000..1d6dc93 --- /dev/null +++ b/app/common/logic/UserLogic.php @@ -0,0 +1,27 @@ +status != 1) { + throw new BusinessException('账号已禁用', 403); + } + + return $user; + } +} \ No newline at end of file diff --git a/app/common/model/Role.php b/app/common/model/Role.php index 3f51b37..ea84200 100644 --- a/app/common/model/Role.php +++ b/app/common/model/Role.php @@ -41,6 +41,8 @@ class Role extends Model * @var bool */ public $timestamps = true; + + protected $dateFormat = 'U'; // 角色下所有用户 public function users() diff --git a/app/common/service/PermService.php b/app/common/service/PermService.php index 076a4bd..a629b23 100644 --- a/app/common/service/PermService.php +++ b/app/common/service/PermService.php @@ -2,11 +2,17 @@ namespace app\common\service; -use app\common\model\User; +use app\common\logic\PermLogic; use support\Cache; class PermService { + protected $logic; + + public function __construct() + { + $this->logic = (new PermLogic()); + } // 用户资源缓存key protected static function cacheKey(int $userId): string @@ -17,7 +23,7 @@ class PermService /** * 获取用户所有资源(合并角色+直接分配),结果缓存 */ - public static function getUserResources(int $userId) + public function getUserResources(int $userId) { $cacheKey = self::cacheKey($userId); $cached = Cache::get($cacheKey); @@ -25,84 +31,37 @@ class PermService return $cached; } - $user = User::with(['roles.resources', 'resources'])->find($userId); - if (!$user) { - return collect(); - } + // 调用 Logic 层获取资源 + $resources = $this->logic->getUserResources($userId); + + // 缓存结果 + Cache::set($cacheKey, $resources, 3600); - $all = collect(); - foreach ($user->roles as $role) { - $all = $all->merge($role->resources); - } - $all = $all->merge($user->resources); - $unique = $all->unique('id')->values(); - - Cache::set($cacheKey, $unique, 3600); - return $unique; + return $resources; } /** * 构建目录/菜单树,节点包含权限与打开方式 */ - public static function getMenuTree(int $userId): array + public function getMenuTree(int $userId): array { - $resources = self::getUserResources($userId); - $menus = $resources - ->filter(fn($r) => in_array($r->type, ['directory','menu'])) - ->values(); - return self::buildTree($menus, $resources); - } - - protected static function buildTree($nodes, $allResources, int $parentId = 0): array - { - $tree = []; - foreach ($nodes as $node) { - if ($node->parent_id !== $parentId) continue; - - $children = self::buildTree($nodes, $allResources, $node->id); - - $authList = $allResources - ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) - ->map(fn($r) => [ - 'id' => $r->id, - 'title' => $r->name, - 'auth_mark' => $r->code, - ])->values()->all(); - - $tree[] = [ - 'id' => $node->id, - 'name' => $node->name, - 'path' => $node->url, - 'meta' => [ - 'title' => $node->name, - 'icon' => $node->icon, - 'keepAlive' => true, - 'authList' => $authList, - 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' - ], - 'children' => $children, - ]; - } - return $tree; + // 调用 Logic 层处理菜单构建 + $resources = $this->getUserResources($userId); + return $this->logic->buildTree($resources); } /** * 获取用户所有权限code */ - public static function getPermissionCodes(int $userId): array + public function getPermissionCodes(int $userId): array { - return self::getUserResources($userId) - ->filter(fn($res) => $res->type === 'permission') - ->pluck('code') - ->unique() - ->values() - ->all(); + return $this->logic->getPermissionCodes($userId); } /** * 清除资源相关缓存 */ - public static function clearCache(int $userId): void + public function clearCache(int $userId): void { Cache::delete(self::cacheKey($userId)); } diff --git a/app/common/service/PermService_bak.php b/app/common/service/PermService_bak.php new file mode 100644 index 0000000..a2dcc27 --- /dev/null +++ b/app/common/service/PermService_bak.php @@ -0,0 +1,109 @@ +find($userId); + if (!$user) { + return collect(); + } + + $all = collect(); + foreach ($user->roles as $role) { + $all = $all->merge($role->resources); + } + $all = $all->merge($user->resources); + $unique = $all->unique('id')->values(); + + Cache::set($cacheKey, $unique, 3600); + return $unique; + } + + /** + * 构建目录/菜单树,节点包含权限与打开方式 + */ + public static function getMenuTree(int $userId): array + { + $resources = self::getUserResources($userId); + $menus = $resources + ->filter(fn($r) => in_array($r->type, ['directory','menu'])) + ->values(); + return self::buildTree($menus, $resources); + } + + protected static function buildTree($nodes, $allResources, int $parentId = 0): array + { + $tree = []; + foreach ($nodes as $node) { + if ($node->parent_id !== $parentId) continue; + + $children = self::buildTree($nodes, $allResources, $node->id); + + $authList = $allResources + ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) + ->map(fn($r) => [ + 'id' => $r->id, + 'title' => $r->name, + 'auth_mark' => $r->code, + ])->values()->all(); + + $tree[] = [ + 'id' => $node->id, + 'name' => $node->name, + 'path' => $node->url, + 'meta' => [ + 'title' => $node->name, + 'icon' => $node->icon, + 'keepAlive' => true, + 'authList' => $authList, + 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' + ], + 'children' => $children, + ]; + } + return $tree; + } + + /** + * 获取用户所有权限code + */ + public static function getPermissionCodes(int $userId): array + { + return self::getUserResources($userId) + ->filter(fn($res) => $res->type === 'permission') + ->pluck('code') + ->unique() + ->values() + ->all(); + } + + /** + * 清除资源相关缓存 + */ + public static function clearCache(int $userId): void + { + Cache::delete(self::cacheKey($userId)); + } +} \ No newline at end of file diff --git a/app/common/service/RoleService.php b/app/common/service/RoleService.php new file mode 100644 index 0000000..24c14cf --- /dev/null +++ b/app/common/service/RoleService.php @@ -0,0 +1,55 @@ +roleLogic = (new RoleLogic()); + } + + /** + * 获取角色列表 + */ + public function getRoleList(array $params) + { + return $this->roleLogic->getRoleList($params); + } + + /** + * 获取角色详情 + */ + public function getRoleDetail(int $roleId) + { + return $this->roleLogic->getRoleDetail($roleId); + } + + /** + * 创建角色 + */ + public function createRole(array $data) + { + return $this->roleLogic->createRole($data); + } + + /** + * 更新角色 + */ + public function updateRole(array $data) + { + return $this->roleLogic->updateRole($data); + } + + /** + * 删除角色 + */ + public function deleteRole(int $roleId) + { + return $this->roleLogic->deleteRole($roleId); + } +} diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 99b80d5..98375bc 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -2,19 +2,39 @@ namespace app\common\service; -use app\model\User; +use app\common\logic\UserLogic; class UserService { - public static function getInfo($userId) + + protected $logic; + + public function __construct() { - $user = User::with('roles')->find($userId); - if (!$user) return null; + $this->logic = (new UserLogic()); + } + + /** + * 获取用户信息(包括菜单和权限) + */ + public function getUserInfo(int $uid) + { + $user = $this->logic->userInfo($uid); + $permService = new PermService(); + $menuTree = $permService->getMenuTree($uid); + $permissionCodes = $permService->getPermissionCodes($uid); return [ - 'id' => $user->id, - 'username' => $user->username, - 'roles' => $user->roles->pluck('name')->toArray(), - // 可扩展其他字段 + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + 'avatar' => $user->avatar, + 'phone' => $user->phone, + 'email' => $user->email, + 'status' => $user->status, + 'roles' => $user->roles->pluck('name') + ], + 'menu' => $menuTree, + 'permissions' => $permissionCodes ]; } -} +} \ No newline at end of file diff --git a/app/common/traits/DataTransformer.php b/app/common/traits/DataTransformer.php new file mode 100644 index 0000000..42faf81 --- /dev/null +++ b/app/common/traits/DataTransformer.php @@ -0,0 +1,135 @@ + $field; + + if ($rawData instanceof LengthAwarePaginator) { + $formatted = $this->formatPaginated($rawData, $postProcessor, $fieldFormatter); + } elseif ($rawData instanceof Model) { + $formatted = $this->formatModel($rawData, $fieldFormatter); + } elseif ($rawData instanceof Collection) { + $formatted = $this->formatCollection($rawData, $fieldFormatter); + } elseif (is_array($rawData)) { + $formatted = $this->formatArray($rawData, $fieldFormatter); + } else { + $formatted = (array) $rawData; + } + + if (is_callable($postProcessor)) { + $formatted = $postProcessor($formatted); + } + + return $formatted; + } catch (\Throwable $e) { + return [ + 'error' => true, + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + ]; + } + } + + /** + * 格式化分页数据:将每条记录也进行 transformData 递归处理 + * + * @param LengthAwarePaginator $paginator + * @param callable|null $postProcessor + * @param callable $fieldFormatter + * @return array + */ + protected function formatPaginated( + LengthAwarePaginator $paginator, + ?callable $postProcessor, + callable $fieldFormatter + ): array { + $list = $paginator->getCollection()->map(function ($item) use ($postProcessor, $fieldFormatter) { + return $this->transformData($item, $postProcessor, $fieldFormatter); + })->toArray(); + + return [ + 'records' => $list, + 'total' => $paginator->total(), + 'current' => $paginator->currentPage(), + 'size' => $paginator->perPage() + ]; + } + + /** + * 格式化 Eloquent 模型:递归嵌套关联 + * + * @param Model $model + * @param callable $fieldFormatter + * @return array + */ + protected function formatModel(Model $model, callable $fieldFormatter): array + { + $formatted = []; + + // 主表字段 + foreach ($model->getAttributes() as $key => $value) { + $formatted[$fieldFormatter($key)] = $value; + } + + // 关联数据,递归嵌套 + foreach ($model->getRelations() as $relation => $relationData) { + if ($relationData instanceof Model) { + // 单个关联模型 + $formatted[$relation] = $this->formatModel($relationData, $fieldFormatter); + } elseif ($relationData instanceof Collection) { + // 关联集合 + $formatted[$relation] = $relationData + ->map(fn($item) => $this->transformData($item, null, $fieldFormatter)) + ->toArray(); + } + } + + return $formatted; + } + + /** + * 格式化集合 + * + * @param Collection $collection + * @param callable $fieldFormatter + * @return array + */ + protected function formatCollection(Collection $collection, callable $fieldFormatter): array + { + return $collection->map(fn($item) => $this->transformData($item, null, $fieldFormatter)) + ->toArray(); + } + + /** + * 格式化普通数组 + * + * @param array $array + * @param callable $fieldFormatter + * @return array + */ + protected function formatArray(array $array, callable $fieldFormatter): array + { + return array_map(fn($item) => $this->transformData($item, null, $fieldFormatter), $array); + } +} \ No newline at end of file diff --git a/app/common/validate/RoleValidate.php b/app/common/validate/RoleValidate.php new file mode 100644 index 0000000..bf51cf9 --- /dev/null +++ b/app/common/validate/RoleValidate.php @@ -0,0 +1,31 @@ + 'require|integer', + 'system_id' => 'require|integer', + 'name' => 'require|max:64', + 'desc' => 'max:128', + 'status' => 'require|in:0,1', + ]; + + protected $message = [ + 'id.require' => '角色ID不能为空', + 'system_id.require' => '系统ID不能为空', + 'name.require' => '角色名称不能为空', + 'name.max' => '角色名称不能超过64个字符', + 'desc.max' => '描述不能超过128个字符', + 'status.require' => '状态不能为空', + 'status.in' => '状态值必须为0或1', + ]; + + protected $scene = [ + 'role_create' => ['system_id', 'name', 'desc', 'status'], + 'role_update' => ['id', 'system_id', 'name', 'desc', 'status'], + ]; +} diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 2ea3432..988928f 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -6,6 +6,8 @@ use support\Request; class IndexController { + + protected $noNeedLogin = ['index']; public function index(Request $request) { diff --git a/app/functions.php b/app/functions.php index 14d5e4a..d0682c9 100644 --- a/app/functions.php +++ b/app/functions.php @@ -61,4 +61,12 @@ function getConfig($filePath = null) return []; } return $config; +} + +/* + * 调试用 + */ +function dpr(\Throwable $e) +{ + var_dump("=== === 报错打印 === ===", "文件:".$e->getFile(), "行数:".$e->getLine(), "详情:".$e->getMessage(), "=== === 报错打印 === ==="); } \ No newline at end of file diff --git a/app/middleware/AuthCheck.php b/app/middleware/AuthCheck.php index 3e6eb8a..c085901 100644 --- a/app/middleware/AuthCheck.php +++ b/app/middleware/AuthCheck.php @@ -19,7 +19,7 @@ class AuthCheck implements MiddlewareInterface return $handler($request); } else { try { - $authHeader = $request->header('Authorization'); + $authHeader = $request->header('Fdesk-Auth-Token'); if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { $body = [ 'status' => 'fail', diff --git a/config/middleware.php b/config/middleware.php index cf0335a..ae0ad6e 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -16,6 +16,7 @@ return [ '' => [ \app\middleware\AccessControl::class, - \app\middleware\BasicData::class + \app\middleware\BasicData::class, + \app\middleware\AuthCheck::class ] ]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index db2c831..a6c3a4d 100644 --- a/config/route.php +++ b/config/route.php @@ -34,8 +34,22 @@ Route::group('/common', function () { - // 调试 - Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); + // // 调试 + // Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); + }); + + Route::group('/user', function () { + Route::any('/info', [app\common\controller\UserController::class, 'info']); + }); +}); + +Route::group('/admin', function () { + Route::group('/role', function () { + Route::any('/list', [app\admin\controller\RoleController::class, 'list']); + Route::any('/detail', [app\admin\controller\RoleController::class, 'detail']); + Route::any('/create', [app\admin\controller\RoleController::class, 'create']); + Route::any('/update', [app\admin\controller\RoleController::class, 'update']); + Route::any('/delete', [app\admin\controller\RoleController::class, 'delete']); }); }); From 3a72f8ec0af0dad2e1f5bfc5a7c1686ef07e3ab9 Mon Sep 17 00:00:00 2001 From: mzl Date: Thu, 10 Jul 2025 14:17:50 +0800 Subject: [PATCH 029/120] Merge branch 'develop' into feature/mzl_dev # Conflicts: # app/common/controller/AuthController.php # app/controller/IndexController.php # app/functions.php # app/middleware/AuthCheck.php # config/middleware.php # config/route.php From a45d586f07d58ee9f04c2605523e540962838947 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 10 Jul 2025 14:27:56 +0800 Subject: [PATCH 030/120] crm --- app/crm/controller/FollowController.php | 22 ++++++++++++---- app/crm/model/Customer.php | 2 +- app/crm/model/CustomerHis.php | 2 +- app/crm/model/Follow.php | 13 +-------- app/crm/model/Tag.php | 2 +- app/crm/model/TagGroup.php | 2 +- app/crm/service/FollowService.php | 35 ++++++++++++++++--------- app/crm/validate/FollowValidate.php | 2 +- config/route.php | 9 +++++++ resource/translations/ar/messages.php | 31 ++++++++++++++++++++++ resource/translations/en/messages.php | 31 ++++++++++++++++++++++ resource/translations/ru/messages.php | 31 ++++++++++++++++++++++ resource/translations/zh/messages.php | 31 ++++++++++++++++++++++ 13 files changed, 179 insertions(+), 34 deletions(-) diff --git a/app/crm/controller/FollowController.php b/app/crm/controller/FollowController.php index 7f08e7d..7ee34a4 100644 --- a/app/crm/controller/FollowController.php +++ b/app/crm/controller/FollowController.php @@ -39,9 +39,9 @@ class FollowController extends BaseController public function list(Request $request) { try { - $data = $this->validate(new FollowValidate(), $request->get(), 'upwd_list'); - $result = (new FollowService())->list($request->all()); - return $this->success($this->page($result)); + $data = $this->validate(new FollowValidate(), $request->get(), 'list'); + $result = (new FollowService())->list($request->all(),$request->user->uid); + return $this->success($result); } catch (\Throwable $e) { return $this->error($e->getMessage()); } @@ -69,7 +69,7 @@ class FollowController extends BaseController return $this->error('记录不存在'); } - $res = Follow::getFollow($res); + $res = FollowService::getFollow($res); return $this->success($res); } @@ -96,7 +96,7 @@ class FollowController extends BaseController } $data = [ - 'user_id'=>$request->user['id'], + 'user_id'=>$request->user->uid, 'customer_id'=>$customer_id, 'type'=>$type, 'picture'=>$picture, @@ -117,6 +117,18 @@ class FollowController extends BaseController $customer->warn = 0; $customer->save(); + $follow_next = Util::getParam($request, 'follow_next', 0); + $follow_remark = Util::getParam($request, 'follow_remark', 0); + if($res && (!empty($follow_next))){ + $data = [ + 'follow_next' => $follow_next, + 'follow_remark' => $follow_remark, + 'warn' => 0, + ]; + $res = Customer::where('id', $customer_id) + ->update($data); + } + if ($res) { return $this->success($res,trans('save_success')); } else { diff --git a/app/crm/model/Customer.php b/app/crm/model/Customer.php index 7db2f1e..c03d4ac 100644 --- a/app/crm/model/Customer.php +++ b/app/crm/model/Customer.php @@ -17,7 +17,7 @@ class Customer extends Model * * @var string */ - protected $table = 'customer'; + protected $table = 'crm_customer'; /** * The primary key associated with the table. diff --git a/app/crm/model/CustomerHis.php b/app/crm/model/CustomerHis.php index b7039dd..65d7de6 100644 --- a/app/crm/model/CustomerHis.php +++ b/app/crm/model/CustomerHis.php @@ -17,7 +17,7 @@ class CustomerHis extends Model * * @var string */ - protected $table = 'customer_his'; + protected $table = 'crm_customer_his'; /** * The primary key associated with the table. diff --git a/app/crm/model/Follow.php b/app/crm/model/Follow.php index e44c08b..778b669 100644 --- a/app/crm/model/Follow.php +++ b/app/crm/model/Follow.php @@ -16,7 +16,7 @@ class Follow extends Model * * @var string */ - protected $table = 'follow'; + protected $table = 'crm_follow'; /** * The primary key associated with the table. @@ -47,15 +47,4 @@ class Follow extends Model // 'updated_at' => 'timestamp', // 指定序列化格式 // 'created_at' => 'timestamp', // 指定序列化格式 ]; - - public static function getFollow($follow,$domain = 'https://vehicle-album.oss-cn-beijing.aliyuncs.com') - { - $follow['type_txt'] = Constants::FOLLOW[$follow['type']]; - $follow['firstFollowtime'] = $follow['follow_time']; - $ossservice = new OssService(); - $oss_picture = $ossservice->generatePrivateUrl($follow['picture'],86400); - $follow['oss_picture'] = $oss_picture; -// $follow = Util::formatDate($follow); - return $follow; - } } \ No newline at end of file diff --git a/app/crm/model/Tag.php b/app/crm/model/Tag.php index 4b00471..3e7ec25 100644 --- a/app/crm/model/Tag.php +++ b/app/crm/model/Tag.php @@ -17,7 +17,7 @@ class Tag extends Model * * @var string */ - protected $table = 'tag'; + protected $table = 'crm_tag'; /** * The primary key associated with the table. diff --git a/app/crm/model/TagGroup.php b/app/crm/model/TagGroup.php index 9c2e419..5b5e28e 100644 --- a/app/crm/model/TagGroup.php +++ b/app/crm/model/TagGroup.php @@ -17,7 +17,7 @@ class TagGroup extends Model * * @var string */ - protected $table = 'tag_group'; + protected $table = 'crm_tag_group'; /** * The primary key associated with the table. diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php index b870416..89b7c2b 100644 --- a/app/crm/service/FollowService.php +++ b/app/crm/service/FollowService.php @@ -2,13 +2,15 @@ namespace app\crm\service; +use app\common\service\OssService; use app\crm\model\Follow; use app\crm\utils\Util; +use config\constants\Constants; class FollowService { - public function list($param) + public function list($param, $userId) { @@ -22,16 +24,9 @@ class FollowService $where['customer_id'] = $customer_id; //用户角色 - $userId = $request->user['id']; - if(Util::isEmpty($userId)){ - return $this->error(trans('user_none')); - } - if($request->user['role']!=1){ - $where['user_id'] = $userId; - } //跟进id - $id = Util::getParam($request,'id'); + $id = $param['id']??null; if(!Util::isEmpty($id)){ $where['id'] = $id; } @@ -43,11 +38,27 @@ class FollowService // $host = $request->host(); - $url = 'http:'.$request->url(); +// $url = 'http:'.$request->url(); foreach ($data as &$value){ - $value = Follow::getFollow($value); + $value = self::getFollow($value); } - return $this->success($data,trans('success')); + return $data; + } + + + + public static function getFollow($follow,$domain = 'https://vehicle-album.oss-cn-beijing.aliyuncs.com') + { + $follow['type_txt'] = Constants::FOLLOW[$follow['type']]; + $follow['firstFollowtime'] = $follow['follow_time']; + $oss_picture = ''; + if(!empty($follow['picture'])){ + $ossservice = new OssService(); + $oss_picture = $ossservice->generatePrivateUrl($follow['picture'],86400); + } + $follow['oss_picture'] = $oss_picture; +// $follow = Util::formatDate($follow); + return $follow; } } \ No newline at end of file diff --git a/app/crm/validate/FollowValidate.php b/app/crm/validate/FollowValidate.php index 2ed6e71..a92c4a0 100644 --- a/app/crm/validate/FollowValidate.php +++ b/app/crm/validate/FollowValidate.php @@ -16,6 +16,6 @@ class FollowValidate extends Validate ]; protected $scene = [ - 'upwd_list' => ['customer_id'], + 'list' => ['customer_id'], ]; } \ No newline at end of file diff --git a/config/route.php b/config/route.php index db2c831..0e25da4 100644 --- a/config/route.php +++ b/config/route.php @@ -65,3 +65,12 @@ Route::group('/ticket', function () { }); +//crm +Route::group('/crm', function () { + Route::any('/follow/list', [app\crm\controller\FollowController::class, 'list']); + Route::any('/follow/detail', [app\crm\controller\FollowController::class, 'detail']); + Route::any('/follow/add', [app\crm\controller\FollowController::class, 'save']); + Route::any('/follow/getFollowType', [app\crm\controller\FollowController::class, 'getFollowType']); + Route::any('/follow/getFollowType', [app\crm\controller\FollowController::class, 'getFollowType']); +}); + diff --git a/resource/translations/ar/messages.php b/resource/translations/ar/messages.php index a701f87..7430697 100644 --- a/resource/translations/ar/messages.php +++ b/resource/translations/ar/messages.php @@ -2,4 +2,35 @@ return [ 'status_not_modified' => 'الدولة لا يمكن تعديلها عكسيا', + 'success' => ' النجاح', + 'fail' => ' الفشل', + 'login_success' => ' تم تسجيل الدخول بنجاح', + 'password_error' => ' كلمة المرور غير صحيحة', + 'user_none' => ' المستخدم غير موجود', + 'user_id' => ' معرف المستخدم', + 'user_login' => ' الرجاء تسجيل الدخول أولاً', + 'not_null' => ' لا يمكن أن يكون فارغًا', + 'code_error' => ' رمز التحقق غير صحيح', + 'format_error' => ' تنسيق غير صحيح', + 'email_repeat' => ' البريد الإلكتروني مكرر', + 'email_error' => ' تنسيق البريد الإلكتروني غير صحيح', + 'save_success' => ' تم الحفظ بنجاح', + 'save_fail' => ' فشل الحفظ', + 'register_success' => ' تم التسجيل بنجاح', + 'register_fail' => ' فشل التسجيل', + 'language_success' => ' تم تغيير اللغة بنجاح', + 'language_fail' => ' فشل تغيير اللغة', + 'param_error' => ' خطأ في المعلمات', + 'required_not_null' => ' الحقول المطلوبة لا يمكن أن تكون فارغة', + 'picture_not_null' => ' لا يمكن أن تكون الصورة فارغة', + 'contacts_not_null' => ' لا يمكن أن يكون جهة الاتصال أو البريد الإلكتروني أو الهاتف فارغين جميعًا في نفس الوقت', + 'car_type_not_null' => ' لا يمكن أن تكون التفضيلات فارغة', + 'channel_not_null' => ' لا يمكن أن تكون القناة فارغة', + 'email_consistent' => ' البريد الإلكتروني غير متطابق', + 'sign_not_null' => ' لم يتم تعيين الشركة التابعة', + 'password_short' => ' لا يمكن أن تكون كلمة المرور أقل من 6 أحرف', + 'id_empty' => ' لا يمكن أن يكون المعرف فارغًا', + 'customer_id' => ' معرف العميل', + 'tag_group_id' => ' معرف مجموعة العلامات', + 'claim_fail' => ' لا يمكن استلام العملاء الذين لديهم مالك بالفعل', ]; \ No newline at end of file diff --git a/resource/translations/en/messages.php b/resource/translations/en/messages.php index c676eb2..342ffad 100644 --- a/resource/translations/en/messages.php +++ b/resource/translations/en/messages.php @@ -2,4 +2,35 @@ return [ 'status_not_modified' => 'The status cannot be reversed for modification', + 'success' => ' success', + 'fail' => ' fail', + 'login_success' => ' login success', + 'password_error' => ' password error', + 'user_none' => ' user none', + 'user_id' => ' user ID', + 'user_login' => ' Please log in first', + 'not_null' => ' not null', + 'code_error' => ' code error', + 'format_error' => ' format error', + 'email_repeat' => ' email repeat', + 'email_error' => ' Email format error', + 'save_success' => ' save success', + 'save_fail' => ' save fail', + 'register_success' => ' register success', + 'register_fail' => ' register fail', + 'language_success' => ' Successfully switched languages', + 'language_fail' => ' Language switching failed', + 'param_error' => ' param error', + 'required_not_null' => ' required not_ ull', + 'picture_not_null' => ' picture not null', + 'contacts_not_null' => ' Contact person, email, and phone cannot be empty at the same time', + 'car_type_not_null' => ' preference_not_null', + 'channel_not_null' => ' channel not null', + 'email_consistent' => ' Inconsistent email addresses', + 'sign_not_null' => ' No affiliated company has been set', + 'password_short' => ' Password cannot be less than 6 characters', + 'id_empty' => ' ID cannot be empty', + 'customer_id' => ' customer ID', + 'tag_group_id' => ' tag group ID', + 'claim_fail' => ' Cannot claim ownership of customers', ]; \ No newline at end of file diff --git a/resource/translations/ru/messages.php b/resource/translations/ru/messages.php index 96ab3a4..3a1839f 100644 --- a/resource/translations/ru/messages.php +++ b/resource/translations/ru/messages.php @@ -2,4 +2,35 @@ return [ 'status_not_modified' => 'Статус не может быть изменен в обратном направлении', + 'success' => ' Успех', + 'fail' => ' Неудача', + 'login_success' => ' Регистрация завершена', + 'password_error' => ' Ошибка пароля', + 'user_none' => ' Пользователь не существует', + 'user_id' => ' Идентификатор пользователя', + 'user_login' => ' Пожалуйста, войдите.', + 'not_null' => ' Нельзя быть пустым', + 'code_error' => ' Ошибка кода проверки', + 'format_error' => ' Ошибка формата', + 'email_repeat' => ' Почтовый ящик повторяется', + 'email_error' => ' Ошибка формата почтового ящика', + 'save_success' => ' Сохранить успешно', + 'save_fail' => ' Ошибка сохранения', + 'register_success' => ' Регистрация успешно', + 'register_fail' => ' Ошибка регистрации', + 'language_success' => ' Переключить язык успешно', + 'language_fail' => ' Ошибка переключения языка', + 'param_error' => ' Ошибка параметра', + 'required_not_null' => ' Обязательное заполнение не может быть пустым.', + 'picture_not_null' => ' Фотографии не могут быть пустыми', + 'contacts_not_null' => ' Контакты, почтовый ящик, телефон не может быть одновременно пустым', + 'car_type_not_null' => ' Предпочтения не могут быть пустыми', + 'channel_not_null' => ' Каналы не могут быть пустыми', + 'email_consistent' => ' Почтовый ящик не совпадает', + 'sign_not_null' => ' Компания не создана', + 'password_short' => ' Пароль не может быть меньше 6.', + 'id_empty' => ' ID не может быть пустым', + 'customer_id' => ' Идентификатор клиента', + 'tag_group_id' => ' Идентификатор группы меток', + 'claim_fail' => ' Невозможность получения принадлежащего клиенту', ]; \ No newline at end of file diff --git a/resource/translations/zh/messages.php b/resource/translations/zh/messages.php index 229d7ee..96c3cb7 100644 --- a/resource/translations/zh/messages.php +++ b/resource/translations/zh/messages.php @@ -2,4 +2,35 @@ return [ 'status_not_modified' => '状态不能逆向修改', + 'success' => ' 成功', + 'fail' => ' 失败', + 'login_success' => ' 登录成功', + 'password_error' => ' 密码错误', + 'user_none' => ' 用户不存在', + 'user_id' => ' 用户ID', + 'user_login' => ' 请先登录', + 'not_null' => ' 不能为空', + 'code_error' => ' 验证码错误', + 'format_error' => ' 格式错误', + 'email_repeat' => ' 邮箱重复', + 'email_error' => ' 邮箱格式错误', + 'save_success' => ' 保存成功', + 'save_fail' => ' 保存失败', + 'register_success' => ' 注册成功', + 'register_fail' => ' 注册失败', + 'language_success' => ' 切换语言成功', + 'language_fail' => ' 切换语言失败', + 'param_error' => ' 参数错误', + 'required_not_null' => ' 必填项不能为空', + 'picture_not_null' => ' 图片不能为空', + 'contacts_not_null' => ' 联系人,邮箱,电话不能同时为空', + 'car_type_not_null' => ' 偏好不能为空', + 'channel_not_null' => ' 渠道不能为空', + 'email_consistent' => ' 邮箱不一致', + 'sign_not_null' => ' 未设置所属公司', + 'password_short' => ' 密码不能少于6位', + 'id_empty' => ' id不能为空', + 'customer_id' => ' 客户ID', + 'tag_group_id' => ' 标签组ID', + 'claim_fail' => ' 不能领取有归属客户', ]; \ No newline at end of file From 92eff917476ba29009e12f0502c431e7c37ec8c2 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 11 Jul 2025 14:52:16 +0800 Subject: [PATCH 031/120] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9D=83=E9=99=90?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1.sql | 3 - app/admin/controller/RoleController.php | 20 +- app/common/controller/AuthController.php | 94 ++++--- app/common/logic/AuthLogic.php | 255 ++++++++++-------- app/common/logic/BaseLogic.php | 86 ++++++ app/common/logic/PermLogic.php | 88 +++++- app/common/model/User.php | 8 +- ...serFeishuApp.php => UserFeishuApp_bak.php} | 2 +- app/common/service/AuthService.php | 11 +- app/common/service/PermService.php | 3 - app/common/validate/RoleValidate.php | 9 +- app/controller/IndexController.php | 2 +- app/functions.php | 89 +++--- app/view/index/qrcode.html | 2 +- composer.json | 2 +- composer.lock | 27 +- config/route.php | 4 +- config/whitelist.php | 2 +- 18 files changed, 463 insertions(+), 244 deletions(-) delete mode 100644 1.sql create mode 100644 app/common/logic/BaseLogic.php rename app/common/model/{UserFeishuApp.php => UserFeishuApp_bak.php} (97%) diff --git a/1.sql b/1.sql deleted file mode 100644 index ea1e67c..0000000 --- a/1.sql +++ /dev/null @@ -1,3 +0,0 @@ -User ← user_roles → Role ← role_resources → Resource (type=menu/button) - ↘︎ - role_permissions → Permission \ No newline at end of file diff --git a/app/admin/controller/RoleController.php b/app/admin/controller/RoleController.php index 232b38e..59ccb54 100644 --- a/app/admin/controller/RoleController.php +++ b/app/admin/controller/RoleController.php @@ -16,9 +16,17 @@ class RoleController extends \app\common\controller\BaseController try { $params = $request->all(); $result = (new RoleService())->getRoleList($params); - return $this->success($this->transformData($result), '角色列表获取成功'); + // 定义后处理器 + $postProcessor = function($formattedData) { + if (isset($formattedData['created_at'])) + $formattedData['created_at'] = date('Y-m-d H:i:s', $formattedData['created_at']); + if (isset($formattedData['updated_at'])) + $formattedData['updated_at'] = date('Y-m-d H:i:s', $formattedData['updated_at']); + return $formattedData; + }; + return $this->success($this->transformData($result, $postProcessor), '角色列表获取成功'); } catch (\Throwable $e) { - return $this->error($e->getMessage(), 1, $e->getCode()); + return $this->error($e->getMessage()); } } @@ -35,7 +43,7 @@ class RoleController extends \app\common\controller\BaseController $result = (new RoleService())->getRoleDetail($roleId); return $this->success($result, '角色详情获取成功'); } catch (\Throwable $e) { - return $this->error($e->getMessage(), 1, $e->getCode()); + return $this->error($e->getMessage()); } } @@ -51,7 +59,7 @@ class RoleController extends \app\common\controller\BaseController } catch (\think\exception\ValidateException $e) { return $this->error($e->getError(), 422); } catch (\Throwable $e) { - return $this->error($e->getMessage(), 1, $e->getCode()); + return $this->error($e->getMessage()); } } @@ -67,7 +75,7 @@ class RoleController extends \app\common\controller\BaseController } catch (\think\exception\ValidateException $e) { return $this->error($e->getError(), 422); } catch (\Throwable $e) { - return $this->error($e->getMessage(), 1, $e->getCode()); + return $this->error($e->getMessage()); } } @@ -84,7 +92,7 @@ class RoleController extends \app\common\controller\BaseController (new RoleService())->deleteRole($roleId); return $this->success([], '角色删除成功'); } catch (\Throwable $e) { - return $this->error($e->getMessage(), 1, $e->getCode()); + return $this->error($e->getMessage()); } } } diff --git a/app/common/controller/AuthController.php b/app/common/controller/AuthController.php index 1375f84..02676e8 100644 --- a/app/common/controller/AuthController.php +++ b/app/common/controller/AuthController.php @@ -14,19 +14,19 @@ class AuthController extends BaseController */ protected $noNeedHeader = ['feishuAuth']; - protected $noNeedLogin = ['feishuScope', 'login', 'register']; + protected $noNeedLogin = ['feishuScope', 'login', 'register', 'feishuAuth']; // 调试 - // public function feishuAuth(Request $request) - // { - // try { - // $code = $request->all(); - // return $this->success($code); - // } catch (\Throwable $e) { - // return $this->error($e->getMessage()); - // } - // } + public function feishuAuth(Request $request) + { + try { + $code = $request->all(); + return $this->success($code); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } public function feishuScope(Request $request) { @@ -60,46 +60,56 @@ class AuthController extends BaseController } } - public function verify(Request $request) - { - try { - $token = $request->post('access_token') ?: $request->header('authorization'); - $systemCode = $request->post('system_code'); - if (!$token || !$systemCode) return $this->error('参数缺失', 422); - $payload = (new AuthService())->verifyToken($token, $systemCode); - return $this->success($payload, '验证成功'); - } catch (\Throwable $e) { - return $this->error($e->getMessage() ?: '无效或过期的token', 401); - } - } - public function refresh(Request $request) { try { - $refresh_token = $request->post('refresh_token'); - $systemCode = $request->post('system_code'); - if (!$refresh_token || !$systemCode) return $this->error('参数缺失', 422); - $result = (new AuthService())->refreshToken($refresh_token, $systemCode); + $data = $this->requestData($request); + if (!$data['refresh_token'] || !$data['system_code']) return $this->error('参数缺失', 422); + $result = (new AuthService())->refreshToken($data['refresh_token'], $data['system_code']); return $this->success($result, '刷新成功'); } catch (\Throwable $e) { return $this->error($e->getMessage(), 401); } } - public function logout(Request $request) - { - $token = $request->post('access_token') ?: $request->header('Authorization'); - $systemCode = $request->post('system_code') ?: $request->header('system_code'); - (new AuthService())->logout($token, $systemCode); - return $this->success([], '已登出'); - } - public function blockToken(Request $request) - { - $token = $request->post('token'); - $systemCode = $request->post('system_code'); - if (!$token || !$systemCode) return $this->error('参数缺失', 422); - (new AuthService())->blockToken($token, $systemCode); - return $this->success([], '已强制停用该token'); - } + // public function perm(Request $request) + // { + // try { + // $data = $this->requestData($request); + // if (!$data['access_token'] || !$data['system_code']) return $this->error('参数缺失', 422); + // $result = (new AuthService())->logout($data['access_token'], $data['system_code']); + // return $this->success($result, '已登出'); + // } catch (\Throwable $e) {} + // } + + // public function verify(Request $request) + // { + // try { + // $token = $request->post('access_token') ?: $request->header('authorization'); + // $systemCode = $request->post('system_code'); + // if (!$token || !$systemCode) return $this->error('参数缺失', 422); + // $payload = (new AuthService())->verifyToken($token, $systemCode); + // return $this->success($payload, '验证成功'); + // } catch (\Throwable $e) { + // return $this->error($e->getMessage() ?: '无效或过期的token', 401); + // } + // } + + // public function logout(Request $request) + // { + // $token = $request->post('access_token') ?: $request->header('Authorization'); + // $systemCode = $request->post('system_code') ?: $request->header('system_code'); + // (new AuthService())->logout($token, $systemCode); + // return $this->success([], '已登出'); + // } + + // public function blockToken(Request $request) + // { + // $token = $request->post('token'); + // $systemCode = $request->post('system_code'); + // if (!$token || !$systemCode) return $this->error('参数缺失', 422); + // (new AuthService())->blockToken($token, $systemCode); + // return $this->success([], '已强制停用该token'); + // } } \ No newline at end of file diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index 3c84694..ccdbf16 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -7,7 +7,6 @@ use app\common\model\CorpFeishuApp; use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; -use app\common\model\UserFeishuApp; use app\common\model\UserSn; use app\common\utils\Feishu; use app\common\utils\Jwt; @@ -46,87 +45,83 @@ class AuthLogic return $this->generateTokensAndReturn($user, $system, $data); } - /** - * 扫码登录 - */ - public function qrCodeLogin(array $data): array - { - $userId = $this->validateQrCodeToken($data['qr_code_token']); - $user = User::find($userId); - - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - - return $this->generateTokensAndReturn($user, $system, $data); - } - /** * 注册 */ public function register(array $data) { - // 获取可用用户编号 - $sn = $this->getAvailableUserSn(); - $data['sn'] = $sn->user_sn; + try { + // 获取可用用户编号 + $sn = $this->getAvailableUserSn(); + $data['sn'] = $sn->user_sn; - // 对密码进行哈希处理 - $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + // 对密码进行哈希处理 + $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); - // 获取企业ID - $corpInfo = $this->validateCorp($data['corp_abbr']); - $data['corp_id'] = $corpInfo->id; + // 获取企业ID + $corpInfo = $this->validateCorp($data['corp_abbr']); + $data['corp_id'] = $corpInfo->id; - // 处理账户 - $data = $data + normalizeAccount($data['account']); - - // 创建用户记录 - $user = $this->createUser($data); + // 处理账户 + $data = $data + normalizeAccount($data['account']); + + // 创建用户记录 + $user = $this->createUser($data); - // 更新用户编号的用户 ID - $this->updateUserId($user->id, $sn->user_sn); + // 更新用户编号的用户 ID + $this->updateUserId($user->id, $sn->user_sn); - $system = $this->validateSystem($data['system_code']); - return $this->generateTokensAndReturn($user, $system, $data); + $system = $this->validateSystem($data['system_code']); + return $this->generateTokensAndReturn($user, $system, $data); + } catch (\Throwable $e) { + throw $e; + } } - /** ----------- 内部方法 ------------- **/ - /** * 查找或创建飞书用户 */ private function getOrCreateUser(array $feishuUserInfo) { - $userFeishuApp = UserFeishuApp::firstOrCreate([ - 'name' => $feishuUserInfo['en_name'], - 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), - 'union_id' => $feishuUserInfo['union_id'], - 'user_id' => $feishuUserInfo['user_id'], - 'open_id' => $feishuUserInfo['open_id'], - 'tenant_key' => $feishuUserInfo['tenant_key'] - ]); - - if ($userFeishuApp->uid) { - $user = User::find($userFeishuApp->uid); - $user->isLogin = true; - return $user; - } else { - $user = User::where('phone', $userFeishuApp->mobile)->first(); - $user->isLogin = true; + try { + $phone = $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''); + $user = User::where('phone', $phone)->where('corp_id', $feishuUserInfo['corp_id'])->first(); + if (!$user) { - $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); + // 获取编号 + $userSn = $this->getAvailableUserSn(); + // 创建用户 + $user = User::create([ + 'name' => $feishuUserInfo['en_name'], + 'avatar' => $feishuUserInfo['avatar_middle'], + 'phone' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'], + 'corp_id' => $feishuUserInfo['corp_id'], + 'password' => password_hash($userSn->user_sn, PASSWORD_DEFAULT) + ]); + // 更新使用 + $this->updateUserId($user->id, $userSn->user_sn); + // 更新编号 + if (!$user->update(['sn'=>$userSn->user_sn])) { + throw new BusinessException('编号绑定失败'); + } + // 是否登陆 $user->isLogin = false; - } - $userFeishuApp->uid = $user->id; - $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; - if (!$userFeishuApp->save()) { - throw new BusinessException('绑定用户飞书记录失败'); + } else { + $user->update([ + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'] + ]); } return $user; - } + } catch (\Throwable $e) { + throw $e; + } } /** @@ -137,38 +132,6 @@ class AuthLogic return isset($mobile) ? str_replace('+86', '', $mobile) : ''; } - /** - * 创建飞书用户 - */ - private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) - { - // 1. 获取适用的用户编号 - $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$userSn) { - throw new BusinessException('未找到适用编号'); - } - - // 2. 创建用户 - $user = new User(); - $user->sn = $userSn->user_sn; - $user->name = $feishuUserInfo['en_name']; - $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; - $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); - $user->phone = $userFeishuApp->mobile; - $user->email = ''; - $user->system_code = $feishuUserInfo['system_code']; - $user->corp_id = $feishuUserInfo['corp_id']; - - if (!$user->save()) { - throw new BusinessException('用户创建失败'); - } - - // 更新使用状态 - $this->updateUserId($user->id, $userSn->user_sn); - - return $user; - } - /** * 生成并记录token,返回包含token的响应数据 */ @@ -295,7 +258,8 @@ class AuthLogic public function feishuAppInfo(string $corpAbbr, string $systemCode) { $corpInfo = $this->validateCorp($corpAbbr); - $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); + $systemInfo = $this->validateSystem($systemCode); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemInfo->id); return [ 'app_id' => $fsAppInfo->app_id, 'app_secret' => $fsAppInfo->app_secret, @@ -303,7 +267,8 @@ class AuthLogic 'scope' => $fsAppInfo->scopes, 'state' => $fsAppInfo->state, 'abbr' => $corpAbbr, - 'corp_id' => $corpInfo->id + 'corp_id' => $corpInfo->id, + 'system_id' => $systemInfo->id ]; } @@ -349,9 +314,9 @@ class AuthLogic /** * 验证飞书应用信息 */ - private function validateFeishuApp(int $corpId, string $systemCode) + private function validateFeishuApp(int $corpId, int $systemId) { - $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first(); + $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first(); if (!$feishuAppInfo) { throw new BusinessException('飞书应用不存在', 403); } @@ -371,18 +336,6 @@ class AuthLogic } } - /** - * 验证扫码令牌 - */ - private function validateQrCodeToken(string $qrCodeToken) - { - $userId = Redis::get("qr_code_token:{$qrCodeToken}"); - if (!$userId) { - throw new BusinessException('扫码令牌无效或已过期', 401); - } - return $userId; - } - /** * 获取可用的用户编号 * @@ -463,4 +416,88 @@ class AuthLogic Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration')); return $refreshToken; } + + + /** + * 刷新token + * @param string $refreshToken + * @param string|null $systemCode 可选,指定系统 + * @return array + */ + public function refreshToken(string $refreshToken, ?string $systemCode = null): array + { + if (!$systemCode) throw new BusinessException('缺少system_code', 422); + + // 校验refresh_token + $userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}"); + if (!$userId) { + throw new BusinessException('refresh_token无效或已过期', 401); + } + + $validToken = Redis::get("refresh_token:{$userId}:{$systemCode}"); + if ($validToken !== $refreshToken) { + throw new BusinessException('refresh_token无效或已过期', 401); + } + + $user = User::find($userId); + if (!$user || $user->status != 1) { + throw new BusinessException('用户无效', 403); + } + + $system = $this->validateSystem($systemCode); + $this->validateSystemPermission($user, $system); + + $payload = $this->buildJwtPayload($user, $system); + $token = $this->generateAccessToken($payload); + $refreshToken = $this->generateRefreshToken($user->id, $systemCode); + $this->recordUserToken($user->id, $token, $systemCode); + + return [ + 'access_token' => $token, + 'refresh_token' => $refreshToken, + 'expires_in' => config('jwt.jwt_expiration'), + // 'payload' => $payload, + ]; + } + + /** + * 登出/失效token + */ + public function logout(?string $token, ?string $systemCode = null) + { + if ($token) { + Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); + $payload = null; + try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} + $userId = $payload['uid'] ?? null; + $aud = $payload['aud'] ?? null; + $sc = $systemCode ?? $aud; + if ($userId && $sc) { + Redis::sRem("user_tokens:{$userId}:{$sc}", $token); + } + Redis::del("user_token_meta:{$token}"); + UserTokenLog::where('token', $token)->update(['is_valid'=>0]); + } + } + + /** + * 强制拉黑token + */ + public function blockToken(string $token, ?string $systemCode = null) + { + if (!$token) { + throw new BusinessException('token不能为空', 422); + } + Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); + $payload = null; + try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} + $userId = $payload['uid'] ?? null; + $aud = $payload['aud'] ?? null; + $sc = $systemCode ?? $aud; + if ($userId && $sc) { + Redis::sRem("user_tokens:{$userId}:{$sc}", $token); + } + Redis::del("user_token_meta:{$token}"); + UserTokenLog::where('token', $token)->update(['is_valid'=>0]); + } } \ No newline at end of file diff --git a/app/common/logic/BaseLogic.php b/app/common/logic/BaseLogic.php new file mode 100644 index 0000000..94beff3 --- /dev/null +++ b/app/common/logic/BaseLogic.php @@ -0,0 +1,86 @@ +paginate($params['per_page'], ['*'], 'page', $params['page']); + } + + return $query->get(); + } + + /** + * 获取单个记录 + */ + public function getDetail(int $id, string $modelClass) + { + $record = $modelClass::find($id); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + return $record; + } + + /** + * 创建记录 + */ + public function create(array $data, string $modelClass) + { + $model = new $modelClass; + $model->fill($data); // 根据字段填充数据 + try { + $model->save(); + } catch (\Throwable $e) { + throw new BusinessException("创建记录失败: " . $e->getMessage(), 500); + } + + return $model; + } + + /** + * 更新记录 + */ + public function update(array $data, string $modelClass) + { + $record = $modelClass::find($data['id']); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + $record->update($data); + return $record; + } + + /** + * 删除记录 + */ + public function delete(int $id, string $modelClass) + { + $record = $modelClass::find($id); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + try { + $record->delete(); + } catch (\Throwable $e) { + throw new BusinessException("删除记录失败: " . $e->getMessage(), 500); + } + + return true; + } +} diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index d8d917c..889470e 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -27,16 +27,82 @@ class PermLogic /** * 构建目录/菜单树,节点包含权限与打开方式 */ - public function buildTree($resources, int $parentId = 0): array + // public function buildTree($resources, int $parentId = 0): array + // { + // $tree = []; + // foreach ($resources as $node) { + // if ($node->parent_id !== $parentId) continue; + + // // 子节点 + // $children = []; + // if ($node->type === 'directory') { + // $children = $this->buildTree($resources, $node->id); + // } + + // // 本级权限列表 + // $authList = $resources + // ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) + // ->map(fn($r) => [ + // 'id' => $r->id, + // 'title' => $r->name, + // 'auth_mark' => $r->code, + // ])->values()->all(); + + // $tree[] = [ + // 'id' => $node->id, + // 'name' => $node->name, + // 'path' => $node->url, + // 'component' => $node->type === 'directory' ? 'Layout' : 'Page', + // 'meta' => [ + // 'title' => $node->name, + // 'icon' => $node->icon, + // 'keepAlive' => true, + // 'authList' => $authList, + // 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' + // ], + // 'children' => $children, + // ]; + // } + // return $tree; + // } + /** + * 构建目录/菜单树,节点包含权限与打开方式,并拼接完整路径到 component 字段 + * @param \Illuminate\Support\Collection $resources + * @param int $parentId + * @param string $parentPath + * @return array + */ + public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array { $tree = []; foreach ($resources as $node) { if ($node->parent_id !== $parentId) continue; - // 子节点 - $children = $this->buildTree($resources, $node->id); + // 拼接完整路径 + $currentPath = $parentPath; + // 去掉结尾斜杠,防止重复 + if ($currentPath && substr($currentPath, -1) === '/') { + $currentPath = rtrim($currentPath, '/'); + } + // 子菜单的 path 不要补全斜杠 + if (!empty($node->url)) { + // 如果当前是根节点或父路径为空,直接用自身 + if (!$currentPath) { + $currentPath = $node->url; + } else { + // 防止 url 以 / 开头导致双斜杠 + $url = ltrim($node->url, '/'); + $currentPath = $currentPath . '/' . $url; + } + } - // 本级权限列表 + // 子节点 + $children = []; + if ($node->type === 'directory') { + $children = $this->buildTree($resources, $node->id, $currentPath); + } + + // 权限 $authList = $resources ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) ->map(fn($r) => [ @@ -46,22 +112,24 @@ class PermLogic ])->values()->all(); $tree[] = [ - 'id' => $node->id, - 'name' => $node->name, - 'path' => $node->url, - 'meta' => [ + 'id' => $node->id, + 'name' => $node->name, + 'path' => $node->url, + 'component' => $currentPath, // 这里就是全路径 + 'meta' => [ 'title' => $node->name, 'icon' => $node->icon, 'keepAlive' => true, 'authList' => $authList, - 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' + 'openType' => $node->open_type, ], - 'children' => $children, + 'children' => $children, ]; } return $tree; } + /** * 获取用户所有权限code */ diff --git a/app/common/model/User.php b/app/common/model/User.php index 23e8b1c..4709b44 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -58,8 +58,12 @@ class User extends Model 'phone', 'email', 'status', - 'system_code', - 'corp_id' + 'corp_id', + 'user_type', + 'union_id', + 'user_id', + 'open_id', + 'tenant_key' ]; // 用户所有角色 diff --git a/app/common/model/UserFeishuApp.php b/app/common/model/UserFeishuApp_bak.php similarity index 97% rename from app/common/model/UserFeishuApp.php rename to app/common/model/UserFeishuApp_bak.php index c64e8cd..9aa06c0 100644 --- a/app/common/model/UserFeishuApp.php +++ b/app/common/model/UserFeishuApp_bak.php @@ -20,7 +20,7 @@ use support\Model; * @property integer $created_at 创建时间 * @property integer $updated_at 更新时间 */ -class UserFeishuApp extends Model +class UserFeishuApp_bak extends Model { /** * The connection name for the model. diff --git a/app/common/service/AuthService.php b/app/common/service/AuthService.php index aa8f4eb..6de72a2 100644 --- a/app/common/service/AuthService.php +++ b/app/common/service/AuthService.php @@ -31,7 +31,7 @@ class AuthService return match ($loginType) { 'feishu' => $logic->feishuLogin($data), - 'qrcode' => $logic->qrCodeLogin($data), + // 'qrcode' => $logic->qrCodeLogin($data), default => $logic->uPwdLogin($data), }; } @@ -86,4 +86,13 @@ class AuthService { (new AuthLogic())->blockToken($token, $systemCode); } + + + // public function perms() + // { + // $permService = new PermService(); + // $menuTree = $permService->getMenuTree($uid); + // $permissionCodes = $permService->getPermissionCodes($uid); + // return (new PermService())->getMenuTree($this->userId) + // } } \ No newline at end of file diff --git a/app/common/service/PermService.php b/app/common/service/PermService.php index a629b23..8f8770c 100644 --- a/app/common/service/PermService.php +++ b/app/common/service/PermService.php @@ -30,13 +30,10 @@ class PermService if ($cached !== null) { return $cached; } - // 调用 Logic 层获取资源 $resources = $this->logic->getUserResources($userId); - // 缓存结果 Cache::set($cacheKey, $resources, 3600); - return $resources; } diff --git a/app/common/validate/RoleValidate.php b/app/common/validate/RoleValidate.php index bf51cf9..330fae6 100644 --- a/app/common/validate/RoleValidate.php +++ b/app/common/validate/RoleValidate.php @@ -11,7 +11,8 @@ class RoleValidate extends Validate 'system_id' => 'require|integer', 'name' => 'require|max:64', 'desc' => 'max:128', - 'status' => 'require|in:0,1', + 'sort' => 'integer', + 'status' => 'in:0,1', ]; protected $message = [ @@ -20,12 +21,12 @@ class RoleValidate extends Validate 'name.require' => '角色名称不能为空', 'name.max' => '角色名称不能超过64个字符', 'desc.max' => '描述不能超过128个字符', - 'status.require' => '状态不能为空', + 'sort.integer' => '排序为数字', 'status.in' => '状态值必须为0或1', ]; protected $scene = [ - 'role_create' => ['system_id', 'name', 'desc', 'status'], - 'role_update' => ['id', 'system_id', 'name', 'desc', 'status'], + 'role_create' => ['system_id', 'name', 'desc', 'sort'], + 'role_update' => ['id', 'system_id', 'name', 'desc', 'sort', 'status'], ]; } diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 988928f..f0feb2c 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -7,7 +7,7 @@ use support\Request; class IndexController { - protected $noNeedLogin = ['index']; + protected $noNeedLogin = ['index', 'view']; public function index(Request $request) { diff --git a/app/functions.php b/app/functions.php index d0682c9..2a05e3f 100644 --- a/app/functions.php +++ b/app/functions.php @@ -2,8 +2,6 @@ /** * Here is your custom functions. */ - -use support\exception\BusinessException; /** * 标准化账号参数 @@ -11,62 +9,69 @@ use support\exception\BusinessException; * @param string $account 用户输入的账号(可能是手机号或邮箱) * @return array 返回 ['phone' => $account] 或 ['email' => $account] */ -function normalizeAccount(string $account): array -{ - // 使用FILTER_VALIDATE_EMAIL验证邮箱格式 - if (filter_var($account, FILTER_VALIDATE_EMAIL)) { - return ['email' => $account]; - } - - // 清理手机号中的非数字字符 - $cleanPhone = preg_replace('/[^0-9]/', '', $account); - - // 验证手机号格式(简单版:11位数字) - if (preg_match('/^1[3-9]\d{9}$/', $cleanPhone)) { +if (!function_exists('normalizeAccount')) { + function normalizeAccount(string $account): array + { + // 使用FILTER_VALIDATE_EMAIL验证邮箱格式 + if (filter_var($account, FILTER_VALIDATE_EMAIL)) { + return ['email' => $account]; + } + + // 清理手机号中的非数字字符 + $cleanPhone = preg_replace('/[^0-9]/', '', $account); + + // 验证手机号格式(简单版:11位数字) + if (preg_match('/^1[3-9]\d{9}$/', $cleanPhone)) { + return ['phone' => $cleanPhone]; + } + + // 如果既不是邮箱也不是手机号,根据业务需求处理 + // 这里默认按手机号处理(可根据实际需求调整) return ['phone' => $cleanPhone]; } - - // 如果既不是邮箱也不是手机号,根据业务需求处理 - // 这里默认按手机号处理(可根据实际需求调整) - return ['phone' => $cleanPhone]; } /** * 公共html页面 */ -function htmlpage() -{ - static $html; - if (!$html) { - $html = file_get_contents(public_path('mzl.html')); +if (!function_exists('htmlpage')) { + function htmlpage() + { + static $html; + if (!$html) { + $html = file_get_contents(public_path('mzl.html')); + } + return $html; } - return $html; } - /** * 获取配置 */ -function getConfig($filePath = null) -{ - // 构建文件路径 - $config_file = app_path($filePath); - // 检查文件是否存在 - if (!file_exists($config_file)) { - return []; +if (!function_exists('getConfig')) { + function getConfig($filePath = null) + { + // 构建文件路径 + $config_file = app_path($filePath); + // 检查文件是否存在 + if (!file_exists($config_file)) { + return []; + } + // 包含文件并获取返回的数组 + $config = include $config_file; + // 验证是否为数组 + if (!is_array($config)) { + return []; + } + return $config; } - // 包含文件并获取返回的数组 - $config = include $config_file; - // 验证是否为数组 - if (!is_array($config)) { - return []; - } - return $config; } /* * 调试用 */ -function dpr(\Throwable $e) -{ - var_dump("=== === 报错打印 === ===", "文件:".$e->getFile(), "行数:".$e->getLine(), "详情:".$e->getMessage(), "=== === 报错打印 === ==="); +if (!function_exists('dpr')) { + function dpr(\Throwable $e) + { + var_dump("=== === 报错打印 === ===", "文件:".$e->getFile(), "行数:".$e->getLine(), "详情:".$e->getMessage(), "=== === 报错打印 === ==="); + } } \ No newline at end of file diff --git a/app/view/index/qrcode.html b/app/view/index/qrcode.html index 26d1918..fb6f11c 100644 --- a/app/view/index/qrcode.html +++ b/app/view/index/qrcode.html @@ -16,7 +16,7 @@ // const REDIRECT_URI = encodeURIComponent('https://example.com/api/oauth/callback/feishu'); // const STATE = '随机串_防CSRF'; // // 用于跳转获取 code 的 URL - const GOTO = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=cli_a73e1e6406fa501c&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&response_type=code&state=MZL`; + const GOTO = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=cli_a72f13d6c022500d&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&response_type=code&state=MZL`; // const GOTO = `https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a73e1e6406fa501c&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&scope=contact:user.base:readonly contact:contact.base:readonly contact:user.employee_id:readonly im:message:send_as_bot&state=xinyouhe`; // 2. 创建二维码实例 const QRLoginObj = QRLogin({ diff --git a/composer.json b/composer.json index ccf259e..e4e63bb 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "topthink/think-validate": "^3.0", "shopwwi/webman-filesystem": "1.1", "shopwwi/filesystem-oss": "^1.0", - "webman/redis-queue": "^1.3", + "webman/redis-queue": "^2.1", "symfony/translation": "^7.3" }, "suggest": { diff --git a/composer.lock b/composer.lock index 01843a4..b9b58c7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "acb636fcb8774558a04bd674f9f95f55", + "content-hash": "30cc3cbb162022972a21efed9b69d1e1", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -6155,26 +6155,23 @@ }, { "name": "webman/redis-queue", - "version": "v1.3.1", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/webman-php/redis-queue.git", - "reference": "6fd491b19acb006d60931724aa2577a046ccb612" + "reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/6fd491b19acb006d60931724aa2577a046ccb612", - "reference": "6fd491b19acb006d60931724aa2577a046ccb612", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/12e1c9915ec64c91c0c9c255606bd0c7de27334c", + "reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c", + "shasum": "" }, "require": { - "workerman/redis-queue": "^1.0" + "ext-redis": "*", + "php": ">=8.1", + "workerman/redis-queue": "^1.2", + "workerman/webman-framework": "^2.1 || dev-master" }, "type": "library", "autoload": { @@ -6186,9 +6183,9 @@ "description": "Redis message queue plugin for webman.", "support": { "issues": "https://github.com/webman-php/redis-queue/issues", - "source": "https://github.com/webman-php/redis-queue/tree/v1.3.1" + "source": "https://github.com/webman-php/redis-queue/tree/v2.1.0" }, - "time": "2024-02-28T07:21:52+00:00" + "time": "2025-01-24T12:56:01+00:00" }, { "name": "workerman/coroutine", diff --git a/config/route.php b/config/route.php index a6c3a4d..41c3dff 100644 --- a/config/route.php +++ b/config/route.php @@ -31,11 +31,11 @@ Route::group('/common', function () { Route::any('/login', [app\common\controller\AuthController::class, 'login']); Route::any('/register', [app\common\controller\AuthController::class, 'register']); Route::any('/feishu/scope', [app\common\controller\AuthController::class, 'feishuScope']); - + Route::any('/refresh/token', [app\common\controller\AuthController::class, 'refresh']); // // 调试 - // Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); + Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); }); Route::group('/user', function () { diff --git a/config/whitelist.php b/config/whitelist.php index 8071847..d1669b3 100644 --- a/config/whitelist.php +++ b/config/whitelist.php @@ -4,6 +4,6 @@ return [ 'basi_cdata' => [ '/', '/index/index', - // '/index/view' + '/index/view' ] ]; \ No newline at end of file From fef9422da0d9d1322db1a5c559da4b2ad372e376 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 11 Jul 2025 15:00:21 +0800 Subject: [PATCH 032/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 61 +++++++--------------------------- 1 file changed, 12 insertions(+), 49 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index 889470e..b4fe819 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -25,48 +25,7 @@ class PermLogic } /** - * 构建目录/菜单树,节点包含权限与打开方式 - */ - // public function buildTree($resources, int $parentId = 0): array - // { - // $tree = []; - // foreach ($resources as $node) { - // if ($node->parent_id !== $parentId) continue; - - // // 子节点 - // $children = []; - // if ($node->type === 'directory') { - // $children = $this->buildTree($resources, $node->id); - // } - - // // 本级权限列表 - // $authList = $resources - // ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) - // ->map(fn($r) => [ - // 'id' => $r->id, - // 'title' => $r->name, - // 'auth_mark' => $r->code, - // ])->values()->all(); - - // $tree[] = [ - // 'id' => $node->id, - // 'name' => $node->name, - // 'path' => $node->url, - // 'component' => $node->type === 'directory' ? 'Layout' : 'Page', - // 'meta' => [ - // 'title' => $node->name, - // 'icon' => $node->icon, - // 'keepAlive' => true, - // 'authList' => $authList, - // 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' - // ], - // 'children' => $children, - // ]; - // } - // return $tree; - // } - /** - * 构建目录/菜单树,节点包含权限与打开方式,并拼接完整路径到 component 字段 + * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单component处理 * @param \Illuminate\Support\Collection $resources * @param int $parentId * @param string $parentPath @@ -80,23 +39,19 @@ class PermLogic // 拼接完整路径 $currentPath = $parentPath; - // 去掉结尾斜杠,防止重复 if ($currentPath && substr($currentPath, -1) === '/') { $currentPath = rtrim($currentPath, '/'); } - // 子菜单的 path 不要补全斜杠 if (!empty($node->url)) { - // 如果当前是根节点或父路径为空,直接用自身 if (!$currentPath) { $currentPath = $node->url; } else { - // 防止 url 以 / 开头导致双斜杠 $url = ltrim($node->url, '/'); $currentPath = $currentPath . '/' . $url; } } - // 子节点 + // 子节点递归 $children = []; if ($node->type === 'directory') { $children = $this->buildTree($resources, $node->id, $currentPath); @@ -111,11 +66,20 @@ class PermLogic 'auth_mark' => $r->code, ])->values()->all(); + // 判断是否有子级菜单(非权限) + $hasChildrenMenu = collect($resources)->where('parent_id', $node->id) + ->whereIn('type', ['directory', 'menu'])->isNotEmpty(); + + // component 字段规则: + // 1. 存在子菜单,固定 '/index/index' + // 2. 否则为当前完整路径(或其它你想要的) + $componentValue = $hasChildrenMenu ? '/index/index' : $currentPath; + $tree[] = [ 'id' => $node->id, 'name' => $node->name, 'path' => $node->url, - 'component' => $currentPath, // 这里就是全路径 + 'component' => $componentValue, 'meta' => [ 'title' => $node->name, 'icon' => $node->icon, @@ -129,7 +93,6 @@ class PermLogic return $tree; } - /** * 获取用户所有权限code */ From 91f26b740e8c26324d33dcdf25cb3ec2bca91fd6 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 11 Jul 2025 15:07:31 +0800 Subject: [PATCH 033/120] Merge branch 'develop' into feature/mzl_dev # Conflicts: # app/common/controller/AuthController.php # app/common/logic/AuthLogic.php # app/common/model/User.php # app/controller/IndexController.php # app/functions.php # app/middleware/AuthCheck.php # app/view/index/qrcode.html # composer.json # composer.lock # config/middleware.php # config/route.php # config/whitelist.php From 890ddff34bd6a52729761b0fbc0d0c08420fbac7 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 11 Jul 2025 15:13:44 +0800 Subject: [PATCH 034/120] =?UTF-8?q?=E6=88=91=E7=9A=84=E5=B7=A5=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/controller/TicketController.php | 18 ++++++++++++++++++ app/ticket/logic/TicketLogic.php | 4 ++++ config/route.php | 1 + 3 files changed, 23 insertions(+) diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php index 95dc539..defd5af 100644 --- a/app/ticket/controller/TicketController.php +++ b/app/ticket/controller/TicketController.php @@ -17,6 +17,24 @@ class TicketController extends BaseController return $this->error($e->getMessage()); } } + + /** + * 我的工单 + * @param Request $request + * @return \support\Response + */ + public function self(Request $request) + { + try { + $request->setGet('user_id',$request->user->uid); + $result = (new TicketService())->list($request->all()); + return $this->success($result); +// return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage().' '.$e->getLine().' '.$e->getFile().' '.$e->getTraceAsString()); + } + } + public function add(Request $request) { try { diff --git a/app/ticket/logic/TicketLogic.php b/app/ticket/logic/TicketLogic.php index e22cdb7..e7d098d 100644 --- a/app/ticket/logic/TicketLogic.php +++ b/app/ticket/logic/TicketLogic.php @@ -20,11 +20,15 @@ class TicketLogic if(!empty($params['categorie_id'])){ $where[] = ['categorie_id', '=', $params['categorie_id']]; } + if(!empty($params['user_id'])){ + $where[] = ['user_id', '=', $params['user_id']]; + } if(!empty($params['status'])){ $where[] = ['status', '=', $params['status']]; } $size = $params['size']; $current = $params['current']; + return Ticket::with('attachments')->with('categorie')->with('categorie.translation')->where($where)->orderBy('id','desc')->paginate($size, ['*'], 'page', $current); } public function add(array $data) diff --git a/config/route.php b/config/route.php index a6c3a4d..97f68c4 100644 --- a/config/route.php +++ b/config/route.php @@ -59,6 +59,7 @@ Route::group('/ticket', function () { Route::any('/ticket/add', [app\ticket\controller\TicketController::class, 'add']); Route::any('/ticket/edit', [app\ticket\controller\TicketController::class, 'edit']); Route::any('/ticket/list', [app\ticket\controller\TicketController::class, 'index']); + Route::any('/ticket/self', [app\ticket\controller\TicketController::class, 'self']); Route::any('/ticket/status', [app\ticket\controller\TicketController::class, 'status']); Route::any('/ticket/getStatus', [app\ticket\controller\TicketController::class, 'getStatus']); From 1a2cb17785c989b5a4ec923bdac78df5c8a94d52 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 11 Jul 2025 15:17:29 +0800 Subject: [PATCH 035/120] Merge branch 'develop' into feature/mzl_dev # Conflicts: # app/common/controller/AuthController.php # app/common/logic/AuthLogic.php # app/common/model/User.php # app/controller/IndexController.php # app/functions.php # app/middleware/AuthCheck.php # app/view/index/qrcode.html # composer.json # composer.lock # config/middleware.php # config/route.php # config/whitelist.php From a6f2284f515071bf95d4b942faf6aff6ddd2d30f Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 11 Jul 2025 15:21:00 +0800 Subject: [PATCH 036/120] Merge branch 'feature/mzl_dev' into develop # Conflicts: # app/common/controller/AuthController.php # app/common/logic/AuthLogic.php # app/common/model/User.php # app/controller/IndexController.php # app/functions.php # app/middleware/AuthCheck.php # app/view/index/qrcode.html # composer.json # composer.lock # config/middleware.php # config/route.php # config/whitelist.php --- 1.sql | 3 - app/admin/controller/AuthController_bak.php | 83 ------- app/admin/controller/RoleController.php | 98 ++++++++ app/common/controller/AuthController.php | 76 +++--- app/common/controller/BaseController.php | 5 +- app/common/controller/UserController.php | 25 ++ app/common/logic/AuthLogic.php | 255 +++++++++++--------- app/common/logic/BaseLogic.php | 86 +++++++ app/common/logic/PermLogic.php | 109 +++++++++ app/common/logic/RoleLogic.php | 93 +++++++ app/common/logic/UserLogic.php | 27 +++ app/common/model/Role.php | 2 + app/common/model/User.php | 8 +- app/common/model/UserFeishuApp_bak.php | 69 ++++++ app/common/service/AuthService.php | 11 +- app/common/service/PermService.php | 84 ++----- app/common/service/PermService_bak.php | 109 +++++++++ app/common/service/RoleService.php | 55 +++++ app/common/service/UserService.php | 38 ++- app/common/traits/DataTransformer.php | 135 +++++++++++ app/common/validate/RoleValidate.php | 32 +++ app/controller/IndexController.php | 2 + app/functions.php | 89 ++++--- app/middleware/AuthCheck.php | 2 +- app/view/index/qrcode.html | 2 +- composer.json | 2 +- composer.lock | 27 +-- config/middleware.php | 3 +- config/route.php | 18 +- config/whitelist.php | 2 +- 30 files changed, 1185 insertions(+), 365 deletions(-) delete mode 100644 1.sql delete mode 100644 app/admin/controller/AuthController_bak.php create mode 100644 app/admin/controller/RoleController.php create mode 100644 app/common/controller/UserController.php create mode 100644 app/common/logic/BaseLogic.php create mode 100644 app/common/logic/PermLogic.php create mode 100644 app/common/logic/RoleLogic.php create mode 100644 app/common/logic/UserLogic.php create mode 100644 app/common/model/UserFeishuApp_bak.php create mode 100644 app/common/service/PermService_bak.php create mode 100644 app/common/service/RoleService.php create mode 100644 app/common/traits/DataTransformer.php create mode 100644 app/common/validate/RoleValidate.php diff --git a/1.sql b/1.sql deleted file mode 100644 index ea1e67c..0000000 --- a/1.sql +++ /dev/null @@ -1,3 +0,0 @@ -User ← user_roles → Role ← role_resources → Resource (type=menu/button) - ↘︎ - role_permissions → Permission \ No newline at end of file diff --git a/app/admin/controller/AuthController_bak.php b/app/admin/controller/AuthController_bak.php deleted file mode 100644 index f6feeb4..0000000 --- a/app/admin/controller/AuthController_bak.php +++ /dev/null @@ -1,83 +0,0 @@ -authServer = $auth; - $this->resourceServer = $res; - } - - // 浏览器授权页 - public function authorize(ServerRequestInterface $request) - { - // 如果未登录,跳转登录页(自行实现) - // 登录后,确认授权 -> issue auth code -> 重定向回 client - - // …未登录重定向到登录页,登录后调用: - // $authRequest = $this->authServer->validateAuthorizationRequest($request); - // …用户同意授权… - // return $this->authServer->completeAuthorizationRequest($authRequest, new Psr7Response()); - } - - // 授权码换 Token - public function token(ServerRequestInterface $request) - { - return $this->authServer->respondToAccessTokenRequest($request, new Psr7Response()); - } - - // 获取用户信息 - public function userinfo(ServerRequestInterface $request) - { - $psr = $this->resourceServer->validateAuthenticatedRequest($request); - $userId = $psr->getAttribute('oauth_user_id'); - // 根据 $userId 查询用户表返回 - // return json(['id' => $userId, 'username' => 'demo']); - - // 用 Webman 的 support\Response 返回 JSON 更方便 - return json([ - 'id' => $userId, - 'username' => User::find($userId)->username, - ]); - } - - - public function info(Request $request) - { - $userId = $request->session()->get('user_id'); - if (!$userId) { - return json(['code'=>401,'msg'=>'未登录']); - } - - $user = User::with('roles')->find($userId); - - return json([ - 'code' => 0, - 'data' => [ - 'user' => [ - 'id' => $user->id, - 'username' => $user->username, - 'roles' => $user->roles->pluck('name')->all(), - ], - 'menus' => AuthService::getMenuTree($userId), - 'permissions' => AuthService::getPermissionCodes($userId), - ], - 'msg' => 'ok', - ]); - } -} diff --git a/app/admin/controller/RoleController.php b/app/admin/controller/RoleController.php new file mode 100644 index 0000000..59ccb54 --- /dev/null +++ b/app/admin/controller/RoleController.php @@ -0,0 +1,98 @@ +all(); + $result = (new RoleService())->getRoleList($params); + // 定义后处理器 + $postProcessor = function($formattedData) { + if (isset($formattedData['created_at'])) + $formattedData['created_at'] = date('Y-m-d H:i:s', $formattedData['created_at']); + if (isset($formattedData['updated_at'])) + $formattedData['updated_at'] = date('Y-m-d H:i:s', $formattedData['updated_at']); + return $formattedData; + }; + return $this->success($this->transformData($result, $postProcessor), '角色列表获取成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 获取角色详情 + */ + public function detail(Request $request) + { + try { + $roleId = $request->get('role_id'); + if (!$roleId) { + return $this->error('缺少角色ID', 422); + } + $result = (new RoleService())->getRoleDetail($roleId); + return $this->success($result, '角色详情获取成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 增加角色 + */ + public function create(Request $request) + { + try { + $data = $this->validate(new RoleValidate(), $request->post(), 'role_create'); + $result = (new RoleService())->createRole($data); + return $this->success($result, '角色创建成功'); + } catch (\think\exception\ValidateException $e) { + return $this->error($e->getError(), 422); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 修改角色 + */ + public function update(Request $request) + { + try { + $data = $this->validate(new RoleValidate(), $request->post(), 'role_update'); + $result = (new RoleService())->updateRole($data); + return $this->success($result, '角色更新成功'); + } catch (\think\exception\ValidateException $e) { + return $this->error($e->getError(), 422); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 删除角色 + */ + public function delete(Request $request) + { + try { + $roleId = $request->get('role_id'); + if (!$roleId) { + return $this->error('缺少角色ID', 422); + } + (new RoleService())->deleteRole($roleId); + return $this->success([], '角色删除成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } +} diff --git a/app/common/controller/AuthController.php b/app/common/controller/AuthController.php index 4129ee0..02676e8 100644 --- a/app/common/controller/AuthController.php +++ b/app/common/controller/AuthController.php @@ -14,6 +14,8 @@ class AuthController extends BaseController */ protected $noNeedHeader = ['feishuAuth']; + protected $noNeedLogin = ['feishuScope', 'login', 'register', 'feishuAuth']; + // 调试 public function feishuAuth(Request $request) @@ -58,46 +60,56 @@ class AuthController extends BaseController } } - public function verify(Request $request) - { - try { - $token = $request->post('access_token') ?: $request->header('authorization'); - $systemCode = $request->post('system_code'); - if (!$token || !$systemCode) return $this->error('参数缺失', 422); - $payload = (new AuthService())->verifyToken($token, $systemCode); - return $this->success($payload, '验证成功'); - } catch (\Throwable $e) { - return $this->error($e->getMessage() ?: '无效或过期的token', 401); - } - } - public function refresh(Request $request) { try { - $refresh_token = $request->post('refresh_token'); - $systemCode = $request->post('system_code'); - if (!$refresh_token || !$systemCode) return $this->error('参数缺失', 422); - $result = (new AuthService())->refreshToken($refresh_token, $systemCode); + $data = $this->requestData($request); + if (!$data['refresh_token'] || !$data['system_code']) return $this->error('参数缺失', 422); + $result = (new AuthService())->refreshToken($data['refresh_token'], $data['system_code']); return $this->success($result, '刷新成功'); } catch (\Throwable $e) { return $this->error($e->getMessage(), 401); } } - public function logout(Request $request) - { - $token = $request->post('access_token') ?: $request->header('Authorization'); - $systemCode = $request->post('system_code') ?: $request->header('system_code'); - (new AuthService())->logout($token, $systemCode); - return $this->success([], '已登出'); - } - public function blockToken(Request $request) - { - $token = $request->post('token'); - $systemCode = $request->post('system_code'); - if (!$token || !$systemCode) return $this->error('参数缺失', 422); - (new AuthService())->blockToken($token, $systemCode); - return $this->success([], '已强制停用该token'); - } + // public function perm(Request $request) + // { + // try { + // $data = $this->requestData($request); + // if (!$data['access_token'] || !$data['system_code']) return $this->error('参数缺失', 422); + // $result = (new AuthService())->logout($data['access_token'], $data['system_code']); + // return $this->success($result, '已登出'); + // } catch (\Throwable $e) {} + // } + + // public function verify(Request $request) + // { + // try { + // $token = $request->post('access_token') ?: $request->header('authorization'); + // $systemCode = $request->post('system_code'); + // if (!$token || !$systemCode) return $this->error('参数缺失', 422); + // $payload = (new AuthService())->verifyToken($token, $systemCode); + // return $this->success($payload, '验证成功'); + // } catch (\Throwable $e) { + // return $this->error($e->getMessage() ?: '无效或过期的token', 401); + // } + // } + + // public function logout(Request $request) + // { + // $token = $request->post('access_token') ?: $request->header('Authorization'); + // $systemCode = $request->post('system_code') ?: $request->header('system_code'); + // (new AuthService())->logout($token, $systemCode); + // return $this->success([], '已登出'); + // } + + // public function blockToken(Request $request) + // { + // $token = $request->post('token'); + // $systemCode = $request->post('system_code'); + // if (!$token || !$systemCode) return $this->error('参数缺失', 422); + // (new AuthService())->blockToken($token, $systemCode); + // return $this->success([], '已强制停用该token'); + // } } \ No newline at end of file diff --git a/app/common/controller/BaseController.php b/app/common/controller/BaseController.php index 0fefb82..2dbf636 100644 --- a/app/common/controller/BaseController.php +++ b/app/common/controller/BaseController.php @@ -4,12 +4,13 @@ namespace app\common\controller; use support\exception\BusinessException; use app\common\traits\ResponseTrait; +use app\common\traits\DataTransformer; use think\Validate; use support\Request; -abstract class BaseController +class BaseController { - use ResponseTrait; + use ResponseTrait, DataTransformer; protected mixed $validator = null; diff --git a/app/common/controller/UserController.php b/app/common/controller/UserController.php new file mode 100644 index 0000000..efab4a1 --- /dev/null +++ b/app/common/controller/UserController.php @@ -0,0 +1,25 @@ +user->uid; + $result = (new UserService())->getUserInfo($uid); + return $this->success($result, '获取用户信息'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + +} \ No newline at end of file diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index 3c84694..ccdbf16 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -7,7 +7,6 @@ use app\common\model\CorpFeishuApp; use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; -use app\common\model\UserFeishuApp; use app\common\model\UserSn; use app\common\utils\Feishu; use app\common\utils\Jwt; @@ -46,87 +45,83 @@ class AuthLogic return $this->generateTokensAndReturn($user, $system, $data); } - /** - * 扫码登录 - */ - public function qrCodeLogin(array $data): array - { - $userId = $this->validateQrCodeToken($data['qr_code_token']); - $user = User::find($userId); - - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - - return $this->generateTokensAndReturn($user, $system, $data); - } - /** * 注册 */ public function register(array $data) { - // 获取可用用户编号 - $sn = $this->getAvailableUserSn(); - $data['sn'] = $sn->user_sn; + try { + // 获取可用用户编号 + $sn = $this->getAvailableUserSn(); + $data['sn'] = $sn->user_sn; - // 对密码进行哈希处理 - $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + // 对密码进行哈希处理 + $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); - // 获取企业ID - $corpInfo = $this->validateCorp($data['corp_abbr']); - $data['corp_id'] = $corpInfo->id; + // 获取企业ID + $corpInfo = $this->validateCorp($data['corp_abbr']); + $data['corp_id'] = $corpInfo->id; - // 处理账户 - $data = $data + normalizeAccount($data['account']); - - // 创建用户记录 - $user = $this->createUser($data); + // 处理账户 + $data = $data + normalizeAccount($data['account']); + + // 创建用户记录 + $user = $this->createUser($data); - // 更新用户编号的用户 ID - $this->updateUserId($user->id, $sn->user_sn); + // 更新用户编号的用户 ID + $this->updateUserId($user->id, $sn->user_sn); - $system = $this->validateSystem($data['system_code']); - return $this->generateTokensAndReturn($user, $system, $data); + $system = $this->validateSystem($data['system_code']); + return $this->generateTokensAndReturn($user, $system, $data); + } catch (\Throwable $e) { + throw $e; + } } - /** ----------- 内部方法 ------------- **/ - /** * 查找或创建飞书用户 */ private function getOrCreateUser(array $feishuUserInfo) { - $userFeishuApp = UserFeishuApp::firstOrCreate([ - 'name' => $feishuUserInfo['en_name'], - 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), - 'union_id' => $feishuUserInfo['union_id'], - 'user_id' => $feishuUserInfo['user_id'], - 'open_id' => $feishuUserInfo['open_id'], - 'tenant_key' => $feishuUserInfo['tenant_key'] - ]); - - if ($userFeishuApp->uid) { - $user = User::find($userFeishuApp->uid); - $user->isLogin = true; - return $user; - } else { - $user = User::where('phone', $userFeishuApp->mobile)->first(); - $user->isLogin = true; + try { + $phone = $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''); + $user = User::where('phone', $phone)->where('corp_id', $feishuUserInfo['corp_id'])->first(); + if (!$user) { - $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); + // 获取编号 + $userSn = $this->getAvailableUserSn(); + // 创建用户 + $user = User::create([ + 'name' => $feishuUserInfo['en_name'], + 'avatar' => $feishuUserInfo['avatar_middle'], + 'phone' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'], + 'corp_id' => $feishuUserInfo['corp_id'], + 'password' => password_hash($userSn->user_sn, PASSWORD_DEFAULT) + ]); + // 更新使用 + $this->updateUserId($user->id, $userSn->user_sn); + // 更新编号 + if (!$user->update(['sn'=>$userSn->user_sn])) { + throw new BusinessException('编号绑定失败'); + } + // 是否登陆 $user->isLogin = false; - } - $userFeishuApp->uid = $user->id; - $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; - if (!$userFeishuApp->save()) { - throw new BusinessException('绑定用户飞书记录失败'); + } else { + $user->update([ + 'union_id' => $feishuUserInfo['union_id'], + 'user_id' => $feishuUserInfo['user_id'], + 'open_id' => $feishuUserInfo['open_id'], + 'tenant_key' => $feishuUserInfo['tenant_key'] + ]); } return $user; - } + } catch (\Throwable $e) { + throw $e; + } } /** @@ -137,38 +132,6 @@ class AuthLogic return isset($mobile) ? str_replace('+86', '', $mobile) : ''; } - /** - * 创建飞书用户 - */ - private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) - { - // 1. 获取适用的用户编号 - $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$userSn) { - throw new BusinessException('未找到适用编号'); - } - - // 2. 创建用户 - $user = new User(); - $user->sn = $userSn->user_sn; - $user->name = $feishuUserInfo['en_name']; - $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; - $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); - $user->phone = $userFeishuApp->mobile; - $user->email = ''; - $user->system_code = $feishuUserInfo['system_code']; - $user->corp_id = $feishuUserInfo['corp_id']; - - if (!$user->save()) { - throw new BusinessException('用户创建失败'); - } - - // 更新使用状态 - $this->updateUserId($user->id, $userSn->user_sn); - - return $user; - } - /** * 生成并记录token,返回包含token的响应数据 */ @@ -295,7 +258,8 @@ class AuthLogic public function feishuAppInfo(string $corpAbbr, string $systemCode) { $corpInfo = $this->validateCorp($corpAbbr); - $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); + $systemInfo = $this->validateSystem($systemCode); + $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemInfo->id); return [ 'app_id' => $fsAppInfo->app_id, 'app_secret' => $fsAppInfo->app_secret, @@ -303,7 +267,8 @@ class AuthLogic 'scope' => $fsAppInfo->scopes, 'state' => $fsAppInfo->state, 'abbr' => $corpAbbr, - 'corp_id' => $corpInfo->id + 'corp_id' => $corpInfo->id, + 'system_id' => $systemInfo->id ]; } @@ -349,9 +314,9 @@ class AuthLogic /** * 验证飞书应用信息 */ - private function validateFeishuApp(int $corpId, string $systemCode) + private function validateFeishuApp(int $corpId, int $systemId) { - $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first(); + $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first(); if (!$feishuAppInfo) { throw new BusinessException('飞书应用不存在', 403); } @@ -371,18 +336,6 @@ class AuthLogic } } - /** - * 验证扫码令牌 - */ - private function validateQrCodeToken(string $qrCodeToken) - { - $userId = Redis::get("qr_code_token:{$qrCodeToken}"); - if (!$userId) { - throw new BusinessException('扫码令牌无效或已过期', 401); - } - return $userId; - } - /** * 获取可用的用户编号 * @@ -463,4 +416,88 @@ class AuthLogic Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration')); return $refreshToken; } + + + /** + * 刷新token + * @param string $refreshToken + * @param string|null $systemCode 可选,指定系统 + * @return array + */ + public function refreshToken(string $refreshToken, ?string $systemCode = null): array + { + if (!$systemCode) throw new BusinessException('缺少system_code', 422); + + // 校验refresh_token + $userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}"); + if (!$userId) { + throw new BusinessException('refresh_token无效或已过期', 401); + } + + $validToken = Redis::get("refresh_token:{$userId}:{$systemCode}"); + if ($validToken !== $refreshToken) { + throw new BusinessException('refresh_token无效或已过期', 401); + } + + $user = User::find($userId); + if (!$user || $user->status != 1) { + throw new BusinessException('用户无效', 403); + } + + $system = $this->validateSystem($systemCode); + $this->validateSystemPermission($user, $system); + + $payload = $this->buildJwtPayload($user, $system); + $token = $this->generateAccessToken($payload); + $refreshToken = $this->generateRefreshToken($user->id, $systemCode); + $this->recordUserToken($user->id, $token, $systemCode); + + return [ + 'access_token' => $token, + 'refresh_token' => $refreshToken, + 'expires_in' => config('jwt.jwt_expiration'), + // 'payload' => $payload, + ]; + } + + /** + * 登出/失效token + */ + public function logout(?string $token, ?string $systemCode = null) + { + if ($token) { + Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); + $payload = null; + try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} + $userId = $payload['uid'] ?? null; + $aud = $payload['aud'] ?? null; + $sc = $systemCode ?? $aud; + if ($userId && $sc) { + Redis::sRem("user_tokens:{$userId}:{$sc}", $token); + } + Redis::del("user_token_meta:{$token}"); + UserTokenLog::where('token', $token)->update(['is_valid'=>0]); + } + } + + /** + * 强制拉黑token + */ + public function blockToken(string $token, ?string $systemCode = null) + { + if (!$token) { + throw new BusinessException('token不能为空', 422); + } + Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); + $payload = null; + try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} + $userId = $payload['uid'] ?? null; + $aud = $payload['aud'] ?? null; + $sc = $systemCode ?? $aud; + if ($userId && $sc) { + Redis::sRem("user_tokens:{$userId}:{$sc}", $token); + } + Redis::del("user_token_meta:{$token}"); + UserTokenLog::where('token', $token)->update(['is_valid'=>0]); + } } \ No newline at end of file diff --git a/app/common/logic/BaseLogic.php b/app/common/logic/BaseLogic.php new file mode 100644 index 0000000..94beff3 --- /dev/null +++ b/app/common/logic/BaseLogic.php @@ -0,0 +1,86 @@ +paginate($params['per_page'], ['*'], 'page', $params['page']); + } + + return $query->get(); + } + + /** + * 获取单个记录 + */ + public function getDetail(int $id, string $modelClass) + { + $record = $modelClass::find($id); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + return $record; + } + + /** + * 创建记录 + */ + public function create(array $data, string $modelClass) + { + $model = new $modelClass; + $model->fill($data); // 根据字段填充数据 + try { + $model->save(); + } catch (\Throwable $e) { + throw new BusinessException("创建记录失败: " . $e->getMessage(), 500); + } + + return $model; + } + + /** + * 更新记录 + */ + public function update(array $data, string $modelClass) + { + $record = $modelClass::find($data['id']); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + $record->update($data); + return $record; + } + + /** + * 删除记录 + */ + public function delete(int $id, string $modelClass) + { + $record = $modelClass::find($id); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + try { + $record->delete(); + } catch (\Throwable $e) { + throw new BusinessException("删除记录失败: " . $e->getMessage(), 500); + } + + return true; + } +} diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php new file mode 100644 index 0000000..b4fe819 --- /dev/null +++ b/app/common/logic/PermLogic.php @@ -0,0 +1,109 @@ +find($userId); + if (!$user) { + return collect(); + } + + $all = collect(); + foreach ($user->roles as $role) { + $all = $all->merge($role->resources); + } + $all = $all->merge($user->resources); + return $all->unique('id')->values(); + } + + /** + * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单component处理 + * @param \Illuminate\Support\Collection $resources + * @param int $parentId + * @param string $parentPath + * @return array + */ + public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array + { + $tree = []; + foreach ($resources as $node) { + if ($node->parent_id !== $parentId) continue; + + // 拼接完整路径 + $currentPath = $parentPath; + if ($currentPath && substr($currentPath, -1) === '/') { + $currentPath = rtrim($currentPath, '/'); + } + if (!empty($node->url)) { + if (!$currentPath) { + $currentPath = $node->url; + } else { + $url = ltrim($node->url, '/'); + $currentPath = $currentPath . '/' . $url; + } + } + + // 子节点递归 + $children = []; + if ($node->type === 'directory') { + $children = $this->buildTree($resources, $node->id, $currentPath); + } + + // 权限 + $authList = $resources + ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) + ->map(fn($r) => [ + 'id' => $r->id, + 'title' => $r->name, + 'auth_mark' => $r->code, + ])->values()->all(); + + // 判断是否有子级菜单(非权限) + $hasChildrenMenu = collect($resources)->where('parent_id', $node->id) + ->whereIn('type', ['directory', 'menu'])->isNotEmpty(); + + // component 字段规则: + // 1. 存在子菜单,固定 '/index/index' + // 2. 否则为当前完整路径(或其它你想要的) + $componentValue = $hasChildrenMenu ? '/index/index' : $currentPath; + + $tree[] = [ + 'id' => $node->id, + 'name' => $node->name, + 'path' => $node->url, + 'component' => $componentValue, + 'meta' => [ + 'title' => $node->name, + 'icon' => $node->icon, + 'keepAlive' => true, + 'authList' => $authList, + 'openType' => $node->open_type, + ], + 'children' => $children, + ]; + } + return $tree; + } + + /** + * 获取用户所有权限code + */ + public function getPermissionCodes(int $userId): array + { + $resources = $this->getUserResources($userId); + return $resources + ->filter(fn($res) => $res->type === 'permission') + ->pluck('code') + ->unique() + ->values() + ->all(); + } +} \ No newline at end of file diff --git a/app/common/logic/RoleLogic.php b/app/common/logic/RoleLogic.php new file mode 100644 index 0000000..5cb3ae7 --- /dev/null +++ b/app/common/logic/RoleLogic.php @@ -0,0 +1,93 @@ +where('system_id', $params['system_id']); + } + if (isset($params['status'])) { + $query->where('status', $params['status']); + } + + // 添加分页 + $roles = $query->paginate($params['per_page'] ?? 15); + + return $roles; + } + + /** + * 获取角色详情 + */ + public function getRoleDetail(int $roleId) + { + $role = Role::find($roleId); + if (!$role) { + throw new BusinessException('角色不存在', 404); + } + + return $role; + } + + /** + * 创建角色 + */ + public function createRole(array $data) + { + $role = new Role(); + $role->fill($data); // 根据字段填充数据 + try { + $role->save(); + } catch (\Throwable $e) { + Log::error('创建角色失败:'.$e->getMessage()); + throw new BusinessException('创建角色失败', 500); + } + + return $role; + } + + /** + * 更新角色 + */ + public function updateRole(array $data) + { + $role = Role::find($data['id']); + if (!$role) { + throw new BusinessException('角色不存在', 404); + } + + $role->update($data); // 更新字段 + return $role; + } + + /** + * 删除角色 + */ + public function deleteRole(int $roleId) + { + $role = Role::find($roleId); + if (!$role) { + throw new BusinessException('角色不存在', 404); + } + + try { + $role->delete(); + } catch (\Throwable $e) { + Log::error('删除角色失败:'.$e->getMessage()); + throw new BusinessException('删除角色失败', 500); + } + } +} diff --git a/app/common/logic/UserLogic.php b/app/common/logic/UserLogic.php new file mode 100644 index 0000000..1d6dc93 --- /dev/null +++ b/app/common/logic/UserLogic.php @@ -0,0 +1,27 @@ +status != 1) { + throw new BusinessException('账号已禁用', 403); + } + + return $user; + } +} \ No newline at end of file diff --git a/app/common/model/Role.php b/app/common/model/Role.php index 3f51b37..ea84200 100644 --- a/app/common/model/Role.php +++ b/app/common/model/Role.php @@ -41,6 +41,8 @@ class Role extends Model * @var bool */ public $timestamps = true; + + protected $dateFormat = 'U'; // 角色下所有用户 public function users() diff --git a/app/common/model/User.php b/app/common/model/User.php index 23e8b1c..4709b44 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -58,8 +58,12 @@ class User extends Model 'phone', 'email', 'status', - 'system_code', - 'corp_id' + 'corp_id', + 'user_type', + 'union_id', + 'user_id', + 'open_id', + 'tenant_key' ]; // 用户所有角色 diff --git a/app/common/model/UserFeishuApp_bak.php b/app/common/model/UserFeishuApp_bak.php new file mode 100644 index 0000000..9aa06c0 --- /dev/null +++ b/app/common/model/UserFeishuApp_bak.php @@ -0,0 +1,69 @@ + $logic->feishuLogin($data), - 'qrcode' => $logic->qrCodeLogin($data), + // 'qrcode' => $logic->qrCodeLogin($data), default => $logic->uPwdLogin($data), }; } @@ -86,4 +86,13 @@ class AuthService { (new AuthLogic())->blockToken($token, $systemCode); } + + + // public function perms() + // { + // $permService = new PermService(); + // $menuTree = $permService->getMenuTree($uid); + // $permissionCodes = $permService->getPermissionCodes($uid); + // return (new PermService())->getMenuTree($this->userId) + // } } \ No newline at end of file diff --git a/app/common/service/PermService.php b/app/common/service/PermService.php index 076a4bd..8f8770c 100644 --- a/app/common/service/PermService.php +++ b/app/common/service/PermService.php @@ -2,11 +2,17 @@ namespace app\common\service; -use app\common\model\User; +use app\common\logic\PermLogic; use support\Cache; class PermService { + protected $logic; + + public function __construct() + { + $this->logic = (new PermLogic()); + } // 用户资源缓存key protected static function cacheKey(int $userId): string @@ -17,92 +23,42 @@ class PermService /** * 获取用户所有资源(合并角色+直接分配),结果缓存 */ - public static function getUserResources(int $userId) + public function getUserResources(int $userId) { $cacheKey = self::cacheKey($userId); $cached = Cache::get($cacheKey); if ($cached !== null) { return $cached; } - - $user = User::with(['roles.resources', 'resources'])->find($userId); - if (!$user) { - return collect(); - } - - $all = collect(); - foreach ($user->roles as $role) { - $all = $all->merge($role->resources); - } - $all = $all->merge($user->resources); - $unique = $all->unique('id')->values(); - - Cache::set($cacheKey, $unique, 3600); - return $unique; + // 调用 Logic 层获取资源 + $resources = $this->logic->getUserResources($userId); + // 缓存结果 + Cache::set($cacheKey, $resources, 3600); + return $resources; } /** * 构建目录/菜单树,节点包含权限与打开方式 */ - public static function getMenuTree(int $userId): array + public function getMenuTree(int $userId): array { - $resources = self::getUserResources($userId); - $menus = $resources - ->filter(fn($r) => in_array($r->type, ['directory','menu'])) - ->values(); - return self::buildTree($menus, $resources); - } - - protected static function buildTree($nodes, $allResources, int $parentId = 0): array - { - $tree = []; - foreach ($nodes as $node) { - if ($node->parent_id !== $parentId) continue; - - $children = self::buildTree($nodes, $allResources, $node->id); - - $authList = $allResources - ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) - ->map(fn($r) => [ - 'id' => $r->id, - 'title' => $r->name, - 'auth_mark' => $r->code, - ])->values()->all(); - - $tree[] = [ - 'id' => $node->id, - 'name' => $node->name, - 'path' => $node->url, - 'meta' => [ - 'title' => $node->name, - 'icon' => $node->icon, - 'keepAlive' => true, - 'authList' => $authList, - 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' - ], - 'children' => $children, - ]; - } - return $tree; + // 调用 Logic 层处理菜单构建 + $resources = $this->getUserResources($userId); + return $this->logic->buildTree($resources); } /** * 获取用户所有权限code */ - public static function getPermissionCodes(int $userId): array + public function getPermissionCodes(int $userId): array { - return self::getUserResources($userId) - ->filter(fn($res) => $res->type === 'permission') - ->pluck('code') - ->unique() - ->values() - ->all(); + return $this->logic->getPermissionCodes($userId); } /** * 清除资源相关缓存 */ - public static function clearCache(int $userId): void + public function clearCache(int $userId): void { Cache::delete(self::cacheKey($userId)); } diff --git a/app/common/service/PermService_bak.php b/app/common/service/PermService_bak.php new file mode 100644 index 0000000..a2dcc27 --- /dev/null +++ b/app/common/service/PermService_bak.php @@ -0,0 +1,109 @@ +find($userId); + if (!$user) { + return collect(); + } + + $all = collect(); + foreach ($user->roles as $role) { + $all = $all->merge($role->resources); + } + $all = $all->merge($user->resources); + $unique = $all->unique('id')->values(); + + Cache::set($cacheKey, $unique, 3600); + return $unique; + } + + /** + * 构建目录/菜单树,节点包含权限与打开方式 + */ + public static function getMenuTree(int $userId): array + { + $resources = self::getUserResources($userId); + $menus = $resources + ->filter(fn($r) => in_array($r->type, ['directory','menu'])) + ->values(); + return self::buildTree($menus, $resources); + } + + protected static function buildTree($nodes, $allResources, int $parentId = 0): array + { + $tree = []; + foreach ($nodes as $node) { + if ($node->parent_id !== $parentId) continue; + + $children = self::buildTree($nodes, $allResources, $node->id); + + $authList = $allResources + ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) + ->map(fn($r) => [ + 'id' => $r->id, + 'title' => $r->name, + 'auth_mark' => $r->code, + ])->values()->all(); + + $tree[] = [ + 'id' => $node->id, + 'name' => $node->name, + 'path' => $node->url, + 'meta' => [ + 'title' => $node->name, + 'icon' => $node->icon, + 'keepAlive' => true, + 'authList' => $authList, + 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' + ], + 'children' => $children, + ]; + } + return $tree; + } + + /** + * 获取用户所有权限code + */ + public static function getPermissionCodes(int $userId): array + { + return self::getUserResources($userId) + ->filter(fn($res) => $res->type === 'permission') + ->pluck('code') + ->unique() + ->values() + ->all(); + } + + /** + * 清除资源相关缓存 + */ + public static function clearCache(int $userId): void + { + Cache::delete(self::cacheKey($userId)); + } +} \ No newline at end of file diff --git a/app/common/service/RoleService.php b/app/common/service/RoleService.php new file mode 100644 index 0000000..24c14cf --- /dev/null +++ b/app/common/service/RoleService.php @@ -0,0 +1,55 @@ +roleLogic = (new RoleLogic()); + } + + /** + * 获取角色列表 + */ + public function getRoleList(array $params) + { + return $this->roleLogic->getRoleList($params); + } + + /** + * 获取角色详情 + */ + public function getRoleDetail(int $roleId) + { + return $this->roleLogic->getRoleDetail($roleId); + } + + /** + * 创建角色 + */ + public function createRole(array $data) + { + return $this->roleLogic->createRole($data); + } + + /** + * 更新角色 + */ + public function updateRole(array $data) + { + return $this->roleLogic->updateRole($data); + } + + /** + * 删除角色 + */ + public function deleteRole(int $roleId) + { + return $this->roleLogic->deleteRole($roleId); + } +} diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 99b80d5..98375bc 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -2,19 +2,39 @@ namespace app\common\service; -use app\model\User; +use app\common\logic\UserLogic; class UserService { - public static function getInfo($userId) + + protected $logic; + + public function __construct() { - $user = User::with('roles')->find($userId); - if (!$user) return null; + $this->logic = (new UserLogic()); + } + + /** + * 获取用户信息(包括菜单和权限) + */ + public function getUserInfo(int $uid) + { + $user = $this->logic->userInfo($uid); + $permService = new PermService(); + $menuTree = $permService->getMenuTree($uid); + $permissionCodes = $permService->getPermissionCodes($uid); return [ - 'id' => $user->id, - 'username' => $user->username, - 'roles' => $user->roles->pluck('name')->toArray(), - // 可扩展其他字段 + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + 'avatar' => $user->avatar, + 'phone' => $user->phone, + 'email' => $user->email, + 'status' => $user->status, + 'roles' => $user->roles->pluck('name') + ], + 'menu' => $menuTree, + 'permissions' => $permissionCodes ]; } -} +} \ No newline at end of file diff --git a/app/common/traits/DataTransformer.php b/app/common/traits/DataTransformer.php new file mode 100644 index 0000000..42faf81 --- /dev/null +++ b/app/common/traits/DataTransformer.php @@ -0,0 +1,135 @@ + $field; + + if ($rawData instanceof LengthAwarePaginator) { + $formatted = $this->formatPaginated($rawData, $postProcessor, $fieldFormatter); + } elseif ($rawData instanceof Model) { + $formatted = $this->formatModel($rawData, $fieldFormatter); + } elseif ($rawData instanceof Collection) { + $formatted = $this->formatCollection($rawData, $fieldFormatter); + } elseif (is_array($rawData)) { + $formatted = $this->formatArray($rawData, $fieldFormatter); + } else { + $formatted = (array) $rawData; + } + + if (is_callable($postProcessor)) { + $formatted = $postProcessor($formatted); + } + + return $formatted; + } catch (\Throwable $e) { + return [ + 'error' => true, + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + ]; + } + } + + /** + * 格式化分页数据:将每条记录也进行 transformData 递归处理 + * + * @param LengthAwarePaginator $paginator + * @param callable|null $postProcessor + * @param callable $fieldFormatter + * @return array + */ + protected function formatPaginated( + LengthAwarePaginator $paginator, + ?callable $postProcessor, + callable $fieldFormatter + ): array { + $list = $paginator->getCollection()->map(function ($item) use ($postProcessor, $fieldFormatter) { + return $this->transformData($item, $postProcessor, $fieldFormatter); + })->toArray(); + + return [ + 'records' => $list, + 'total' => $paginator->total(), + 'current' => $paginator->currentPage(), + 'size' => $paginator->perPage() + ]; + } + + /** + * 格式化 Eloquent 模型:递归嵌套关联 + * + * @param Model $model + * @param callable $fieldFormatter + * @return array + */ + protected function formatModel(Model $model, callable $fieldFormatter): array + { + $formatted = []; + + // 主表字段 + foreach ($model->getAttributes() as $key => $value) { + $formatted[$fieldFormatter($key)] = $value; + } + + // 关联数据,递归嵌套 + foreach ($model->getRelations() as $relation => $relationData) { + if ($relationData instanceof Model) { + // 单个关联模型 + $formatted[$relation] = $this->formatModel($relationData, $fieldFormatter); + } elseif ($relationData instanceof Collection) { + // 关联集合 + $formatted[$relation] = $relationData + ->map(fn($item) => $this->transformData($item, null, $fieldFormatter)) + ->toArray(); + } + } + + return $formatted; + } + + /** + * 格式化集合 + * + * @param Collection $collection + * @param callable $fieldFormatter + * @return array + */ + protected function formatCollection(Collection $collection, callable $fieldFormatter): array + { + return $collection->map(fn($item) => $this->transformData($item, null, $fieldFormatter)) + ->toArray(); + } + + /** + * 格式化普通数组 + * + * @param array $array + * @param callable $fieldFormatter + * @return array + */ + protected function formatArray(array $array, callable $fieldFormatter): array + { + return array_map(fn($item) => $this->transformData($item, null, $fieldFormatter), $array); + } +} \ No newline at end of file diff --git a/app/common/validate/RoleValidate.php b/app/common/validate/RoleValidate.php new file mode 100644 index 0000000..330fae6 --- /dev/null +++ b/app/common/validate/RoleValidate.php @@ -0,0 +1,32 @@ + 'require|integer', + 'system_id' => 'require|integer', + 'name' => 'require|max:64', + 'desc' => 'max:128', + 'sort' => 'integer', + 'status' => 'in:0,1', + ]; + + protected $message = [ + 'id.require' => '角色ID不能为空', + 'system_id.require' => '系统ID不能为空', + 'name.require' => '角色名称不能为空', + 'name.max' => '角色名称不能超过64个字符', + 'desc.max' => '描述不能超过128个字符', + 'sort.integer' => '排序为数字', + 'status.in' => '状态值必须为0或1', + ]; + + protected $scene = [ + 'role_create' => ['system_id', 'name', 'desc', 'sort'], + 'role_update' => ['id', 'system_id', 'name', 'desc', 'sort', 'status'], + ]; +} diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 2ea3432..f0feb2c 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -6,6 +6,8 @@ use support\Request; class IndexController { + + protected $noNeedLogin = ['index', 'view']; public function index(Request $request) { diff --git a/app/functions.php b/app/functions.php index 14d5e4a..2a05e3f 100644 --- a/app/functions.php +++ b/app/functions.php @@ -2,8 +2,6 @@ /** * Here is your custom functions. */ - -use support\exception\BusinessException; /** * 标准化账号参数 @@ -11,54 +9,69 @@ use support\exception\BusinessException; * @param string $account 用户输入的账号(可能是手机号或邮箱) * @return array 返回 ['phone' => $account] 或 ['email' => $account] */ -function normalizeAccount(string $account): array -{ - // 使用FILTER_VALIDATE_EMAIL验证邮箱格式 - if (filter_var($account, FILTER_VALIDATE_EMAIL)) { - return ['email' => $account]; - } - - // 清理手机号中的非数字字符 - $cleanPhone = preg_replace('/[^0-9]/', '', $account); - - // 验证手机号格式(简单版:11位数字) - if (preg_match('/^1[3-9]\d{9}$/', $cleanPhone)) { +if (!function_exists('normalizeAccount')) { + function normalizeAccount(string $account): array + { + // 使用FILTER_VALIDATE_EMAIL验证邮箱格式 + if (filter_var($account, FILTER_VALIDATE_EMAIL)) { + return ['email' => $account]; + } + + // 清理手机号中的非数字字符 + $cleanPhone = preg_replace('/[^0-9]/', '', $account); + + // 验证手机号格式(简单版:11位数字) + if (preg_match('/^1[3-9]\d{9}$/', $cleanPhone)) { + return ['phone' => $cleanPhone]; + } + + // 如果既不是邮箱也不是手机号,根据业务需求处理 + // 这里默认按手机号处理(可根据实际需求调整) return ['phone' => $cleanPhone]; } - - // 如果既不是邮箱也不是手机号,根据业务需求处理 - // 这里默认按手机号处理(可根据实际需求调整) - return ['phone' => $cleanPhone]; } /** * 公共html页面 */ -function htmlpage() -{ - static $html; - if (!$html) { - $html = file_get_contents(public_path('mzl.html')); +if (!function_exists('htmlpage')) { + function htmlpage() + { + static $html; + if (!$html) { + $html = file_get_contents(public_path('mzl.html')); + } + return $html; } - return $html; } - /** * 获取配置 */ -function getConfig($filePath = null) -{ - // 构建文件路径 - $config_file = app_path($filePath); - // 检查文件是否存在 - if (!file_exists($config_file)) { - return []; +if (!function_exists('getConfig')) { + function getConfig($filePath = null) + { + // 构建文件路径 + $config_file = app_path($filePath); + // 检查文件是否存在 + if (!file_exists($config_file)) { + return []; + } + // 包含文件并获取返回的数组 + $config = include $config_file; + // 验证是否为数组 + if (!is_array($config)) { + return []; + } + return $config; } - // 包含文件并获取返回的数组 - $config = include $config_file; - // 验证是否为数组 - if (!is_array($config)) { - return []; +} + +/* + * 调试用 + */ +if (!function_exists('dpr')) { + function dpr(\Throwable $e) + { + var_dump("=== === 报错打印 === ===", "文件:".$e->getFile(), "行数:".$e->getLine(), "详情:".$e->getMessage(), "=== === 报错打印 === ==="); } - return $config; } \ No newline at end of file diff --git a/app/middleware/AuthCheck.php b/app/middleware/AuthCheck.php index 3e6eb8a..c085901 100644 --- a/app/middleware/AuthCheck.php +++ b/app/middleware/AuthCheck.php @@ -19,7 +19,7 @@ class AuthCheck implements MiddlewareInterface return $handler($request); } else { try { - $authHeader = $request->header('Authorization'); + $authHeader = $request->header('Fdesk-Auth-Token'); if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { $body = [ 'status' => 'fail', diff --git a/app/view/index/qrcode.html b/app/view/index/qrcode.html index 26d1918..fb6f11c 100644 --- a/app/view/index/qrcode.html +++ b/app/view/index/qrcode.html @@ -16,7 +16,7 @@ // const REDIRECT_URI = encodeURIComponent('https://example.com/api/oauth/callback/feishu'); // const STATE = '随机串_防CSRF'; // // 用于跳转获取 code 的 URL - const GOTO = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=cli_a73e1e6406fa501c&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&response_type=code&state=MZL`; + const GOTO = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=cli_a72f13d6c022500d&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&response_type=code&state=MZL`; // const GOTO = `https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a73e1e6406fa501c&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&scope=contact:user.base:readonly contact:contact.base:readonly contact:user.employee_id:readonly im:message:send_as_bot&state=xinyouhe`; // 2. 创建二维码实例 const QRLoginObj = QRLogin({ diff --git a/composer.json b/composer.json index ccf259e..e4e63bb 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "topthink/think-validate": "^3.0", "shopwwi/webman-filesystem": "1.1", "shopwwi/filesystem-oss": "^1.0", - "webman/redis-queue": "^1.3", + "webman/redis-queue": "^2.1", "symfony/translation": "^7.3" }, "suggest": { diff --git a/composer.lock b/composer.lock index 01843a4..b9b58c7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "acb636fcb8774558a04bd674f9f95f55", + "content-hash": "30cc3cbb162022972a21efed9b69d1e1", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -6155,26 +6155,23 @@ }, { "name": "webman/redis-queue", - "version": "v1.3.1", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/webman-php/redis-queue.git", - "reference": "6fd491b19acb006d60931724aa2577a046ccb612" + "reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/6fd491b19acb006d60931724aa2577a046ccb612", - "reference": "6fd491b19acb006d60931724aa2577a046ccb612", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/12e1c9915ec64c91c0c9c255606bd0c7de27334c", + "reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c", + "shasum": "" }, "require": { - "workerman/redis-queue": "^1.0" + "ext-redis": "*", + "php": ">=8.1", + "workerman/redis-queue": "^1.2", + "workerman/webman-framework": "^2.1 || dev-master" }, "type": "library", "autoload": { @@ -6186,9 +6183,9 @@ "description": "Redis message queue plugin for webman.", "support": { "issues": "https://github.com/webman-php/redis-queue/issues", - "source": "https://github.com/webman-php/redis-queue/tree/v1.3.1" + "source": "https://github.com/webman-php/redis-queue/tree/v2.1.0" }, - "time": "2024-02-28T07:21:52+00:00" + "time": "2025-01-24T12:56:01+00:00" }, { "name": "workerman/coroutine", diff --git a/config/middleware.php b/config/middleware.php index cf0335a..ae0ad6e 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -16,6 +16,7 @@ return [ '' => [ \app\middleware\AccessControl::class, - \app\middleware\BasicData::class + \app\middleware\BasicData::class, + \app\middleware\AuthCheck::class ] ]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index db2c831..41c3dff 100644 --- a/config/route.php +++ b/config/route.php @@ -31,12 +31,26 @@ Route::group('/common', function () { Route::any('/login', [app\common\controller\AuthController::class, 'login']); Route::any('/register', [app\common\controller\AuthController::class, 'register']); Route::any('/feishu/scope', [app\common\controller\AuthController::class, 'feishuScope']); + Route::any('/refresh/token', [app\common\controller\AuthController::class, 'refresh']); - - // 调试 + // // 调试 Route::any('/feishu/auth', [app\common\controller\AuthController::class, 'feishuAuth']); }); + + Route::group('/user', function () { + Route::any('/info', [app\common\controller\UserController::class, 'info']); + }); +}); + +Route::group('/admin', function () { + Route::group('/role', function () { + Route::any('/list', [app\admin\controller\RoleController::class, 'list']); + Route::any('/detail', [app\admin\controller\RoleController::class, 'detail']); + Route::any('/create', [app\admin\controller\RoleController::class, 'create']); + Route::any('/update', [app\admin\controller\RoleController::class, 'update']); + Route::any('/delete', [app\admin\controller\RoleController::class, 'delete']); + }); }); diff --git a/config/whitelist.php b/config/whitelist.php index 8071847..d1669b3 100644 --- a/config/whitelist.php +++ b/config/whitelist.php @@ -4,6 +4,6 @@ return [ 'basi_cdata' => [ '/', '/index/index', - // '/index/view' + '/index/view' ] ]; \ No newline at end of file From abd6d5516e90515bebde4634fc4de46d25651877 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 11 Jul 2025 17:15:10 +0800 Subject: [PATCH 037/120] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8E=A5=E5=8F=A3i?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/PresetController.php | 36 +++++++++++++++++++++ app/common/logic/CorpFeishuAppLogic.php | 16 +++++++++ app/common/logic/CorpLogic.php | 14 ++++++++ app/common/service/CorpFeishuAppService.php | 21 ++++++++++++ app/common/service/CorpService.php | 24 ++++++++++++++ config/route.php | 5 +++ config/whitelist.php | 8 +++-- 7 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 app/common/controller/PresetController.php create mode 100644 app/common/logic/CorpFeishuAppLogic.php create mode 100644 app/common/logic/CorpLogic.php create mode 100644 app/common/service/CorpFeishuAppService.php create mode 100644 app/common/service/CorpService.php diff --git a/app/common/controller/PresetController.php b/app/common/controller/PresetController.php new file mode 100644 index 0000000..964b2a6 --- /dev/null +++ b/app/common/controller/PresetController.php @@ -0,0 +1,36 @@ +getList(); + return $this->success($result, '获取公司列表'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + + + public function feishuApp(Request $request) + { + try { + $corpId = $request->get('corp_id'); + $result = (new CorpFeishuAppService())->getFeishuApp($corpId); + return $this->success($result, '获取飞书应用信息'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/common/logic/CorpFeishuAppLogic.php b/app/common/logic/CorpFeishuAppLogic.php new file mode 100644 index 0000000..2073aac --- /dev/null +++ b/app/common/logic/CorpFeishuAppLogic.php @@ -0,0 +1,16 @@ +first(['id', 'corp_id', 'name', 'app_id', 'r_url', 'state']); + } + +} \ No newline at end of file diff --git a/app/common/logic/CorpLogic.php b/app/common/logic/CorpLogic.php new file mode 100644 index 0000000..cac9672 --- /dev/null +++ b/app/common/logic/CorpLogic.php @@ -0,0 +1,14 @@ +orderBy('sort', 'asc')->get(['id', 'name', 'abbr', 'tenant_key']); + } +} \ No newline at end of file diff --git a/app/common/service/CorpFeishuAppService.php b/app/common/service/CorpFeishuAppService.php new file mode 100644 index 0000000..0c10be8 --- /dev/null +++ b/app/common/service/CorpFeishuAppService.php @@ -0,0 +1,21 @@ +logic = (new CorpFeishuAppLogic()); + } + + public function getFeishuApp(int $corpId) + { + return $this->logic->getFeishuApp($corpId); + } +} \ No newline at end of file diff --git a/app/common/service/CorpService.php b/app/common/service/CorpService.php new file mode 100644 index 0000000..7d18d49 --- /dev/null +++ b/app/common/service/CorpService.php @@ -0,0 +1,24 @@ +logic = (new CorpLogic()); + } + + /** + * 获取公司list + */ + public function getList() + { + return $this->logic->getList(); + } +} \ No newline at end of file diff --git a/config/route.php b/config/route.php index 41c3dff..4c9cb10 100644 --- a/config/route.php +++ b/config/route.php @@ -41,6 +41,11 @@ Route::group('/common', function () { Route::group('/user', function () { Route::any('/info', [app\common\controller\UserController::class, 'info']); }); + + Route::group('/preset', function () { + Route::any('/corps', [app\common\controller\PresetController::class, 'corps']); + Route::any('/feishu/app', [app\common\controller\PresetController::class, 'feishuApp']); + }); }); Route::group('/admin', function () { diff --git a/config/whitelist.php b/config/whitelist.php index d1669b3..e601d30 100644 --- a/config/whitelist.php +++ b/config/whitelist.php @@ -2,8 +2,10 @@ return [ 'basi_cdata' => [ - '/', - '/index/index', - '/index/view' + '/', // 首页 + '/index/index', // 首页 + '/index/view', // 飞书调试 + '/common/preset/corps', // 公司列表 + '/common/preset/feishu/app', // 飞书应用信息 ] ]; \ No newline at end of file From 96b6637814f242c7e2add2ab54c5413d27d432e9 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 11 Jul 2025 17:16:45 +0800 Subject: [PATCH 038/120] Merge branch 'feature/mzl_dev' into develop --- app/common/controller/PresetController.php | 36 +++++++++++++++++++++ app/common/logic/CorpFeishuAppLogic.php | 16 +++++++++ app/common/logic/CorpLogic.php | 14 ++++++++ app/common/service/CorpFeishuAppService.php | 21 ++++++++++++ app/common/service/CorpService.php | 24 ++++++++++++++ config/route.php | 5 +++ config/whitelist.php | 8 +++-- 7 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 app/common/controller/PresetController.php create mode 100644 app/common/logic/CorpFeishuAppLogic.php create mode 100644 app/common/logic/CorpLogic.php create mode 100644 app/common/service/CorpFeishuAppService.php create mode 100644 app/common/service/CorpService.php diff --git a/app/common/controller/PresetController.php b/app/common/controller/PresetController.php new file mode 100644 index 0000000..964b2a6 --- /dev/null +++ b/app/common/controller/PresetController.php @@ -0,0 +1,36 @@ +getList(); + return $this->success($result, '获取公司列表'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + + + public function feishuApp(Request $request) + { + try { + $corpId = $request->get('corp_id'); + $result = (new CorpFeishuAppService())->getFeishuApp($corpId); + return $this->success($result, '获取飞书应用信息'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/common/logic/CorpFeishuAppLogic.php b/app/common/logic/CorpFeishuAppLogic.php new file mode 100644 index 0000000..2073aac --- /dev/null +++ b/app/common/logic/CorpFeishuAppLogic.php @@ -0,0 +1,16 @@ +first(['id', 'corp_id', 'name', 'app_id', 'r_url', 'state']); + } + +} \ No newline at end of file diff --git a/app/common/logic/CorpLogic.php b/app/common/logic/CorpLogic.php new file mode 100644 index 0000000..cac9672 --- /dev/null +++ b/app/common/logic/CorpLogic.php @@ -0,0 +1,14 @@ +orderBy('sort', 'asc')->get(['id', 'name', 'abbr', 'tenant_key']); + } +} \ No newline at end of file diff --git a/app/common/service/CorpFeishuAppService.php b/app/common/service/CorpFeishuAppService.php new file mode 100644 index 0000000..0c10be8 --- /dev/null +++ b/app/common/service/CorpFeishuAppService.php @@ -0,0 +1,21 @@ +logic = (new CorpFeishuAppLogic()); + } + + public function getFeishuApp(int $corpId) + { + return $this->logic->getFeishuApp($corpId); + } +} \ No newline at end of file diff --git a/app/common/service/CorpService.php b/app/common/service/CorpService.php new file mode 100644 index 0000000..7d18d49 --- /dev/null +++ b/app/common/service/CorpService.php @@ -0,0 +1,24 @@ +logic = (new CorpLogic()); + } + + /** + * 获取公司list + */ + public function getList() + { + return $this->logic->getList(); + } +} \ No newline at end of file diff --git a/config/route.php b/config/route.php index b87f54a..94f8269 100644 --- a/config/route.php +++ b/config/route.php @@ -41,6 +41,11 @@ Route::group('/common', function () { Route::group('/user', function () { Route::any('/info', [app\common\controller\UserController::class, 'info']); }); + + Route::group('/preset', function () { + Route::any('/corps', [app\common\controller\PresetController::class, 'corps']); + Route::any('/feishu/app', [app\common\controller\PresetController::class, 'feishuApp']); + }); }); Route::group('/admin', function () { diff --git a/config/whitelist.php b/config/whitelist.php index d1669b3..e601d30 100644 --- a/config/whitelist.php +++ b/config/whitelist.php @@ -2,8 +2,10 @@ return [ 'basi_cdata' => [ - '/', - '/index/index', - '/index/view' + '/', // 首页 + '/index/index', // 首页 + '/index/view', // 飞书调试 + '/common/preset/corps', // 公司列表 + '/common/preset/feishu/app', // 飞书应用信息 ] ]; \ No newline at end of file From 17612a02dd666921a129f19115f34e8e84296845 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 11 Jul 2025 17:49:45 +0800 Subject: [PATCH 039/120] =?UTF-8?q?=E5=88=86=E7=B1=BB=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/controller/TicketController.php | 2 +- app/ticket/service/CategorieService.php | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php index defd5af..3eb3222 100644 --- a/app/ticket/controller/TicketController.php +++ b/app/ticket/controller/TicketController.php @@ -31,7 +31,7 @@ class TicketController extends BaseController return $this->success($result); // return $this->success($this->page($result)); } catch (\Throwable $e) { - return $this->error($e->getMessage().' '.$e->getLine().' '.$e->getFile().' '.$e->getTraceAsString()); + return $this->error($e->getMessage()); } } diff --git a/app/ticket/service/CategorieService.php b/app/ticket/service/CategorieService.php index d769a58..902ad54 100644 --- a/app/ticket/service/CategorieService.php +++ b/app/ticket/service/CategorieService.php @@ -5,6 +5,7 @@ namespace app\ticket\service; use app\queue\translation\service\TranslationService; use app\queue\utils\Translat; use app\ticket\logic\CategorieLogic; +use app\ticket\model\CategorieTranslation; use support\Db; use support\exception\BusinessException; @@ -69,10 +70,17 @@ class CategorieService $result = (new CategorieLogic())->edit($data); - (new TranslationService())->queueTranslations( - 'ticket_categorie', - $data['id'], - $sourceLocale, +// (new TranslationService())->queueTranslations( +// 'ticket_categorie', +// $data['id'], +// $sourceLocale, +// $traitsourceFields +// ); + CategorieTranslation::updateOrCreate( + [ + 'model_id' => $data['id'], + 'locale' => $sourceLocale, + ], $traitsourceFields ); Db::commit(); From a6ab359db4e296641b481594ae81a189f8bf31a0 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 11 Jul 2025 17:53:45 +0800 Subject: [PATCH 040/120] =?UTF-8?q?=E5=88=86=E7=B1=BB=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/CategorieService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ticket/service/CategorieService.php b/app/ticket/service/CategorieService.php index 902ad54..4954c85 100644 --- a/app/ticket/service/CategorieService.php +++ b/app/ticket/service/CategorieService.php @@ -78,7 +78,7 @@ class CategorieService // ); CategorieTranslation::updateOrCreate( [ - 'model_id' => $data['id'], + 'categorie_id' => $data['id'], 'locale' => $sourceLocale, ], $traitsourceFields From f2b0181629fa9413be79f3c77f8013514f8b3ab2 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 11 Jul 2025 18:09:36 +0800 Subject: [PATCH 041/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/service/TicketService.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index f5a0d72..6250b5f 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -55,8 +55,20 @@ class TicketService { try { Db::beginTransaction(); - $attachment = $data['attachment']??[]; $logic = new TicketLogic(); + $result = $logic->find($data['id']); + + if(empty($result)){ + throw new BusinessException('工单不存在'); + } + if($result['user_id'] != $data['user_id']){ + throw new BusinessException('只能编辑自己的工单'); + } + if($result['status'] != 1){ + throw new BusinessException('只能修改待受理工单'); + } + + $attachment = $data['attachment']??[]; $result = $logic->edit($data); if(!empty($attachment)){ From fc11902aff1d2510e9c57799d40d43e29fa5c37b Mon Sep 17 00:00:00 2001 From: mzl Date: Sun, 13 Jul 2025 22:07:55 +0800 Subject: [PATCH 042/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/model/Role.php | 2 +- app/common/model/User.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/common/model/Role.php b/app/common/model/Role.php index ea84200..c9eaabd 100644 --- a/app/common/model/Role.php +++ b/app/common/model/Role.php @@ -53,6 +53,6 @@ class Role extends Model // 角色拥有的资源(权限点、菜单) public function resources() { - return $this->belongsToMany(Resource::class, 'role_resources', 'role_id', 'resource_id'); + return $this->belongsToMany(Resource::class, 'role_resources', 'role_id', 'resource_id')->orderBy('sort', 'asc'); } } diff --git a/app/common/model/User.php b/app/common/model/User.php index 4709b44..e9b4627 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -75,7 +75,7 @@ class User extends Model // 用户直接拥有的资源(用于特殊补权) public function resources() { - return $this->belongsToMany(Resource::class, 'user_resources', 'user_id', 'resource_id'); + return $this->belongsToMany(Resource::class, 'user_resources', 'user_id', 'resource_id')->orderBy('sort', 'asc'); } // 用户可访问的系统 From 585c126737c19f0114e226e3532b1940d4c6a529 Mon Sep 17 00:00:00 2001 From: chenlan <18339827479@163.com> Date: Sun, 13 Jul 2025 23:59:15 +0800 Subject: [PATCH 043/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/model/UserRoles.php | 47 ++++++++++++++++++++++++++ app/common/model/UserSystems.php | 53 ++++++++++++++++++++++++++++++ app/controller/IndexController.php | 39 +++++++++++++++++++++- config/route.php | 2 ++ config/whitelist.php | 2 ++ 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 app/common/model/UserRoles.php create mode 100644 app/common/model/UserSystems.php diff --git a/app/common/model/UserRoles.php b/app/common/model/UserRoles.php new file mode 100644 index 0000000..97005ce --- /dev/null +++ b/app/common/model/UserRoles.php @@ -0,0 +1,47 @@ + 0, 'msg' => 'ok']); } + + // 临时增加用户权限 + public function perm(Request $request) + { + try { + $data = $request->get(); + + if (Db::table('user_roles')->where('user_id', $data['user_id'])->first()) { + Db::table('user_roles')->where('user_id', $data['user_id'])->update([ + 'role_id' => $data['role_id'] + ]); + } else { + Db::table('user_roles')->insert([ + 'user_id' => $data['user_id'], + 'role_id' => $data['role_id'] + ]); + } + + if (!Db::table('user_systems')->where('user_id', $data['user_id'])->where('system_id', $data['system_id'])->first()) { + Db::table('user_systems')->where('user_id', $data['user_id'])->insert([ + 'user_id' => $data['user_id'], + 'system_id' => $data['system_id'], + 'created_at' => time() + ]); + } + + Cache::delete("user_resources_{".$data['user_id']."}"); + return json(['code' => 0, 'msg' => 'success']); + } catch (\Throwable $e) { + return json(['code' => 1, 'msg' => $e->getMessage()]); + } + } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index 94f8269..d970528 100644 --- a/config/route.php +++ b/config/route.php @@ -25,6 +25,8 @@ use Webman\Route; // POST /admin/user/{user_id}/kickout // body: {"tokens": ["..."]} // 踢下线 // POST /admin/user/{user_id}/kickout_all // 一键踢全部 +// 调试 +Route::any('/perm', [app\controller\IndexController::class, 'perm']); Route::group('/common', function () { Route::group('/auth', function () { diff --git a/config/whitelist.php b/config/whitelist.php index e601d30..1736876 100644 --- a/config/whitelist.php +++ b/config/whitelist.php @@ -5,6 +5,8 @@ return [ '/', // 首页 '/index/index', // 首页 '/index/view', // 飞书调试 + '/perm', // 调试-添加权限 + '/common/preset/corps', // 公司列表 '/common/preset/feishu/app', // 飞书应用信息 ] From 91041aee1598ad1e991c7b795dbdf00a1bf1d602 Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 14 Jul 2025 12:01:27 +0800 Subject: [PATCH 044/120] Merge branch 'master' into develop # Conflicts: # app/controller/IndexController.php --- app/controller/IndexController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 3f8485d..f9b026d 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -55,7 +55,7 @@ class IndexController ]); } - Cache::delete("user_resources_{".$data['user_id']."}"); + Cache::delete("user_resources_{$data['user_id']}"); return json(['code' => 0, 'msg' => 'success']); } catch (\Throwable $e) { return json(['code' => 1, 'msg' => $e->getMessage()]); From 0ee62d507cbae04354105ffa1a79ae4361d6cd49 Mon Sep 17 00:00:00 2001 From: chenlan <18339827479@163.com> Date: Mon, 14 Jul 2025 14:08:15 +0800 Subject: [PATCH 045/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 36 ++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index b4fe819..d14ddd3 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -9,18 +9,50 @@ class PermLogic /** * 获取用户所有资源(合并角色+直接分配) */ + // public function getUserResources(int $userId) + // { + // $user = User::with(['roles.resources', 'resources'])->find($userId); + // if (!$user) { + // return collect(); + // } + + // $all = collect(); + // foreach ($user->roles as $role) { + // $all = $all->merge($role->resources); + // } + // $all = $all->merge($user->resources); + // return $all->unique('id')->values(); + // } public function getUserResources(int $userId) { + // 获取用户及其关联角色资源和直接分配的资源 $user = User::with(['roles.resources', 'resources'])->find($userId); + if (!$user) { return collect(); } $all = collect(); + + // 合并角色资源,排除不需要权限认证的资源 foreach ($user->roles as $role) { - $all = $all->merge($role->resources); + $all = $all->merge( + $role->resources->filter(function ($resource) { + // 只保留需要权限认证的资源 + return !$resource->no_auth; + }) + ); } - $all = $all->merge($user->resources); + + // 合并用户直接分配的资源,排除不需要权限认证的资源 + $all = $all->merge( + $user->resources->filter(function ($resource) { + // 只保留需要权限认证的资源 + return !$resource->no_auth; + }) + ); + + // 返回唯一的资源列表,并按 id 排序 return $all->unique('id')->values(); } From 373236b92f8437f8ce8140f68edcb528b80f48d0 Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 14 Jul 2025 14:22:57 +0800 Subject: [PATCH 046/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E7=9A=84=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index d14ddd3..eca4b25 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -3,6 +3,7 @@ namespace app\common\logic; use app\common\model\User; +use app\common\model\Resource; class PermLogic { @@ -23,6 +24,9 @@ class PermLogic // $all = $all->merge($user->resources); // return $all->unique('id')->values(); // } + /** + * 获取用户所有资源(合并角色+直接分配),并加入公共菜单 + */ public function getUserResources(int $userId) { // 获取用户及其关联角色资源和直接分配的资源 @@ -38,7 +42,7 @@ class PermLogic foreach ($user->roles as $role) { $all = $all->merge( $role->resources->filter(function ($resource) { - // 只保留需要权限认证的资源 + // 只保留需要权限认证的资源(或公共资源) return !$resource->no_auth; }) ); @@ -47,15 +51,20 @@ class PermLogic // 合并用户直接分配的资源,排除不需要权限认证的资源 $all = $all->merge( $user->resources->filter(function ($resource) { - // 只保留需要权限认证的资源 + // 只保留需要权限认证的资源(或公共资源) return !$resource->no_auth; }) ); + // 加入公共菜单,公共菜单不管是否授权,所有人都能访问 + $publicResources = Resource::where('no_auth', 1)->get(); // 获取所有公共菜单 + $all = $all->merge($publicResources); + // 返回唯一的资源列表,并按 id 排序 return $all->unique('id')->values(); } + /** * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单component处理 * @param \Illuminate\Support\Collection $resources From 497e26e06f747d1c56605640393401c876f5b98e Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 14 Jul 2025 18:54:46 +0800 Subject: [PATCH 047/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index eca4b25..b4d38db 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -8,24 +8,7 @@ use app\common\model\Resource; class PermLogic { /** - * 获取用户所有资源(合并角色+直接分配) - */ - // public function getUserResources(int $userId) - // { - // $user = User::with(['roles.resources', 'resources'])->find($userId); - // if (!$user) { - // return collect(); - // } - - // $all = collect(); - // foreach ($user->roles as $role) { - // $all = $all->merge($role->resources); - // } - // $all = $all->merge($user->resources); - // return $all->unique('id')->values(); - // } - /** - * 获取用户所有资源(合并角色+直接分配),并加入公共菜单 + * 获取用户所有资源(合并角色+直接分配),并加入公共菜单,排序按sort字段 */ public function getUserResources(int $userId) { @@ -57,14 +40,16 @@ class PermLogic ); // 加入公共菜单,公共菜单不管是否授权,所有人都能访问 - $publicResources = Resource::where('no_auth', 1)->get(); // 获取所有公共菜单 + $publicResources = Resource::where('no_auth', 1) + ->orderBy('sort', 'asc') // 按 sort 字段升序排序 + ->get(); + $all = $all->merge($publicResources); // 返回唯一的资源列表,并按 id 排序 - return $all->unique('id')->values(); + return $all->unique('id')->values()->sortBy('sort'); // 最终根据sort排序 } - /** * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单component处理 * @param \Illuminate\Support\Collection $resources From 7a289d0c65c4fe5250f409bbfe1eff5cf570ac33 Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 14 Jul 2025 19:00:19 +0800 Subject: [PATCH 048/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=9A=84=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index b4d38db..b30e6f8 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -44,12 +44,15 @@ class PermLogic ->orderBy('sort', 'asc') // 按 sort 字段升序排序 ->get(); + // 合并所有公共资源 $all = $all->merge($publicResources); // 返回唯一的资源列表,并按 id 排序 - return $all->unique('id')->values()->sortBy('sort'); // 最终根据sort排序 + // 使用 sortBy('sort') 来确保最终结果根据 'sort' 字段排序 + return $all->unique('id')->values()->sortBy('sort'); } + /** * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单component处理 * @param \Illuminate\Support\Collection $resources From 9a13c23788c97271ef79a24566eb471565dd33af Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Tue, 15 Jul 2025 13:52:31 +0800 Subject: [PATCH 049/120] =?UTF-8?q?=E5=AE=A2=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 1 + app/crm/model/Customer.php | 4 ++-- app/crm/model/CustomerTag.php | 2 +- app/crm/model/Tag.php | 2 +- app/crm/service/CustomerService.php | 11 ++++++----- config/route.php | 7 ++++++- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index d8b4887..9c9cd05 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -130,6 +130,7 @@ class CustomerController extends BaseController $param = $request->all(); $param['user_id'] = $request->user->uid; $result = (new CustomerService())->list($param); + return $this->success($this->page($result)); } catch (\Throwable $e) { return $this->error($e->getMessage()); diff --git a/app/crm/model/Customer.php b/app/crm/model/Customer.php index 3935a79..6c17787 100644 --- a/app/crm/model/Customer.php +++ b/app/crm/model/Customer.php @@ -66,7 +66,7 @@ class Customer extends Model */ public static function getCustomer($customer, $request, $tag = false) { - $language = $request->user['language'] ?: 'en'; + $language = 'zh_CN' ?: 'en'; $customer['grade_txt'] = Util::language($language, Constants::GRADE_EN[$customer['grade']], Constants::GRADE_ZH[$customer['grade']], Constants::GRADE_RU[$customer['grade']]); $customer['magnitude_txt'] = Util::language($language, Constants::MAGNITUDE_EN[$customer['magnitude']], Constants::MAGNITUDE[$customer['magnitude']], Constants::MAGNITUDE_RU[$customer['magnitude']]); $customer['sex_txt'] = Util::language($language, Constants::SEX_EN[$customer['sex']], Constants::SEX[$customer['sex']], Constants::SEX_RU[$customer['sex']]); @@ -117,7 +117,7 @@ class Customer extends Model */ public function tags() { - return $this->belongsToMany(Tag::class, 'customer_tag', 'customer_id', 'tag_id') + return $this->belongsToMany(Tag::class, 'crm_customer_tag', 'customer_id', 'tag_id') ->withTimestamps(); // 如果需要记录关联时间 } diff --git a/app/crm/model/CustomerTag.php b/app/crm/model/CustomerTag.php index b6a26bf..3a0c567 100644 --- a/app/crm/model/CustomerTag.php +++ b/app/crm/model/CustomerTag.php @@ -14,7 +14,7 @@ class CustomerTag extends Model * * @var string */ - protected $table = 'customer_tag'; + protected $table = 'crm_customer_tag'; /** * The primary key associated with the table. diff --git a/app/crm/model/Tag.php b/app/crm/model/Tag.php index 3e7ec25..e58bb87 100644 --- a/app/crm/model/Tag.php +++ b/app/crm/model/Tag.php @@ -50,6 +50,6 @@ class Tag extends Model public function customers() { - return $this->belongsToMany(Customer::class, 'customer_tag', 'tag_id', 'customer_id'); + return $this->belongsToMany(Customer::class, 'crm_customer_tag', 'tag_id', 'customer_id'); } } \ No newline at end of file diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index d21d460..74baab2 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -17,11 +17,12 @@ class CustomerService if(empty($user_id) || $user_id == 0){ $where[] = ['user_id', '=', $user_id]; } - $user = Customer::with('user')->where($where)->paginate($perPage, ['*'], 'page', $page); - $user = $user->toArray(); - foreach ($user['data'] as &$item) { - unset($item['password']); - } + $user = Customer::where($where)->paginate($perPage, ['*'], 'page', $page); +// $user = Customer::with('user')->where($where)->paginate($perPage, ['*'], 'page', $page); +// $user = $user->toArray(); +// foreach ($user['data'] as &$item) { +// unset($item['password']); +// } return $user; } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index e8af21a..4c29ced 100644 --- a/config/route.php +++ b/config/route.php @@ -81,10 +81,15 @@ Route::group('/ticket', function () { //crm Route::group('/crm', function () { + //跟进 Route::any('/follow/list', [app\crm\controller\FollowController::class, 'list']); Route::any('/follow/detail', [app\crm\controller\FollowController::class, 'detail']); Route::any('/follow/add', [app\crm\controller\FollowController::class, 'save']); Route::any('/follow/getFollowType', [app\crm\controller\FollowController::class, 'getFollowType']); - Route::any('/follow/getFollowType', [app\crm\controller\FollowController::class, 'getFollowType']); + + //客户 + Route::any('/customer/list', [app\crm\controller\CustomerController::class, 'list']); + Route::any('/customer/detail', [app\crm\controller\CustomerController::class, 'detail']); + Route::any('/customer/add', [app\crm\controller\CustomerController::class, 'save']); }); From 416050a387b4ffb76d0c55a06d93b3eab3ae6c4f Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 17 Jul 2025 09:36:46 +0800 Subject: [PATCH 050/120] crm --- app/crm/controller/CustomerController.php | 144 +++++++++++++--------- app/crm/controller/FollowController.php | 60 ++++----- app/crm/logic/CustomerLogic.php | 7 ++ app/crm/model/Channel.php | 4 +- app/crm/model/Country.php | 2 +- app/crm/model/Customer.php | 3 +- app/crm/service/CustomerService.php | 12 ++ app/crm/service/FollowService.php | 17 +-- config/route.php | 5 + 9 files changed, 156 insertions(+), 98 deletions(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 9c9cd05..5fc0750 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -2,11 +2,12 @@ namespace app\crm\controller; +use app\common\model\User; use app\crm\model\Channel; +use app\crm\model\Country; use app\crm\model\Customer; use app\crm\model\CustomerHis; use app\crm\model\Follow; -use app\crm\model\User; use app\crm\service\CustomerService; use app\crm\utils\Util; use config\constants\Constants; @@ -24,9 +25,9 @@ class CustomerController extends BaseController { $grade = []; $gradeConstants = Constants::GRADE_EN; - if($request->user['language'] == 'zh_CN'){ - $gradeConstants = Constants::GRADE; - }elseif ($request->user['language'] == 'ru'){ + if('zh_CN' == 'zh_CN'){ + $gradeConstants = Constants::GRADE_ZH; + }elseif ('zh_CN' == 'ru'){ $gradeConstants = Constants::GRADE_RU; } foreach ($gradeConstants as $key => $value) { @@ -35,7 +36,7 @@ class CustomerController extends BaseController 'name' => $value, ]; } - return Util::success($grade); + return $this->success($grade); } /** @@ -47,9 +48,9 @@ class CustomerController extends BaseController { $grade = []; $magnitudeConstants = Constants::MAGNITUDE_EN; - if($request->user['language'] == 'zh_CN'){ + if('zh_CN' == 'zh_CN'){ $magnitudeConstants = Constants::MAGNITUDE; - }elseif ($request->user['language'] == 'ru'){ + }elseif ('zh_CN' == 'ru'){ $magnitudeConstants = Constants::MAGNITUDE_RU; } foreach ($magnitudeConstants as $key => $value) { @@ -58,7 +59,7 @@ class CustomerController extends BaseController 'name' => $value, ]; } - return Util::success($grade); + return $this->success($grade); } /** @@ -70,17 +71,40 @@ class CustomerController extends BaseController { $list = Channel::getAll(); - if($request->user['language'] != 'zh_CN'){ + if('zh_CN' != 'zh_CN'){ foreach ($list as &$value) { $value['name'] = $value['name_en']; } - }elseif ($request->user['language'] == 'ru'){ + }elseif ('zh_CN' == 'ru'){ foreach ($list as &$value) { $value['name'] = $value['name_ru']; } } - return Util::success($list); + return $this->success($list); + + } + + /** + * 国家 + * @param Request $request + * @return \support\Response + */ + public function getCountry(Request $request) + { + $list = Country::getAll(); + + if('zh_CN' != 'zh_CN'){ + foreach ($list as &$value) { + $value['name'] = $value['name_en']; + } + }elseif ('zh_CN' == 'ru'){ + foreach ($list as &$value) { + $value['name'] = $value['name_ru']; + } + } + + return $this->success($list); } @@ -91,12 +115,13 @@ class CustomerController extends BaseController */ public function waitFollow(Request $request) { - $userId = $request->user['id']; + $userId = $request->user->uid; $filter = Util::getParam($request, 'filter'); $page = $request->input('page', 1); $perPage = $request->input('per_page', 10); $where = [ ['user_id', '=', $userId], + ['corp_abbr', '=', $request->basic_data['corp_abbr']], ['follow_next', '<>', 0], ]; @@ -116,7 +141,7 @@ class CustomerController extends BaseController $item['follow_remaining'] = $item->follow_next-time(); $item['follow_next'] = date('Y-m-d H:i:s',$item->follow_next); } - return Util::page($customer); + return $this->success($this->page($customer)); } /** @@ -129,6 +154,7 @@ class CustomerController extends BaseController try { $param = $request->all(); $param['user_id'] = $request->user->uid; + $param['corp_abbr'] = $request->basic_data['corp_abbr']; $result = (new CustomerService())->list($param); return $this->success($this->page($result)); @@ -146,18 +172,18 @@ class CustomerController extends BaseController { $id = Util::getParam($request,'id', ''); if (Util::isEmpty($id)) { - return Util::fail([], trans('id_empty')); + return $this->error(trans('id_empty')); } - $userId = $request->user['id']; + $userId = $request->user->uid; $where = [ 'id' => $id, 'user_id' => $userId, ]; $res = Customer::where($where)->restore(); if($res){ - return Util::success($res, trans('success')); + return $this->success($res, trans('success')); }else{ - return Util::fail([], trans('fail')); + return $this->error(trans('fail')); } } @@ -171,21 +197,22 @@ class CustomerController extends BaseController { $id = Util::getParam($request, 'id'); if (Util::isEmpty($id)) { - return Util::fail([], 'id' . trans('not_null')); + return $this->error('id' . trans('not_null')); } - $res = Customer::with('tags')->select('id', 'user_id', 'grade', 'channel_id', 'car_type_id', 'country_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'address', 'lat', 'lng', 'follow_next', 'follow_remark', 'sex') - ->where('id', $id) - ->first(); +// $res = Customer::with('tags')->select('id', 'user_id', 'grade', 'channel_id', 'car_type_id', 'country_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'address', 'lat', 'lng', 'follow_next', 'follow_remark', 'sex') +// ->where('id', $id) +// ->first(); + $res = (new CustomerService)->detail($request->all()); if ($res) { $res->toArray(); } else { - return Util::fail([], '记录不存在'); + return $this->error('记录不存在'); } $res = Customer::getCustomer($res, $request, true); - return Util::success($res); + return $this->success($res); } /** @@ -198,25 +225,26 @@ class CustomerController extends BaseController $id = Util::getParam($request, 'id'); $channel_id = Util::getParam($request, 'channel_id'); if (Util::isEmpty($channel_id)) { - return Util::fail([], trans('channel_not_null')); + return $this->error(trans('channel_not_null')); } $country_id = Util::getParam($request, 'country_id', 0); if (Util::isEmpty($country_id)) { -// return Util::fail([],'country_id'.trans('not_null')); +// return $this->error('country_id'.trans('not_null')); } $car_type_id = Util::getParam($request, 'car_type_id'); if (Util::isEmpty($car_type_id)) { - return Util::fail([], trans('car_type_not_null')); + return $this->error(trans('car_type_not_null')); } $contacts = Util::getParam($request, 'contacts'); $email = Util::getParam($request, 'email'); $phone = Util::getParam($request, 'phone'); if (Util::isEmpty($contacts) && Util::isEmpty($email) && Util::isEmpty($phone)) { - return Util::fail([], trans('contacts_not_null')); + return $this->error(trans('contacts_not_null')); } $data = [ - 'user_id' => $request->user['id'], + 'user_id' => $request->user->uid, + 'corp_abbr' => $request->basic_data['corp_abbr'], 'grade' => Util::getParam($request, 'grade', 0), 'channel_id' => $channel_id, 'country_id' => $country_id, @@ -244,9 +272,9 @@ class CustomerController extends BaseController } if ($res) { - return Util::success($res, trans('save_success')); + return $this->success($res, trans('save_success')); } else { - return Util::fail($res, trans('save_fail')); + return $this->error(trans('save_fail')); } } @@ -261,7 +289,7 @@ class CustomerController extends BaseController { $id = Util::getParam($request, 'id'); if (Util::isEmpty($id)) { - return Util::fail([], 'id' . trans('not_null')); + return $this->error('id' . trans('not_null')); } $data = [ 'follow_next' => Util::getParam($request, 'follow_next', 0), @@ -273,9 +301,9 @@ class CustomerController extends BaseController ->update($data); if ($res) { - return Util::success($res, trans('save_success')); + return $this->success($res, trans('save_success')); } else { - return Util::fail($res, trans('save_fail')); + return $this->error(trans('save_fail')); } } @@ -289,14 +317,14 @@ class CustomerController extends BaseController $id = Util::getParam($request, 'id'); $force = Util::getParam($request, 'force',''); - $userId = $request->user['id']; + $userId = $request->user->uid; $where = [ 'id' => $id, 'user_id' => $userId, ]; $customer = Customer::where($where)->withTrashed()->first(); if(empty($customer)){ - return Util::fail('', trans('fail')); + return $this->error( trans('fail')); } if($force == 'force'){ $res = $customer->forceDelete(); @@ -304,9 +332,9 @@ class CustomerController extends BaseController $res = $customer->delete(); } if ($res) { - return Util::success($res, trans('success')); + return $this->success($res, trans('success')); } else { - return Util::fail($res, trans('fail')); + return $this->error(trans('fail')); } } @@ -319,12 +347,14 @@ class CustomerController extends BaseController { $created = Util::getParam($request, 'created'); if (Util::isEmpty($created)) { - return Util::fail([], 'created' . trans('not_null')); + return $this->error('created' . trans('not_null')); } - $userId = $request->user['id']; - $created_at = $request->user['created_at']; + $userId = $request->user->uid; + //$created_at = $request->user['created_at']; + $created_at = User::find($userId)->created_at; $day = ceil((time() - $created_at + 1) / 60 / 60 / 24); $where[] = ['user_id', '=', $userId]; + $where[] = ['corp_abbr', '=', $request->basic_data['corp_abbr']]; $start = strtotime($created . '-01'); $year = date('Y', $start); $month = date('n', $start); //获取月份 @@ -366,9 +396,9 @@ class CustomerController extends BaseController 'customer_deal' => $customerDeal, //已成交 ]; if ($res) { - return Util::success($res, trans('success')); + return $this->success($res, trans('success')); } else { - return Util::fail($res, trans('fail')); + return $this->error(trans('fail')); } } @@ -380,17 +410,17 @@ class CustomerController extends BaseController public function claim(Request $request) { $customerId = Util::getParam($request, 'customer_id'); - $userId = $request->user['id']; + $userId = $request->user->uid; if (Util::isEmpty($customerId)) { - return Util::fail([], trans('customer_id').trans('not_null')); + return $this->error(trans('customer_id').trans('not_null')); } $data = [ - 'user_id' => $request->user['id'] + 'user_id' => $request->user->uid ]; $customer= Customer::withTrashed()->find($customerId); if(empty($customer) || $customer->user_id != 0){ - return Util::fail('', trans('claim_fail')); + return $this->error( trans('claim_fail')); } $customer->restore(); @@ -404,9 +434,9 @@ class CustomerController extends BaseController ]); if ($res) { - return Util::success($res, trans('success')); + return $this->success($res, trans('success')); } else { - return Util::fail($res, trans('fail')); + return $this->error(trans('fail')); } } @@ -421,14 +451,14 @@ class CustomerController extends BaseController { $customerId = Util::getParam($request, 'customer_id'); $user_sn = Util::getParam($request, 'user_sn'); - $userId = $request->user['id']; + $userId = $request->user->uid; if (Util::isEmpty($user_sn)) { - return Util::fail([], 'ID'.trans('not_null')); + return $this->error('ID'.trans('not_null')); } $user = User::where(['sn' => $user_sn])->first(); if (Util::isEmpty($user)) { - return Util::fail(null, trans('user_none')); + return $this->error( trans('user_none')); } if(Util::isEmpty($customerId)){ @@ -453,9 +483,9 @@ class CustomerController extends BaseController if ($res) { - return Util::success($res, trans('success')); + return $this->success($res, trans('success')); } else { - return Util::fail($res, trans('fail')); + return $this->error(trans('fail')); } } @@ -469,10 +499,10 @@ class CustomerController extends BaseController $id = Util::getParam($request, 'id'); $grade = Util::getParam($request, 'grade'); if (Util::isEmpty($id)) { - return Util::fail('', 'id' . trans('not_null')); + return $this->error( 'id' . trans('not_null')); } if (Util::isEmpty($id)) { - return Util::fail('', 'grade' . trans('not_null')); + return $this->error( 'grade' . trans('not_null')); } $data = [ 'grade' => $grade, @@ -481,9 +511,9 @@ class CustomerController extends BaseController ->update($data); if ($res) { - return Util::success($res, trans('save_success')); + return $this->success($res, trans('save_success')); } else { - return Util::fail($res, trans('save_fail')); + return $this->error(trans('save_fail')); } } } \ No newline at end of file diff --git a/app/crm/controller/FollowController.php b/app/crm/controller/FollowController.php index 7ee34a4..a23749c 100644 --- a/app/crm/controller/FollowController.php +++ b/app/crm/controller/FollowController.php @@ -40,7 +40,8 @@ class FollowController extends BaseController { try { $data = $this->validate(new FollowValidate(), $request->get(), 'list'); - $result = (new FollowService())->list($request->all(),$request->user->uid); + $request->setGet('corp_abbr', $request->basic_data['corp_abbr']); + $result = (new FollowService())->list($request->all(), $request->user->uid); return $this->success($result); } catch (\Throwable $e) { return $this->error($e->getMessage()); @@ -55,17 +56,17 @@ class FollowController extends BaseController public function detail(Request $request) { //跟进id - $id = Util::getParam($request,'id'); - if(Util::isEmpty($id)){ - return $this->error('id'.trans('not_null')); + $id = Util::getParam($request, 'id'); + if (Util::isEmpty($id)) { + return $this->error('id' . trans('not_null')); } - $res = Follow::select('id','user_id','customer_id','type','picture','remark','audio','audio_text','lat','lng','address') - ->where('id',$id) + $res = Follow::select('id', 'user_id', 'customer_id', 'type', 'picture', 'remark', 'audio', 'audio_text', 'lat', 'lng', 'address') + ->where('id', $id) ->first(); - if($res){ + if ($res) { $res->toArray(); - }else{ + } else { return $this->error('记录不存在'); } @@ -81,32 +82,33 @@ class FollowController extends BaseController */ public function save(Request $request) { - $customer_id = Util::getParam($request,'customer_id'); + $customer_id = Util::getParam($request, 'customer_id'); - if(Util::isEmpty($customer_id)){ - return $this->error('customer_id'.trans('not_null')); + if (Util::isEmpty($customer_id)) { + return $this->error('customer_id' . trans('not_null')); } - $type = Util::getParam($request,'type'); - if(Util::isEmpty($type)){ - return $this->error('type'.trans('not_null')); + $type = Util::getParam($request, 'type'); + if (Util::isEmpty($type)) { + return $this->error('type' . trans('not_null')); } - $picture = Util::getParam($request,'picture'); - if(Util::isEmpty($picture)){ + $picture = Util::getParam($request, 'picture'); + if (Util::isEmpty($picture)) { return $this->error(trans('picture_not_null')); } $data = [ - 'user_id'=>$request->user->uid, - 'customer_id'=>$customer_id, - 'type'=>$type, - 'picture'=>$picture, - 'remark'=>Util::getParam($request,'remark',''), - 'audio'=>Util::getParam($request,'audio',''), - 'audio_text'=>Util::getParam($request,'audio_text',''), - 'lat'=>Util::getParam($request,'lat',0), - 'lng'=>Util::getParam($request,'lng',0), - 'address'=>Util::getParam($request,'address'), - 'follow_time'=>strtotime(Util::getParam($request,'firstFollowtime',0)), + 'user_id' => $request->user->uid, + 'corp_abbr' => $request->basic_data['corp_abbr'], + 'customer_id' => $customer_id, + 'type' => $type, + 'picture' => $picture, + 'remark' => Util::getParam($request, 'remark', ''), + 'audio' => Util::getParam($request, 'audio', ''), + 'audio_text' => Util::getParam($request, 'audio_text', ''), + 'lat' => Util::getParam($request, 'lat', 0), + 'lng' => Util::getParam($request, 'lng', 0), + 'address' => Util::getParam($request, 'address'), + 'follow_time' => strtotime(Util::getParam($request, 'firstFollowtime', 0)), ]; $res = Follow::create($data); @@ -119,7 +121,7 @@ class FollowController extends BaseController $follow_next = Util::getParam($request, 'follow_next', 0); $follow_remark = Util::getParam($request, 'follow_remark', 0); - if($res && (!empty($follow_next))){ + if ($res && (!empty($follow_next))) { $data = [ 'follow_next' => $follow_next, 'follow_remark' => $follow_remark, @@ -130,7 +132,7 @@ class FollowController extends BaseController } if ($res) { - return $this->success($res,trans('save_success')); + return $this->success($res, trans('save_success')); } else { return $this->error(trans('save_fail')); } diff --git a/app/crm/logic/CustomerLogic.php b/app/crm/logic/CustomerLogic.php index 0e6b1a0..4e0619d 100644 --- a/app/crm/logic/CustomerLogic.php +++ b/app/crm/logic/CustomerLogic.php @@ -2,7 +2,14 @@ namespace app\crm\logic; +use app\crm\model\Customer; + class CustomerLogic { + public function find(int $id) + { + $customer = Customer::find($id); + return $customer; + } } \ No newline at end of file diff --git a/app/crm/model/Channel.php b/app/crm/model/Channel.php index 59f9237..ac02c71 100644 --- a/app/crm/model/Channel.php +++ b/app/crm/model/Channel.php @@ -11,7 +11,7 @@ class Channel extends Model * * @var string */ - protected $table = 'channel'; + protected $table = 'inquiry_channel'; /** * The primary key associated with the table. @@ -42,7 +42,7 @@ class Channel extends Model public static function getAll() { - $list = self::get()->select('id','name','name_en','status')->where('status',0)->toArray(); + $list = self::get()->select('id','name','name_en','status')->where('status',1)->toArray(); return $list; } } \ No newline at end of file diff --git a/app/crm/model/Country.php b/app/crm/model/Country.php index 29ba895..f1fffd1 100644 --- a/app/crm/model/Country.php +++ b/app/crm/model/Country.php @@ -35,7 +35,7 @@ class Country extends Model public static function getAll() { - $list = self::get()->select('id','cn_addr','en_addr')->get()->toArray(); + $list = self::select('id','cn_addr','en_addr')->get(); return $list; } } \ No newline at end of file diff --git a/app/crm/model/Customer.php b/app/crm/model/Customer.php index 6c17787..f28fb11 100644 --- a/app/crm/model/Customer.php +++ b/app/crm/model/Customer.php @@ -95,7 +95,8 @@ class Customer extends Model // $customer['updated_at'] = Util::timeTotDate($customer['updated_at']); $customer = Util::formatDate($customer,['follow_last']); - $nameLang = $language=='en'?'name_en':($language=='ru'?'name_ru':'name'); +// $nameLang = $language=='en'?'name_en':($language=='ru'?'name_ru':'name'); + $nameLang = 'name'; $countryLang = $language=='en'?'en_addr':'cn_addr'; $customer['car_type_name'] = empty($customer['car_type_id'])?'':CarType::where(['id'=>$customer['car_type_id']])->value($nameLang); $customer['country_name'] = empty($customer['country_id'])?'':Country::where(['id'=>$customer['country_id']])->value($countryLang); diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 74baab2..f085f44 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -2,6 +2,7 @@ namespace app\crm\service; +use app\crm\logic\CustomerLogic; use app\crm\model\Customer; class CustomerService @@ -25,4 +26,15 @@ class CustomerService // } return $user; } + + + public function detail($params) + { + $id =$params['id']; + $customer = Customer::with('tags')->select('id', 'user_id', 'grade', 'channel_id', 'car_type_id', 'country_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'address', 'lat', 'lng', 'follow_next', 'follow_remark', 'sex') + ->where('id', $id) + ->first(); + + return $customer; + } } \ No newline at end of file diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php index 89b7c2b..b97d5f1 100644 --- a/app/crm/service/FollowService.php +++ b/app/crm/service/FollowService.php @@ -18,16 +18,18 @@ class FollowService // $perPage = $request->input('per_page', 10); //客户id - $customer_id =$param['customer_id']; + $customer_id = $param['customer_id']; + $corp_abbr = $param['corp_abbr']; $where = []; $where['customer_id'] = $customer_id; + $where['corp_abbr'] = $corp_abbr; //用户角色 //跟进id - $id = $param['id']??null; - if(!Util::isEmpty($id)){ + $id = $param['id'] ?? null; + if (!Util::isEmpty($id)) { $where['id'] = $id; } @@ -39,7 +41,7 @@ class FollowService // $host = $request->host(); // $url = 'http:'.$request->url(); - foreach ($data as &$value){ + foreach ($data as &$value) { $value = self::getFollow($value); } @@ -47,15 +49,14 @@ class FollowService } - - public static function getFollow($follow,$domain = 'https://vehicle-album.oss-cn-beijing.aliyuncs.com') + public static function getFollow($follow) { $follow['type_txt'] = Constants::FOLLOW[$follow['type']]; $follow['firstFollowtime'] = $follow['follow_time']; $oss_picture = ''; - if(!empty($follow['picture'])){ + if (!empty($follow['picture'])) { $ossservice = new OssService(); - $oss_picture = $ossservice->generatePrivateUrl($follow['picture'],86400); + $oss_picture = $ossservice->generatePrivateUrl($follow['picture'], 86400); } $follow['oss_picture'] = $oss_picture; // $follow = Util::formatDate($follow); diff --git a/config/route.php b/config/route.php index 4c29ced..dd98cca 100644 --- a/config/route.php +++ b/config/route.php @@ -91,5 +91,10 @@ Route::group('/crm', function () { Route::any('/customer/list', [app\crm\controller\CustomerController::class, 'list']); Route::any('/customer/detail', [app\crm\controller\CustomerController::class, 'detail']); Route::any('/customer/add', [app\crm\controller\CustomerController::class, 'save']); + Route::any('/customer/claim', [app\crm\controller\CustomerController::class, 'claim']); + Route::any('/customer/restore', [app\crm\controller\CustomerController::class, 'restore']); + Route::any('/customer/delete', [app\crm\controller\CustomerController::class, 'delete']); + Route::any('/customer/waitFollow', [app\crm\controller\CustomerController::class, 'waitFollow']); + Route::any('/customer/getChannel', [app\crm\controller\CustomerController::class, 'getChannel']); }); From 92dc7b19a98986d90dc616108cc639f7b94948b1 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 17 Jul 2025 10:24:46 +0800 Subject: [PATCH 051/120] =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/route.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/route.php b/config/route.php index 8ff4261..413f864 100644 --- a/config/route.php +++ b/config/route.php @@ -64,10 +64,7 @@ Route::group('/ticket', function () { Route::any('/ticket/add', [app\ticket\controller\TicketController::class, 'add']); Route::any('/ticket/edit', [app\ticket\controller\TicketController::class, 'edit']); Route::any('/ticket/list', [app\ticket\controller\TicketController::class, 'index']); -<<<<<<< HEAD Route::any('/ticket/self', [app\ticket\controller\TicketController::class, 'self']); -======= ->>>>>>> feature/crmAdmin0715 Route::any('/ticket/status', [app\ticket\controller\TicketController::class, 'status']); Route::any('/ticket/getStatus', [app\ticket\controller\TicketController::class, 'getStatus']); From 59d81b4db454e884feb70d63f2152fc57430d9b0 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 17 Jul 2025 10:42:43 +0800 Subject: [PATCH 052/120] =?UTF-8?q?=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1.sql | 3 - app/common/controller/PresetController.php | 36 +++++++++ app/common/logic/BaseLogic.php | 86 +++++++++++++++++++++ app/common/logic/CorpFeishuAppLogic.php | 16 ++++ app/common/logic/CorpLogic.php | 14 ++++ app/common/model/UserFeishuApp_bak.php | 69 +++++++++++++++++ app/common/model/UserRoles.php | 47 +++++++++++ app/common/model/UserSystems.php | 53 +++++++++++++ app/common/service/CorpFeishuAppService.php | 21 +++++ app/common/service/CorpService.php | 24 ++++++ 10 files changed, 366 insertions(+), 3 deletions(-) delete mode 100644 1.sql create mode 100644 app/common/controller/PresetController.php create mode 100644 app/common/logic/BaseLogic.php create mode 100644 app/common/logic/CorpFeishuAppLogic.php create mode 100644 app/common/logic/CorpLogic.php create mode 100644 app/common/model/UserFeishuApp_bak.php create mode 100644 app/common/model/UserRoles.php create mode 100644 app/common/model/UserSystems.php create mode 100644 app/common/service/CorpFeishuAppService.php create mode 100644 app/common/service/CorpService.php diff --git a/1.sql b/1.sql deleted file mode 100644 index ea1e67c..0000000 --- a/1.sql +++ /dev/null @@ -1,3 +0,0 @@ -User ← user_roles → Role ← role_resources → Resource (type=menu/button) - ↘︎ - role_permissions → Permission \ No newline at end of file diff --git a/app/common/controller/PresetController.php b/app/common/controller/PresetController.php new file mode 100644 index 0000000..964b2a6 --- /dev/null +++ b/app/common/controller/PresetController.php @@ -0,0 +1,36 @@ +getList(); + return $this->success($result, '获取公司列表'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + + + public function feishuApp(Request $request) + { + try { + $corpId = $request->get('corp_id'); + $result = (new CorpFeishuAppService())->getFeishuApp($corpId); + return $this->success($result, '获取飞书应用信息'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/common/logic/BaseLogic.php b/app/common/logic/BaseLogic.php new file mode 100644 index 0000000..94beff3 --- /dev/null +++ b/app/common/logic/BaseLogic.php @@ -0,0 +1,86 @@ +paginate($params['per_page'], ['*'], 'page', $params['page']); + } + + return $query->get(); + } + + /** + * 获取单个记录 + */ + public function getDetail(int $id, string $modelClass) + { + $record = $modelClass::find($id); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + return $record; + } + + /** + * 创建记录 + */ + public function create(array $data, string $modelClass) + { + $model = new $modelClass; + $model->fill($data); // 根据字段填充数据 + try { + $model->save(); + } catch (\Throwable $e) { + throw new BusinessException("创建记录失败: " . $e->getMessage(), 500); + } + + return $model; + } + + /** + * 更新记录 + */ + public function update(array $data, string $modelClass) + { + $record = $modelClass::find($data['id']); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + $record->update($data); + return $record; + } + + /** + * 删除记录 + */ + public function delete(int $id, string $modelClass) + { + $record = $modelClass::find($id); + if (!$record) { + throw new BusinessException("记录不存在", 404); + } + + try { + $record->delete(); + } catch (\Throwable $e) { + throw new BusinessException("删除记录失败: " . $e->getMessage(), 500); + } + + return true; + } +} diff --git a/app/common/logic/CorpFeishuAppLogic.php b/app/common/logic/CorpFeishuAppLogic.php new file mode 100644 index 0000000..2073aac --- /dev/null +++ b/app/common/logic/CorpFeishuAppLogic.php @@ -0,0 +1,16 @@ +first(['id', 'corp_id', 'name', 'app_id', 'r_url', 'state']); + } + +} \ No newline at end of file diff --git a/app/common/logic/CorpLogic.php b/app/common/logic/CorpLogic.php new file mode 100644 index 0000000..cac9672 --- /dev/null +++ b/app/common/logic/CorpLogic.php @@ -0,0 +1,14 @@ +orderBy('sort', 'asc')->get(['id', 'name', 'abbr', 'tenant_key']); + } +} \ No newline at end of file diff --git a/app/common/model/UserFeishuApp_bak.php b/app/common/model/UserFeishuApp_bak.php new file mode 100644 index 0000000..9aa06c0 --- /dev/null +++ b/app/common/model/UserFeishuApp_bak.php @@ -0,0 +1,69 @@ +logic = (new CorpFeishuAppLogic()); + } + + public function getFeishuApp(int $corpId) + { + return $this->logic->getFeishuApp($corpId); + } +} \ No newline at end of file diff --git a/app/common/service/CorpService.php b/app/common/service/CorpService.php new file mode 100644 index 0000000..7d18d49 --- /dev/null +++ b/app/common/service/CorpService.php @@ -0,0 +1,24 @@ +logic = (new CorpLogic()); + } + + /** + * 获取公司list + */ + public function getList() + { + return $this->logic->getList(); + } +} \ No newline at end of file From cb06f7a0e91903708c3edcb280911f9b95a2f918 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 17 Jul 2025 15:10:24 +0800 Subject: [PATCH 053/120] =?UTF-8?q?crm=E9=A6=96=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 7 +++++++ config/route.php | 2 ++ 2 files changed, 9 insertions(+) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 5fc0750..50e85b9 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -16,6 +16,13 @@ use support\Request; class CustomerController extends BaseController { + /** + * 不需要头信息的方法 + */ + protected $noNeedHeader = []; + + protected $noNeedLogin = ['getChannel', 'getCountry']; + /** * 客户分级 * @param Request $request diff --git a/config/route.php b/config/route.php index 413f864..4fd7025 100644 --- a/config/route.php +++ b/config/route.php @@ -101,6 +101,8 @@ Route::group('/crm', function () { Route::any('/customer/restore', [app\crm\controller\CustomerController::class, 'restore']); Route::any('/customer/delete', [app\crm\controller\CustomerController::class, 'delete']); Route::any('/customer/waitFollow', [app\crm\controller\CustomerController::class, 'waitFollow']); + Route::any('/customer/statistics', [app\crm\controller\CustomerController::class, 'statistics']); Route::any('/customer/getChannel', [app\crm\controller\CustomerController::class, 'getChannel']); + Route::any('/customer/getCountry', [app\crm\controller\CustomerController::class, 'getCountry']); }); From 02b752aa98e5094d10a0838cd2f3710b54ba72a5 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Thu, 17 Jul 2025 16:25:47 +0800 Subject: [PATCH 054/120] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/TagGroupController.php | 15 ++++++++++----- config/route.php | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/crm/controller/TagGroupController.php b/app/crm/controller/TagGroupController.php index 7e5dff1..3cf24e1 100644 --- a/app/crm/controller/TagGroupController.php +++ b/app/crm/controller/TagGroupController.php @@ -17,7 +17,7 @@ class TagGroupController extends BaseController { $list = TagGroup::with('tags')->get(); $languageArr = Constants::LANGUAGE; - $language = $request->user['language']; + $language = 'zh_CN'; foreach ($list as &$item) { $item['names'] = unserialize($item['names']); if (in_array($language, $languageArr)) { @@ -52,10 +52,15 @@ class TagGroupController extends BaseController return $this->error( trans('tag_group_id') . trans('not_null')); } - if(empty($tagId)){ - $res = CustomerTag::where(['customer_id' => $customerId, 'group_id' => $groupId])->delete(); - }else{ - $res = CustomerTag::updateOrCreate(['customer_id' => $customerId, 'group_id' => $groupId], ['tag_id' => $tagId]); + + try { + if(empty($tagId)){ + $res = CustomerTag::where(['customer_id' => $customerId, 'group_id' => $groupId])->delete(); + }else{ + $res = CustomerTag::updateOrCreate(['customer_id' => $customerId, 'group_id' => $groupId], ['tag_id' => $tagId]); + } + } catch (\Throwable $e) { + return $this->error($e->getMessage()); } if($res){ diff --git a/config/route.php b/config/route.php index 4fd7025..b694023 100644 --- a/config/route.php +++ b/config/route.php @@ -104,5 +104,8 @@ Route::group('/crm', function () { Route::any('/customer/statistics', [app\crm\controller\CustomerController::class, 'statistics']); Route::any('/customer/getChannel', [app\crm\controller\CustomerController::class, 'getChannel']); Route::any('/customer/getCountry', [app\crm\controller\CustomerController::class, 'getCountry']); + + Route::any('/tagGroup/list', [app\crm\controller\TagGroupController::class, 'list']); + Route::any('/tagGroup/updatePivot', [app\crm\controller\TagGroupController::class, 'updatePivot']); }); From 4988129503c310a764adb2a231eb14d1a025e39a Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 18 Jul 2025 10:04:54 +0800 Subject: [PATCH 055/120] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UserController.php | 19 +++++++++++++++++++ app/common/logic/UserLogic.php | 18 ++++++++++++++++++ app/common/model/User.php | 7 ++++++- app/common/service/UserService.php | 14 ++++++++++++++ config/route.php | 6 ++++++ 5 files changed, 63 insertions(+), 1 deletion(-) diff --git a/app/common/controller/UserController.php b/app/common/controller/UserController.php index efab4a1..2a55181 100644 --- a/app/common/controller/UserController.php +++ b/app/common/controller/UserController.php @@ -22,4 +22,23 @@ class UserController extends BaseController } } + /** + * 编辑用户 + */ + public function edit(Request $request) + { + try { + $uid = $request->user->uid; + $data = $request->all(); + $result = (new UserService())->edit($uid,$data); + unset($result['password']); + unset($result['union_id']); + unset($result['open_id']); + unset($result['tenant_key']); + return $this->success($result, '编辑成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + } \ No newline at end of file diff --git a/app/common/logic/UserLogic.php b/app/common/logic/UserLogic.php index 1d6dc93..28ebf6e 100644 --- a/app/common/logic/UserLogic.php +++ b/app/common/logic/UserLogic.php @@ -24,4 +24,22 @@ class UserLogic return $user; } + + /** + * 用户信息 + */ + public function edit(int $uid, array $data) + { + $user = User::find($uid); + + if (!$user) { + throw new BusinessException('账户不存在', 404); + } + + $user->fill($data); + $user->save(); + + return $user; + } + } \ No newline at end of file diff --git a/app/common/model/User.php b/app/common/model/User.php index e9b4627..5d1b8f0 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -54,6 +54,7 @@ class User extends Model 'sn', 'name', 'avatar', + 'sex', 'password', 'phone', 'email', @@ -63,7 +64,11 @@ class User extends Model 'union_id', 'user_id', 'open_id', - 'tenant_key' + 'tenant_key', + 'country_id', + 'position', + 'socialize', + 'socialize_pic', ]; // 用户所有角色 diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 98375bc..b927c52 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -37,4 +37,18 @@ class UserService 'permissions' => $permissionCodes ]; } + + /** + * 获取用户信息(包括菜单和权限) + */ + public function edit(int $uid, array $data) + { + unset($data['password']); + unset($data['phone']); + unset($data['email']); + unset($data['sn']); + + $user = $this->logic->edit($uid,$data); + return $user; + } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index b694023..3563589 100644 --- a/config/route.php +++ b/config/route.php @@ -104,8 +104,14 @@ Route::group('/crm', function () { Route::any('/customer/statistics', [app\crm\controller\CustomerController::class, 'statistics']); Route::any('/customer/getChannel', [app\crm\controller\CustomerController::class, 'getChannel']); Route::any('/customer/getCountry', [app\crm\controller\CustomerController::class, 'getCountry']); + Route::any('/customer/followNext', [app\crm\controller\CustomerController::class, 'followNext']); Route::any('/tagGroup/list', [app\crm\controller\TagGroupController::class, 'list']); Route::any('/tagGroup/updatePivot', [app\crm\controller\TagGroupController::class, 'updatePivot']); + + Route::any('/upload/image', [app\common\controller\UploadController::class, 'uploadImage']); + + + Route::any('/user/edit', [app\common\controller\UserController::class, 'edit']); }); From deb6a2db9778b528c3bc0aa2e5f93ca50252d111 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 18 Jul 2025 14:07:04 +0800 Subject: [PATCH 056/120] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UserController.php | 14 ++++++++++++++ app/common/service/UserService.php | 24 ++++++++++++++++++++++++ config/route.php | 1 + 3 files changed, 39 insertions(+) diff --git a/app/common/controller/UserController.php b/app/common/controller/UserController.php index 2a55181..fc8413d 100644 --- a/app/common/controller/UserController.php +++ b/app/common/controller/UserController.php @@ -22,6 +22,20 @@ class UserController extends BaseController } } + /** + * 用户信息 + */ + public function getUser(Request $request) + { + try { + $uid = $request->user->uid; + $result = (new UserService())->getUser($uid); + return $this->success($result, '获取用户信息'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + /** * 编辑用户 */ diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index b927c52..f5daf14 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -3,6 +3,7 @@ namespace app\common\service; use app\common\logic\UserLogic; +use app\crm\model\Country; class UserService { @@ -38,6 +39,29 @@ class UserService ]; } + + /** + * 获取用户信息(包括菜单和权限) + */ + public function getUser(int $uid) + { + $user = $this->logic->userInfo($uid); + return [ + 'id' => $user->id, + 'name' => $user->name, + 'position' => $user->position, + 'socialize' => $user->socialize, + 'socialize_pic' => $user->socialize_pic, + 'country_id' => $user->country_id, + 'country_txt' => Country::where('id',$user->country_id)->value('cn_addr'), + 'avatar' => $user->avatar, + 'phone' => $user->phone, + 'email' => $user->email, + 'status' => $user->status, + 'roles' => $user->roles->pluck('name') + ]; + } + /** * 获取用户信息(包括菜单和权限) */ diff --git a/config/route.php b/config/route.php index 3563589..737ce1b 100644 --- a/config/route.php +++ b/config/route.php @@ -40,6 +40,7 @@ Route::group('/common', function () { Route::group('/user', function () { Route::any('/info', [app\common\controller\UserController::class, 'info']); + Route::any('/get', [app\common\controller\UserController::class, 'getUser']); }); Route::group('/preset', function () { From c09bf049fb3841332d31f3de9aaeaaeb3818b419 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 18 Jul 2025 15:15:37 +0800 Subject: [PATCH 057/120] =?UTF-8?q?=E5=AE=A2=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UserController.php | 3 +- app/common/service/UserService.php | 8 +- app/crm/controller/CustomerController.php | 4 +- app/crm/model/Customer.php | 55 ----------- app/crm/service/CustomerService.php | 110 ++++++++++++++++++++-- 5 files changed, 114 insertions(+), 66 deletions(-) diff --git a/app/common/controller/UserController.php b/app/common/controller/UserController.php index fc8413d..c32f153 100644 --- a/app/common/controller/UserController.php +++ b/app/common/controller/UserController.php @@ -29,7 +29,8 @@ class UserController extends BaseController { try { $uid = $request->user->uid; - $result = (new UserService())->getUser($uid); + $corp_abbr = $request->basic_data['corp_abbr']; + $result = (new UserService())->getUser($uid,$corp_abbr); return $this->success($result, '获取用户信息'); } catch (\Throwable $e) { return $this->error($e->getMessage()); diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index f5daf14..c55c4cc 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -3,6 +3,7 @@ namespace app\common\service; use app\common\logic\UserLogic; +use app\common\model\Corp; use app\crm\model\Country; class UserService @@ -43,19 +44,24 @@ class UserService /** * 获取用户信息(包括菜单和权限) */ - public function getUser(int $uid) + public function getUser(int $uid, $corp_abbr) { $user = $this->logic->userInfo($uid); + $corp_name = Corp::where('abbr', $corp_abbr)->value('name'); + $ossservice = new OssService(); return [ 'id' => $user->id, 'name' => $user->name, 'position' => $user->position, 'socialize' => $user->socialize, 'socialize_pic' => $user->socialize_pic, + 'socialize_pic_full' => (!empty($user->socialize_pic))?$ossservice->generatePrivateUrl($user->socialize_pic):'', 'country_id' => $user->country_id, 'country_txt' => Country::where('id',$user->country_id)->value('cn_addr'), 'avatar' => $user->avatar, + 'avatar_full' => (!empty($user->avatar))?$ossservice->generatePrivateUrl($user->avatar):'', 'phone' => $user->phone, + 'corp_name' => $corp_name, 'email' => $user->email, 'status' => $user->status, 'roles' => $user->roles->pluck('name') diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 50e85b9..775666f 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -166,7 +166,7 @@ class CustomerController extends BaseController return $this->success($this->page($result)); } catch (\Throwable $e) { - return $this->error($e->getMessage()); + return $this->error($e->getMessage().$e->getFile().$e->getLine()); } } @@ -217,7 +217,7 @@ class CustomerController extends BaseController return $this->error('记录不存在'); } - $res = Customer::getCustomer($res, $request, true); + $res = CustomerService::getCustomer($res, 'zh_CN', true); return $this->success($res); } diff --git a/app/crm/model/Customer.php b/app/crm/model/Customer.php index f28fb11..af5c571 100644 --- a/app/crm/model/Customer.php +++ b/app/crm/model/Customer.php @@ -58,61 +58,6 @@ class Customer extends Model return $date->format(Carbon::parse($date)->toDateTimeString()); } - /** - * @param $customer - * @param $request - * @param $tag 是否返回客户标签 - * @return mixed - */ - public static function getCustomer($customer, $request, $tag = false) - { - $language = 'zh_CN' ?: 'en'; - $customer['grade_txt'] = Util::language($language, Constants::GRADE_EN[$customer['grade']], Constants::GRADE_ZH[$customer['grade']], Constants::GRADE_RU[$customer['grade']]); - $customer['magnitude_txt'] = Util::language($language, Constants::MAGNITUDE_EN[$customer['magnitude']], Constants::MAGNITUDE[$customer['magnitude']], Constants::MAGNITUDE_RU[$customer['magnitude']]); - $customer['sex_txt'] = Util::language($language, Constants::SEX_EN[$customer['sex']], Constants::SEX[$customer['sex']], Constants::SEX_RU[$customer['sex']]); - - if ($customer['grade']<=2) { - $customer['follow'] = 0; - $customer['follow_txt'] = Util::language($language,'Following','跟进中','Продолжение'); - }elseif ($customer['grade']<5) { - $customer['follow'] = 1; - $customer['follow_txt'] = Util::language($language,'transacted','已成交','Транзакция завершена'); - }else{ - $customer['follow'] = 2; - $customer['follow_txt'] = Util::language($language,'lost','已流失','Потерянный'); - } - - $where = [ - 'customer_id' => $customer['id'] - ]; - $begin_follow = Follow::select('id','created_at','updated_at') - ->where($where) - ->min('created_at'); - - - $customer['begin_follow'] = Util::timeTotDate($begin_follow); -// $customer['created_at'] = Util::timeTotDate($customer['created_at']); -// $customer['updated_at'] = Util::timeTotDate($customer['updated_at']); - - $customer = Util::formatDate($customer,['follow_last']); -// $nameLang = $language=='en'?'name_en':($language=='ru'?'name_ru':'name'); - $nameLang = 'name'; - $countryLang = $language=='en'?'en_addr':'cn_addr'; - $customer['car_type_name'] = empty($customer['car_type_id'])?'':CarType::where(['id'=>$customer['car_type_id']])->value($nameLang); - $customer['country_name'] = empty($customer['country_id'])?'':Country::where(['id'=>$customer['country_id']])->value($countryLang); - $customer['channel_name'] = empty($customer['channel_id'])?'':Channel::where(['id'=>$customer['channel_id']])->value($nameLang); -// $customer['last_follow'] = $last_follow; - - if ($tag) { - foreach ($customer['tags'] as &$tag) { - $tag['names'] = unserialize($tag['names']); - $tag['name'] = $tag['names'][$language]; - } - } - - return $customer; - } - /** * 客户的标签 */ diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index f085f44..648c0d8 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -3,7 +3,13 @@ namespace app\crm\service; use app\crm\logic\CustomerLogic; +use app\crm\model\CarType; +use app\crm\model\Channel; +use app\crm\model\Country; use app\crm\model\Customer; +use app\crm\model\Follow; +use app\crm\utils\Util; +use config\constants\Constants; class CustomerService { @@ -13,18 +19,108 @@ class CustomerService // $perPage = $request->input('per_page', 10); $perPage = $params['size'] ?? 10; $page = $params['current'] ?? 1; + $keyword = $params['keyword'] ?? ''; + $recycle = $params['recycle'] ?? ''; + $grade = $params['grade'] ?? ''; $where = []; $user_id =$params['user_id']; if(empty($user_id) || $user_id == 0){ $where[] = ['user_id', '=', $user_id]; } - $user = Customer::where($where)->paginate($perPage, ['*'], 'page', $page); -// $user = Customer::with('user')->where($where)->paginate($perPage, ['*'], 'page', $page); -// $user = $user->toArray(); -// foreach ($user['data'] as &$item) { -// unset($item['password']); -// } - return $user; + + $seas = $params['seas'] ?? ''; + if($seas == 'seas'){ + $where[] = ['user_id', 0]; + } + if($recycle == 'recycle'){ + $where[] = ['user_id', $user_id]; + } + + $query = Customer::where($where); + + if(!empty($tagId)){ + $query->whereHas('tags', function($query) use ($tagId){ + $query->where('tag.id', $tagId); + }); + } + if (!Util::isEmpty($keyword)) { + $query->where(function ($query) use ($keyword) { + $query->where('contacts', 'like', "%{$keyword}%") + ->orWhere('phone', 'like', "%{$keyword}%"); + }); + } + if (!Util::isEmpty($grade)) { + $query->whereIn('grade', $grade); + } + + if ($recycle == 'recycle' || $seas == 'seas') { + $query->onlyTrashed(); + } + + $customer = Customer::where($where)->paginate($perPage, ['*'], 'page', $page); + foreach ($customer as &$item) { + $item = $this->getCustomer($item); + if($seas == 'seas'){ + $item['seas_time'] = time()-strtotime($item['deleted_at']); + } + } + unset($item); + + return $customer; + } + + + /** + * @param $customer + * @param $request + * @param $tag 是否返回客户标签 + * @return mixed + */ + public static function getCustomer($customer, $language = 'zh_CN', $tag = false) + { + $customer['grade_txt'] = Util::language($language, Constants::GRADE_EN[$customer['grade']], Constants::GRADE_ZH[$customer['grade']], Constants::GRADE_RU[$customer['grade']]); + $customer['magnitude_txt'] = Util::language($language, Constants::MAGNITUDE_EN[$customer['magnitude']], Constants::MAGNITUDE[$customer['magnitude']], Constants::MAGNITUDE_RU[$customer['magnitude']]); + $customer['sex_txt'] = Util::language($language, Constants::SEX_EN[$customer['sex']], Constants::SEX[$customer['sex']], Constants::SEX_RU[$customer['sex']]); + + if ($customer['grade']<=2) { + $customer['follow'] = 0; + $customer['follow_txt'] = Util::language($language,'Following','跟进中','Продолжение'); + }elseif ($customer['grade']<5) { + $customer['follow'] = 1; + $customer['follow_txt'] = Util::language($language,'transacted','已成交','Транзакция завершена'); + }else{ + $customer['follow'] = 2; + $customer['follow_txt'] = Util::language($language,'lost','已流失','Потерянный'); + } + + $where = [ + 'customer_id' => $customer['id'] + ]; + $begin_follow = Follow::select('id','created_at','updated_at') + ->where($where) + ->min('created_at'); + + + $customer['begin_follow'] = Util::timeTotDate($begin_follow); +// $customer['created_at'] = Util::timeTotDate($customer['created_at']); +// $customer['updated_at'] = Util::timeTotDate($customer['updated_at']); + + $customer = Util::formatDate($customer,['follow_last']); + $nameLang = $language=='en'?'name_en':($language=='ru'?'name_ru':'name'); + $countryLang = $language=='en'?'en_addr':'cn_addr'; +// $customer['car_type_name'] = empty($customer['car_type_id'])?'':CarType::where(['id'=>$customer['car_type_id']])->value($nameLang); + $customer['country_name'] = empty($customer['country_id'])?'':Country::where(['id'=>$customer['country_id']])->value($countryLang); + $customer['channel_name'] = empty($customer['channel_id'])?'':Channel::where(['id'=>$customer['channel_id']])->value($nameLang); +// $customer['last_follow'] = $last_follow; + + if ($tag) { + foreach ($customer['tags'] as &$tag) { + $tag['names'] = unserialize($tag['names']); + $tag['name'] = $tag['names'][$language]; + } + } + + return $customer; } From cc98a78ad6a17c576c1336919aba355d78f302ae Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 18 Jul 2025 15:43:51 +0800 Subject: [PATCH 058/120] =?UTF-8?q?=E8=B7=9F=E8=BF=9B=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/FollowService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php index b97d5f1..0d3fd8e 100644 --- a/app/crm/service/FollowService.php +++ b/app/crm/service/FollowService.php @@ -29,7 +29,7 @@ class FollowService //跟进id $id = $param['id'] ?? null; - if (!Util::isEmpty($id)) { + if (!empty($id)) { $where['id'] = $id; } @@ -58,7 +58,7 @@ class FollowService $ossservice = new OssService(); $oss_picture = $ossservice->generatePrivateUrl($follow['picture'], 86400); } - $follow['oss_picture'] = $oss_picture; + $follow['picture_full'] = $oss_picture; // $follow = Util::formatDate($follow); return $follow; } From edb8c5e9e2611796e3853f48899a2220461e5962 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 18 Jul 2025 16:45:16 +0800 Subject: [PATCH 059/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UserController.php | 19 +++++++++++++++++++ app/common/logic/UserLogic.php | 11 +++++++++++ app/common/service/UserService.php | 20 +++++++++++++++++++- app/crm/service/CustomerService.php | 21 ++++++++++++++++++++- config/route.php | 1 + 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/app/common/controller/UserController.php b/app/common/controller/UserController.php index c32f153..2a22145 100644 --- a/app/common/controller/UserController.php +++ b/app/common/controller/UserController.php @@ -56,4 +56,23 @@ class UserController extends BaseController } } + /** + * 修改密码 + * @param Request $request + * @return \support\Response + * @throws \Random\RandomException + */ + public function savePassword(Request $request) + { + try { + $uid = $request->user->uid; + $data = $request->all(); + $result = (new UserService())->savePassword($uid,$data);; + return $this->success($result, '编辑成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + } \ No newline at end of file diff --git a/app/common/logic/UserLogic.php b/app/common/logic/UserLogic.php index 28ebf6e..4b58247 100644 --- a/app/common/logic/UserLogic.php +++ b/app/common/logic/UserLogic.php @@ -42,4 +42,15 @@ class UserLogic return $user; } + /** + * 修改密码 + */ + public function savePassword(int $uid, array $data) + { + $password = password_hash($data['password_new'], PASSWORD_DEFAULT); + $res = User::where('id',$uid)->update(['password' => $password]); + + return $res; + } + } \ No newline at end of file diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index c55c4cc..85684d3 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -4,7 +4,9 @@ namespace app\common\service; use app\common\logic\UserLogic; use app\common\model\Corp; +use app\common\model\User; use app\crm\model\Country; +use support\exception\BusinessException; class UserService { @@ -69,7 +71,7 @@ class UserService } /** - * 获取用户信息(包括菜单和权限) + * 编辑用户信息 */ public function edit(int $uid, array $data) { @@ -81,4 +83,20 @@ class UserService $user = $this->logic->edit($uid,$data); return $user; } + + /** + * 修改用户密码 + */ + public function savePassword(int $uid, array $data) + { + // 对密码进行哈希处理 + $password = $data['password']; + $user = User::find($uid); + if (!password_verify($password, $user->password)) { + throw new BusinessException('密码错误'); + } + + $user = $this->logic->savePassword($uid,$data); + return $user; + } } \ No newline at end of file diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 648c0d8..51737b1 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -21,7 +21,8 @@ class CustomerService $page = $params['current'] ?? 1; $keyword = $params['keyword'] ?? ''; $recycle = $params['recycle'] ?? ''; - $grade = $params['grade'] ?? ''; + $follow = $params['follow'] ?? ''; + $follow_last = $params['follow_last'] ?? ''; $where = []; $user_id =$params['user_id']; if(empty($user_id) || $user_id == 0){ @@ -36,6 +37,24 @@ class CustomerService $where[] = ['user_id', $user_id]; } + + $grade = []; + if (!Util::isEmpty($follow) && $follow == 0) { + $where[] = ['grade', '<=', 2]; + } elseif (!Util::isEmpty($follow) && $follow == 1) { + $grade = [3, 4]; + } elseif (!Util::isEmpty($follow) && $follow == 2) { + $where[] = ['grade', '=', 5]; + } + + if (!Util::isEmpty($follow_last) && $follow_last == 0) { + $where[] = ['follow_last', '>', strtotime('-1 day')]; + } elseif (!Util::isEmpty($follow_last) && $follow_last == 1) { + $where[] = ['follow_last', '>', strtotime('-3 day')]; + } elseif (!Util::isEmpty($follow_last) && $follow_last == 2) { + $where[] = ['follow_last', '>', strtotime('-7 day')]; + } + $query = Customer::where($where); if(!empty($tagId)){ diff --git a/config/route.php b/config/route.php index 737ce1b..d0c9db4 100644 --- a/config/route.php +++ b/config/route.php @@ -114,5 +114,6 @@ Route::group('/crm', function () { Route::any('/user/edit', [app\common\controller\UserController::class, 'edit']); + Route::any('/user/savePassword', [app\common\controller\UserController::class, 'savePassword']); }); From c87643c3ea50d7810a08ad20996762ac7cb054bb Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 18 Jul 2025 17:08:58 +0800 Subject: [PATCH 060/120] =?UTF-8?q?=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 775666f..8eee3ac 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -238,10 +238,10 @@ class CustomerController extends BaseController if (Util::isEmpty($country_id)) { // return $this->error('country_id'.trans('not_null')); } - $car_type_id = Util::getParam($request, 'car_type_id'); - if (Util::isEmpty($car_type_id)) { - return $this->error(trans('car_type_not_null')); - } +// $car_type_id = Util::getParam($request, 'car_type_id'); +// if (Util::isEmpty($car_type_id)) { +// return $this->error(trans('car_type_not_null')); +// } $contacts = Util::getParam($request, 'contacts'); $email = Util::getParam($request, 'email'); $phone = Util::getParam($request, 'phone'); @@ -358,7 +358,8 @@ class CustomerController extends BaseController } $userId = $request->user->uid; //$created_at = $request->user['created_at']; - $created_at = User::find($userId)->created_at; + $created_at = User::find($userId)->value('created_at'); + $created_at = strtotime($created_at); $day = ceil((time() - $created_at + 1) / 60 / 60 / 24); $where[] = ['user_id', '=', $userId]; $where[] = ['corp_abbr', '=', $request->basic_data['corp_abbr']]; From 228408149b1456886e5b2bc82e460e8642d44469 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 18 Jul 2025 17:32:42 +0800 Subject: [PATCH 061/120] crud --- app/admin/controller/CorpController.php | 40 ++ app/admin/logic/CorpLogic.php | 17 + app/admin/service/CorpService.php | 122 ++++ app/common/controller/AuthController.php | 2 +- app/common/controller/BaseController.php | 44 ++ app/common/logic/AuthLogic_bak.php | 593 ------------------ app/common/logic/BaseLogic.php | 86 --- app/common/logic/CrudLogic_bak.php | 106 ++++ app/common/model/UserFeishuApp_bak.php | 69 -- app/common/service/AuthService_bak.php | 155 ----- app/common/service/CrudService.php | 143 +++++ app/common/traits/DataTransformer.php | 144 +++-- app/common/traits/FormatKvTrait.php | 154 +++++ .../traits/crud/CrudControllerTrait.php | 126 ++++ app/common/traits/crud/CrudLogicTrait.php | 96 +++ app/common/traits/crud/CrudServiceTrait.php | 209 ++++++ .../traits/crud/CrudServiceTrait_bak2.php | 270 ++++++++ app/common/utils/CrudUtil.php | 406 ++++++++++++ app/controller/IndexController.php | 3 + app/middleware/AuthCheck.php | 62 +- app/middleware/PermissionMiddleware.php | 33 - config/middleware.php | 1 - config/route.php | 7 + 23 files changed, 1880 insertions(+), 1008 deletions(-) create mode 100644 app/admin/controller/CorpController.php create mode 100644 app/admin/logic/CorpLogic.php create mode 100644 app/admin/service/CorpService.php delete mode 100644 app/common/logic/AuthLogic_bak.php delete mode 100644 app/common/logic/BaseLogic.php create mode 100644 app/common/logic/CrudLogic_bak.php delete mode 100644 app/common/model/UserFeishuApp_bak.php delete mode 100644 app/common/service/AuthService_bak.php create mode 100644 app/common/service/CrudService.php create mode 100644 app/common/traits/FormatKvTrait.php create mode 100644 app/common/traits/crud/CrudControllerTrait.php create mode 100644 app/common/traits/crud/CrudLogicTrait.php create mode 100644 app/common/traits/crud/CrudServiceTrait.php create mode 100644 app/common/traits/crud/CrudServiceTrait_bak2.php create mode 100644 app/common/utils/CrudUtil.php delete mode 100644 app/middleware/PermissionMiddleware.php diff --git a/app/admin/controller/CorpController.php b/app/admin/controller/CorpController.php new file mode 100644 index 0000000..748f79f --- /dev/null +++ b/app/admin/controller/CorpController.php @@ -0,0 +1,40 @@ +service = new CorpService(); + } + + protected function indexFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + return $data; + } + + /** + * 如果想对某些接口做额外校验或改写, + * 也可以在这里覆盖 trait 提供的方法: + * + * public function store(Request $request) + * { + * // 自定义验证 + * $this->validate(...); + * // 然后再调用标准逻辑 + * return parent::store($request); + * } + */ +} \ No newline at end of file diff --git a/app/admin/logic/CorpLogic.php b/app/admin/logic/CorpLogic.php new file mode 100644 index 0000000..99fa0ee --- /dev/null +++ b/app/admin/logic/CorpLogic.php @@ -0,0 +1,17 @@ +logicComplex(function($q) use ($params) { + // // 自定义 join、where、select + // return $q->where('status', 1) + // ->orderBy('created_at','desc') + // ->paginate($params['limit'] ?? 15); + // })->toArray(); + // } + + // /** + // * 复杂查询示例: + // * 只要在 URL 里添加 ?complex_query=1, + // * 并传入你需要的任何参数, + // * `serviceList()` 就会走到这里来: + // */ + // protected function complexQuery(Request $request): array + // { + // // 假设前端传了 date_from、date_to + // $from = $request->get('date_from'); + // $to = $request->get('date_to'); + + // // 交给 CrudUtil 执行一个自定义 query + // $pager = $this->logicComplex(function($q) use ($from, $to) { + // $q->where('status', 1); + + // if ($from && $to) { + // $q->whereBetween('created_at', [$from, $to]); + // } + + // // 再加个排序 + // $q->orderBy('created_at','desc'); + + // // 用 paginate 而不是 listAll + // return $q->paginate( + // /* perPage */ (int)request()->get('limit',15), + // ['*'], + // 'page', + // (int)request()->get('page',1) + // ); + // }); + + // // 把 Paginator 转成数组格式 + // return [ + // 'total' => $pager->total(), + // 'data' => $pager->items(), + // ]; + // } + + + // protected function afterServiceList(array $res): array + // { + // // 对列表的 data 做处理 + // $res['data'] = array_map(fn($i)=>[...], $res['data']); + // return $res; + // } + + // protected function afterServiceShow($item) + // { + // // 对详情对象处理 + // $item['extra']=123; + // return $item; + // } + + // afterServiceStore/Update/Destroy 同理 + + + // protected function afterServiceList(mixed $result): array + // { + // if ($result['records']) { + // $result['records'] = array_map(fn($i) => [...$i], $result['records']); + // } + + // return $result; + // } +} \ No newline at end of file diff --git a/app/common/controller/AuthController.php b/app/common/controller/AuthController.php index 02676e8..93c39fb 100644 --- a/app/common/controller/AuthController.php +++ b/app/common/controller/AuthController.php @@ -12,7 +12,7 @@ class AuthController extends BaseController /** * 不需要头信息的方法 */ - protected $noNeedHeader = ['feishuAuth']; + protected $noNeedHeaderData = ['feishuAuth']; protected $noNeedLogin = ['feishuScope', 'login', 'register', 'feishuAuth']; diff --git a/app/common/controller/BaseController.php b/app/common/controller/BaseController.php index 2dbf636..f617239 100644 --- a/app/common/controller/BaseController.php +++ b/app/common/controller/BaseController.php @@ -10,10 +10,54 @@ use support\Request; class BaseController { + /** + * 响应处理、数据格式化处理 + */ use ResponseTrait, DataTransformer; + /** + * 验证器 + * @var Validate + */ protected mixed $validator = null; + /** + * @var Model + */ + protected $model = null; + + /** + * 不登录不鉴权 + * @var array + */ + protected $noNeedLogin = []; + + /** + * 登录不鉴权 + * @var array + */ + protected $noNeedAuth = []; + + /** + * 不用头部数据 + * @var array + */ + protected $noNeedHeaderData = []; + + /** + * 数据限制 + * null 不做限制,任何管理员都可以查看该表的所有数据 + * auth 管理员能看到自己以及自己的子管理员插入的数据 + * personal 管理员只能看到自己插入的数据 + * @var string + */ + protected $dataLimit = null; + + /** + * 数据限制字段 + */ + protected $dataLimitField = 'admin_id'; + /** * 验证数据 * diff --git a/app/common/logic/AuthLogic_bak.php b/app/common/logic/AuthLogic_bak.php deleted file mode 100644 index 0e68093..0000000 --- a/app/common/logic/AuthLogic_bak.php +++ /dev/null @@ -1,593 +0,0 @@ -validateUser($data['account'], $data['password']); - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 飞书登录 - * 如果用户不存在则新增用户信息 - */ - public function feishuLogin(array $data, bool $isLogin = true): array - { - $feishuUserInfo = $this->getFeishuUserInfo($data); - $user = $this->getOrCreateUser($feishuUserInfo); - - $system = $this->validateSystem($data['system_code']); - if ($user->isLogin === true) { - $this->validateSystemPermission($user, $system); - } - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 扫码登录 - */ - public function qrCodeLogin(array $data): array - { - $userId = $this->validateQrCodeToken($data['qr_code_token']); - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = $this->validateSystem($data['system_code']); - $this->validateSystemPermission($user, $system); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - - // 记录 token 到 Redis 和 MySQL - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 注册 - */ - public function register(array $data) - { - // 获取可用用户编号 - $sn = $this->getAvailableUserSn(); - $data['sn'] = $sn->user_sn; - - // 对密码进行哈希处理 - $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); - - // 获取企业ID - $corpInfo = $this->validateCorp($data['corp_abbr']); - $data['corp_id'] = $corpInfo->id; - - // 创建用户记录 - $user = $this->createUser($data); - - // 更新用户编号的用户 ID - $this->updateUserId($user->id, $sn->user_sn); - - // 注册后如果直接登录(返回token) - $system = $this->validateSystem($data['system_code']); - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); - $this->recordUserToken($user->id, $token, $data['system_code']); - - return [ - 'user' => $user, - 'access_token' => $token, - 'refresh_token' => $refreshToken, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - public function updateUserId($userId, $userSn) - { - $updated = UserSn::where('user_sn', $userSn) - ->update(['user_id' => $userId]); - - if ($updated === 0) { - throw new BusinessException('用户编号不存在或更新失败'); - } - - return $updated; - } - - /** - * 获取可用的用户编号 - * - * @return \app\common\model\UserSn 用户编号模型实例 - * @throws BusinessException 当未找到适用编号时抛出 - */ - private function getAvailableUserSn(): \app\common\model\UserSn - { - $userSnModel = new UserSn(); - $sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$sn) { - Log::error('用户注册时未找到适用编号'); - throw new BusinessException('未找到适用编号'); - } - return $sn; - } - - /** - * 创建用户记录 - * - * @param array $data 包含用户信息的数组 - * @return \app\common\model\User 用户模型实例 - */ - private function createUser(array $data): \app\common\model\User - { - try { - return User::create($data); - } catch (\Throwable $e) { - Log::error('用户注册时创建用户记录失败', [ - 'data' => $data, - 'error' => $e->getMessage() - ]); - throw new BusinessException('用户注册失败,请稍后重试', 500); - } - } - - /** - * 刷新token - * @param string $refreshToken - * @param string|null $systemCode 可选,指定系统 - * @return array - */ - public function refreshToken(string $refreshToken, ?string $systemCode = null): array - { - if (!$systemCode) throw new BusinessException('缺少system_code', 422); - - // 校验refresh_token - $userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}"); - if (!$userId) { - throw new BusinessException('refresh_token无效或已过期', 401); - } - $validToken = Redis::get("refresh_token:{$userId}:{$systemCode}"); - if ($validToken !== $refreshToken) { - throw new BusinessException('refresh_token无效或已过期', 401); - } - - $user = User::find($userId); - if (!$user || $user->status != 1) { - throw new BusinessException('用户无效', 403); - } - - $system = $this->validateSystem($systemCode); - $this->validateSystemPermission($user, $system); - - $payload = $this->buildJwtPayload($user, $system); - $token = $this->generateAccessToken($payload); - $this->recordUserToken($user->id, $token, $systemCode); - - return [ - 'access_token' => $token, - 'expires_in' => config('jwt.jwt_expiration'), - 'payload' => $payload, - ]; - } - - /** - * 记录用户token到 Redis 和 MySQL - */ - protected function recordUserToken(int $userId, string $token, string $systemCode) - { - $request = request(); - $ip = $request ? $request->getRealIp() : ''; - $ua = $request ? $request->header('user-agent', '') : ''; - $now = time(); - $expireAt = $now + config('jwt.jwt_expiration'); - - // Redis Set 用户token集合 - Redis::sAdd("user_tokens:{$userId}:{$systemCode}", $token); - Redis::expire("user_tokens:{$userId}:{$systemCode}", config('jwt.jwt_expiration')); - - // Redis Hash token元信息 - $meta = [ - 'system_code' => $systemCode, - 'login_ip' => $ip, - 'user_agent' => $ua, - 'login_time' => $now, - 'expired_at' => $expireAt, - ]; - Redis::hMSet("user_token_meta:{$token}", $meta); - Redis::expire("user_token_meta:{$token}", config('jwt.jwt_expiration')); - - // 可选:MySQL 持久化 - try { - UserTokenLog::create([ - 'user_id' => $userId, - 'token' => $token, - 'system_code'=> $systemCode, - 'login_ip' => $ip, - 'user_agent' => $ua, - 'login_time' => $now, - 'expired_at' => $expireAt, - 'is_valid' => 1, - ]); - } catch (\Throwable $e) { - Log::error('Token log write failed: '.$e->getMessage()); - } - } - - - /** - * 校验token有效性 - */ - public function verifyToken(string $token, string $systemCode): array - { - if (!$systemCode) throw new BusinessException('缺少system_code', 422); - - $payload = (new Jwt())->verifyToken($token); - - // 校验黑名单 - if (Redis::has("token_blacklist:{$token}")) { - throw new BusinessException('token已失效', 403); - } - - // 强制校验aud(系统隔离) - if ($systemCode && $payload['aud'] != $systemCode && $payload['aud'] != ($payload['system_code'] ?? '')) { - throw new BusinessException('token不属于当前系统', 403); - } - - return (array)$payload; - } - - /** - * 登出/失效token - */ - public function logout(?string $token, ?string $systemCode = null) - { - if ($token) { - Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - $aud = $payload['aud'] ?? null; - $sc = $systemCode ?? $aud; - if ($userId && $sc) { - Redis::sRem("user_tokens:{$userId}:{$sc}", $token); - } - Redis::del("user_token_meta:{$token}"); - UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } - } - - /** - * 强制拉黑token - */ - public function blockToken(string $token, ?string $systemCode = null) - { - if (!$token) { - throw new BusinessException('token不能为空', 422); - } - Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration')); - $payload = null; - try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {} - $userId = $payload['uid'] ?? null; - $aud = $payload['aud'] ?? null; - $sc = $systemCode ?? $aud; - if ($userId && $sc) { - Redis::sRem("user_tokens:{$userId}:{$sc}", $token); - } - Redis::del("user_token_meta:{$token}"); - UserTokenLog::where('token', $token)->update(['is_valid'=>0]); - } - - /** - * 获取用户所有活跃token及元信息(按system_code) - */ - public function getUserTokens(int $userId, string $systemCode): array - { - $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; - $result = []; - foreach ($tokens as $token) { - $meta = Redis::hGetAll("user_token_meta:{$token}"); - $result[] = [ - 'token' => $token, - 'meta' => $meta, - ]; - } - return $result; - } - - /** - * 强制某用户所有token全部下线 - */ - public function blockAllTokensOfUser(int $userId, string $systemCode) - { - $tokens = Redis::sMembers("user_tokens:{$userId}:{$systemCode}") ?: []; - foreach ($tokens as $token) { - $this->blockToken($token, $systemCode); - } - } - - /** ----------- 内部方法 ------------- **/ - - /** - * 用户验证 - */ - private function validateUser(string $account, string $password) - { - $user = User::byAccount($account)->first(); - if (!$user || !password_verify($password, $user->password)) { - throw new BusinessException('账号或密码错误', 401); - } - if ($user->status != 1) { - throw new BusinessException('账号已禁用', 403); - } - return $user; - } - - /** - * 系统验证 - */ - private function validateSystem(string $systemCode) - { - $system = Systems::where('code', $systemCode)->first(); - if (!$system) { - throw new BusinessException('系统不存在', 403); - } - return $system; - } - - /** - * 公司验证 - */ - private function validateCorp(string $corpAbbr) - { - $corp = Corp::where('abbr', $corpAbbr)->first(); - if (!$corp) { - throw new BusinessException('公司不存在', 403); - } - return $corp; - } - - /** - * 飞书应用信息 - */ - private function validateFeishuApp(int $corpId, string $systemCode) - { - $feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_code', $systemCode)->first(); - if (!$feishuAppInfo) { - throw new BusinessException('飞书应用不存在', 403); - } - return $feishuAppInfo; - } - - /** - * 用户系统权限校验 - */ - private function validateSystemPermission(User $user, Systems $system) - { - if ($system->is_role) { - $systemIds = $user->systems->pluck('id')->toArray(); - if (!in_array($system->id, $systemIds)) { - throw new BusinessException('无该系统权限', 403); - } - } - } - - /** - * JWT payload 构建 - * 可扩展 payload 字段 - */ - private function buildJwtPayload(User $user, ?Systems $system = null): array - { - $now = time(); - $exp = $now + config('jwt.jwt_expiration'); - $system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : []; - $role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : []; - - return [ - 'sub' => 'user_' . $user->id, - 'aud' => $system->code, - 'iat' => $now, - 'nbf' => $now, - 'exp' => $exp, - 'typ' => 'access', - 'uid' => $user->id, - 'role_ids' => $role_ids, - 'system_ids' => $system_ids - ]; - } - - /** - * JWT签发 - */ - private function generateAccessToken(array $payload) - { - return (new Jwt())->generateToken($payload); - } - - /** - * 刷新令牌生成 - */ - private function generateRefreshToken(int $userId, string $systemCode) - { - $refreshToken = bin2hex(random_bytes(32)); - // user_id+system_code只允许一个refresh_token生效 - Redis::set("refresh_token:{$userId}:{$systemCode}", $refreshToken, config('jwt.jwt_refresh_token_expiration')); - // 建立refresh_token到user_id+system_code映射,便于校验 - Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration')); - return $refreshToken; - } - - /** - * 验证扫码令牌 - */ - private function validateQrCodeToken(string $qrCodeToken) - { - $userId = Redis::get("qr_code_token:{$qrCodeToken}"); - if (!$userId) { - throw new BusinessException('扫码令牌无效或已过期', 401); - } - return $userId; - } - - /** - * 获取飞书用户信息 - */ - private function getFeishuUserInfo(array $data) - { - // 获取凭证 - $certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']); - // 获取token - $feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']); - $userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']); - // 获取用户信息 - $userInfo = $feishu->userInfo($userAccessToken['access_token']); - if ($userInfo['code'] != 0) { - throw new BusinessException('授权失败,请重试'); - } - return $userInfo['data'] + $data + $certificate; - } - - /** - * 查找或创建飞书用户 - */ - private function getOrCreateUser(array $feishuUserInfo) - { - $userFeishuApp = UserFeishuApp::firstOrCreate([ - 'name' => $feishuUserInfo['en_name'], - 'mobile' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), - 'union_id' => $feishuUserInfo['union_id'], - 'user_id' => $feishuUserInfo['user_id'], - 'open_id' => $feishuUserInfo['open_id'], - 'tenant_key' => $feishuUserInfo['tenant_key'] - ]); - - if ($userFeishuApp->uid) { - $user = User::find($userFeishuApp->uid); - $user->isLogin = true; - return $user; - } else { - $user = User::where('phone', $userFeishuApp->mobile)->first(); - $user->isLogin = true; - if (!$user) { - $user = $this->createUserByFeishu($feishuUserInfo, $userFeishuApp); - $user->isLogin = false; - } - $userFeishuApp->uid = $user->id; - $userFeishuApp->corp_id = $feishuUserInfo['corp_id']; - if (!$userFeishuApp->save()) { - throw new BusinessException('绑定用户飞书记录失败'); - } - return $user; - } - } - - /** - * 过滤和格式化手机号 - */ - private function sanitizePhone($mobile) - { - return isset($mobile) ? str_replace('+86', '', $mobile) : ''; - } - - /** - * 创建飞书用户 - */ - private function createUserByFeishu(array $feishuUserInfo, UserFeishuApp $userFeishuApp) - { - // 1. 获取适用的用户编号 - $userSn = UserSn::where('user_id', '=', 0)->inRandomOrder()->first(); - if (!$userSn) { - throw new BusinessException('未找到适用编号'); - } - - // 2. 创建用户 - $user = new User(); - $user->sn = $userSn->user_sn; - $user->name = $feishuUserInfo['en_name']; - $user->avatar = $feishuUserInfo['avatar_middle'] ?? ''; - $user->password = password_hash($userSn->user_sn, PASSWORD_DEFAULT); - $user->phone = $userFeishuApp->mobile; - $user->email = ''; - $user->system_code = $feishuUserInfo['system_code']; - $user->corp_id = $feishuUserInfo['corp_id']; - - if (!$user->save()) { - throw new BusinessException('用户创建失败'); - } - - // 更新使用状态 - $this->updateUserId($user->id, $userSn->user_sn); - - return $user; - } - - - /** - * 飞书应用信息 - */ - public function feishuAppInfo(string $corpAbbr, string $systemCode) - { - $corpInfo = $this->validateCorp($corpAbbr); - $fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemCode); - return [ - 'app_id' => $fsAppInfo->app_id, - 'app_secret' => $fsAppInfo->app_secret, - 'r_url' => $fsAppInfo->r_url, - 'scope' => $fsAppInfo->scopes, - 'state' => $fsAppInfo->state, - 'abbr' => $corpAbbr, - 'corp_id' => $corpInfo->id - ]; - } -} \ No newline at end of file diff --git a/app/common/logic/BaseLogic.php b/app/common/logic/BaseLogic.php deleted file mode 100644 index 94beff3..0000000 --- a/app/common/logic/BaseLogic.php +++ /dev/null @@ -1,86 +0,0 @@ -paginate($params['per_page'], ['*'], 'page', $params['page']); - } - - return $query->get(); - } - - /** - * 获取单个记录 - */ - public function getDetail(int $id, string $modelClass) - { - $record = $modelClass::find($id); - if (!$record) { - throw new BusinessException("记录不存在", 404); - } - - return $record; - } - - /** - * 创建记录 - */ - public function create(array $data, string $modelClass) - { - $model = new $modelClass; - $model->fill($data); // 根据字段填充数据 - try { - $model->save(); - } catch (\Throwable $e) { - throw new BusinessException("创建记录失败: " . $e->getMessage(), 500); - } - - return $model; - } - - /** - * 更新记录 - */ - public function update(array $data, string $modelClass) - { - $record = $modelClass::find($data['id']); - if (!$record) { - throw new BusinessException("记录不存在", 404); - } - - $record->update($data); - return $record; - } - - /** - * 删除记录 - */ - public function delete(int $id, string $modelClass) - { - $record = $modelClass::find($id); - if (!$record) { - throw new BusinessException("记录不存在", 404); - } - - try { - $record->delete(); - } catch (\Throwable $e) { - throw new BusinessException("删除记录失败: " . $e->getMessage(), 500); - } - - return true; - } -} diff --git a/app/common/logic/CrudLogic_bak.php b/app/common/logic/CrudLogic_bak.php new file mode 100644 index 0000000..91a4c16 --- /dev/null +++ b/app/common/logic/CrudLogic_bak.php @@ -0,0 +1,106 @@ +modelClass = $modelClass; + } + + /** + * 列表(带分页) + * @param array $config CrudUtil::buildQuery 接受的配置 + * @param int $limit + * @param int $page + */ + public function list(array $config, int $limit, int $page) + { + $config['model'] = $this->modelClass; + $query = CrudUtil::buildQuery($config); + return CrudUtil::paginate($query, $limit, $page); + } + + /** + * 列表(不分页) + */ + public function listAll(array $config) + { + $config['model'] = $this->modelClass; + $query = CrudUtil::buildQuery($config); + return CrudUtil::getAll($query); + } + + /** + * 根据主键查询单条 + */ + public function show($id, array $with = []) + { + return CrudUtil::findByPk($this->modelClass, $id, $with); + } + + /** + * 根据任意条件查询单条(可带 dataLimit) + */ + public function showBy(array $where, array $with = [], array $dataLimit = []) + { + return CrudUtil::findOneBy($this->modelClass, $where, $with, $dataLimit); + } + + /** + * 创建(可带 dataLimit) + */ + public function create(array $data, array $dataLimit = []) + { + return CrudUtil::create($this->modelClass, $data, $dataLimit); + } + + /** + * 主键更新(可带 dataLimit) + */ + public function updateByPk($id, array $data, array $dataLimit = []) + { + return CrudUtil::updateByPk($this->modelClass, $id, $data, $dataLimit); + } + + /** + * 任意条件更新(可带 dataLimit) + */ + public function updateBy(array $where, array $data, array $dataLimit = []) + { + return CrudUtil::updateBy($this->modelClass, $where, $data, $dataLimit); + } + + /** + * 主键删除 + */ + public function deleteByPk(array $ids) + { + return CrudUtil::deleteByPk($this->modelClass, $ids); + } + + /** + * 任意条件删除(可带 dataLimit) + */ + public function deleteBy(array $where, array $dataLimit = []) + { + return CrudUtil::deleteBy($this->modelClass, $where, $dataLimit); + } + + /** + * 复杂查询回调 + * @param callable $cb function(Builder $q){ ... } + */ + public function complex(callable $cb) + { + return CrudUtil::executeComplex($this->modelClass, $cb); + } +} \ No newline at end of file diff --git a/app/common/model/UserFeishuApp_bak.php b/app/common/model/UserFeishuApp_bak.php deleted file mode 100644 index 9aa06c0..0000000 --- a/app/common/model/UserFeishuApp_bak.php +++ /dev/null @@ -1,69 +0,0 @@ -find($userId); - if (!$user) { - return collect(); - } - - // 合并:角色资源 + 用户直接资源 - $all = collect(); - foreach ($user->roles as $role) { - $all = $all->merge($role->resources); - } - $all = $all->merge($user->resources); - $unique = $all->unique('id')->values(); - - Cache::set($cacheKey, $unique, 3600); - return $unique; - } - - /** - * 构建目录/菜单树,并给每个菜单节点挂载权限列表和打开方式 - */ - public static function getMenuTree(int $userId): array - { - $resources = self::getUserResources($userId); - $menus = $resources - ->filter(fn($r) => in_array($r->type, ['directory','menu'])) - ->values(); - return self::buildTree($menus, $resources); - } - - protected static function buildTree($nodes, $allResources, int $parentId = 0): array - { - $tree = []; - foreach ($nodes as $node) { - if ($node->parent_id !== $parentId) continue; - - // 子节点 - $children = self::buildTree($nodes, $allResources, $node->id); - - // 本级权限列表 - $authList = $allResources - ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) - ->map(fn($r) => [ - 'id' => $r->id, - 'title' => $r->name, - 'auth_mark' => $r->code, - ])->values()->all(); - - $tree[] = [ - 'id' => $node->id, - 'name' => $node->name, - 'path' => $node->url, - 'meta' => [ - 'title' => $node->name, - 'icon' => $node->icon, - 'keepAlive' => true, - 'authList' => $authList, - // 新增打开方式字段 - 'openType' => $node->open_type, // 'internal' | 'external' | 'iframe' - ], - 'children' => $children, - ]; - } - return $tree; - } - - /** - * 获取用户所有权限 code(仅 type=permission) - */ - public static function getPermissionCodes(int $userId): array - { - return self::getUserResources($userId) - ->filter(fn($res) => $res->type === 'permission') - ->pluck('code') - ->unique() - ->values() - ->all(); - } - - /** 清除缓存(角色/资源变更时调用) */ - public static function clearCache(int $userId): void - { - Cache::delete(self::cacheKey($userId)); - } - - public function login(array $data): array - { - $loginType = $data['login_type'] ?? 'upwd'; - $logic = new AuthLogic(); - - return match ($loginType) { - 'feishu' => $logic->feishuLogin($data), - 'qrcode' => $logic->qrCodeLogin($data), - default => $logic->uPwdLogin($data), - }; - } - - /** - * 用户注册 - * - * @param array $data - * @return User - */ - public function register(array $data): User - { - return (new AuthLogic())->register($data); - } - - - public function verifyToken(string $token): array - { - return (new AuthLogic())->verifyToken($token); - } - - public function refreshToken(string $refreshToken): array - { - return (new AuthLogic())->refreshToken($refreshToken); - } - - public function logout(?string $token) - { - (new AuthLogic())->logout($token); - } - - public function blockToken(string $token) - { - (new AuthLogic())->blockToken($token); - } -} \ No newline at end of file diff --git a/app/common/service/CrudService.php b/app/common/service/CrudService.php new file mode 100644 index 0000000..8d66a37 --- /dev/null +++ b/app/common/service/CrudService.php @@ -0,0 +1,143 @@ +logic = $logic; + } + + /** + * 列表接口 + * 支持前端直接传递: + * - filter_* → where + * - sort_by, order + * - fields (逗号分隔) + * - with (逗号分隔) + * - withFields (relation:field,field;...) + * - dataLimitType, dataLimitField + * - limit, page + * - complex_query=1 则调用 complexQuery() + */ + public function list(Request $request): array + { + $p = $request->get(); + + // 复杂查询开关 + if (!empty($p['complex_query']) && method_exists($this, 'complexQuery')) { + return $this->complexQuery($p); + } + + // 1. 构造 where + $where = []; + foreach ($p as $k => $v) { + if (strpos($k, 'filter_') === 0 && $v !== '') { + $col = substr($k, 7); + // 简单等值或 like + $where[$col] = strpos($v, '%') !== false ? ['like', $v] : $v; + } + } + + // 2. 构造其他配置 + $config = [ + 'where' => $where, + 'order' => [($p['sort_by'] ?? 'id') => ($p['order'] ?? 'desc')], + 'fields' => !empty($p['fields']) ? explode(',', $p['fields']) : [], + 'with' => !empty($p['with']) ? explode(',', $p['with']) : [], + 'withFields' => $this->parseWithFields($p['withFields'] ?? ''), + 'dataLimit' => !empty($p['dataLimitType']) ? [ + 'type' => $p['dataLimitType'], + 'field' => $p['dataLimitField'] ?? 'admin_id' + ] : [], + ]; + + $limit = (int)($p['limit'] ?? 15); + $page = (int)($p['page'] ?? 1); + + $pager = $this->logic->list($config, $limit, $page); + return ['total' => $pager->total(), 'data' => $pager->items()]; + } + + /** + * 详情 + */ + public function show($id) + { + return $this->logic->show($id, []); + } + + /** + * 创建 + */ + public function store(Request $request) + { + $data = $request->post(); + $dataLimit = $this->getDataLimitFromRequest($request); + return $this->logic->create($data, $dataLimit); + } + + /** + * 更新 + */ + public function update(Request $request) + { + $key = $this->logic->modelClass::make()->getKeyName(); + $id = (int)$request->post($key); + $data= $request->post(); + $dataLimit = $this->getDataLimitFromRequest($request); + return $this->logic->updateByPk($id, $data, $dataLimit); + } + + /** + * 删除 + */ + public function destroy(Request $request) + { + $key = $this->logic->modelClass::make()->getKeyName(); + $ids = (array)$request->post($key, []); + return $this->logic->deleteByPk($ids); + } + + /** ---------- helpers ---------- */ + + /** + * 解析 withFields 参数 "rel:field,field;rel2:..." + */ + protected function parseWithFields(string $str): array + { + if (!$str) return []; + $out = []; + foreach (explode(';', $str) as $pair) { + [$rel, $flds] = explode(':', $pair); + $out[$rel] = explode(',', $flds); + } + return $out; + } + + /** + * 从请求里读取 dataLimitType/Field + */ + protected function getDataLimitFromRequest(Request $req): array + { + $type = $req->get('dataLimitType'); + if (!$type) return []; + return [ + 'type' => $type, + 'field' => $req->get('dataLimitField', 'admin_id'), + ]; + } + + /** + * 子类可重写,做复杂联表查询等 + */ + protected function complexQuery(array $params): array + { + throw new \LogicException('请在子类实现 complexQuery 方法'); + } +} \ No newline at end of file diff --git a/app/common/traits/DataTransformer.php b/app/common/traits/DataTransformer.php index 42faf81..4f3abac 100644 --- a/app/common/traits/DataTransformer.php +++ b/app/common/traits/DataTransformer.php @@ -1,47 +1,51 @@ callable 字段名格式化回调,如 fn($f)=>snake_case($f) + * - 'postFormatter' => callable 结果级加工回调,接收整个数组并返回 * - * @return array + * @return array 转换并加工后的纯 PHP 数组 */ protected function transformData( mixed $rawData, - ?callable $postProcessor = null, + ?callable $postFormatter = null, ?callable $fieldFormatter = null ): array { try { $fieldFormatter = $fieldFormatter ?: fn($field) => $field; - if ($rawData instanceof LengthAwarePaginator) { - $formatted = $this->formatPaginated($rawData, $postProcessor, $fieldFormatter); + $arr = $this->formatPaginated($rawData, $postFormatter, $fieldFormatter); } elseif ($rawData instanceof Model) { - $formatted = $this->formatModel($rawData, $fieldFormatter); + $arr = $this->formatModel($rawData, $fieldFormatter); } elseif ($rawData instanceof Collection) { - $formatted = $this->formatCollection($rawData, $fieldFormatter); + $arr = $this->formatCollection($rawData, $fieldFormatter); } elseif (is_array($rawData)) { - $formatted = $this->formatArray($rawData, $fieldFormatter); + $arr = $this->formatArray($rawData, $fieldFormatter); } else { - $formatted = (array) $rawData; + $arr = (array)$rawData; } - if (is_callable($postProcessor)) { - $formatted = $postProcessor($formatted); + if (is_callable($postFormatter)) { + $arr = $postFormatter($arr); } - return $formatted; + return $arr; } catch (\Throwable $e) { return [ 'error' => true, @@ -52,84 +56,96 @@ trait DataTransformer } /** - * 格式化分页数据:将每条记录也进行 transformData 递归处理 + * 格式化分页数据:转换每条记录 * - * @param LengthAwarePaginator $paginator - * @param callable|null $postProcessor - * @param callable $fieldFormatter - * @return array + * @param LengthAwarePaginator $p + * @param callable $fmt + * @return array{records: array, total: int, current: int, size: int} */ protected function formatPaginated( - LengthAwarePaginator $paginator, - ?callable $postProcessor, - callable $fieldFormatter + LengthAwarePaginator $p, + ?callable $postFormatter, + callable $fmt ): array { - $list = $paginator->getCollection()->map(function ($item) use ($postProcessor, $fieldFormatter) { - return $this->transformData($item, $postProcessor, $fieldFormatter); - })->toArray(); + $records = $p->getCollection() + ->map(fn($item) => $this->transformData($item, $postFormatter, $fmt)) + ->toArray(); return [ - 'records' => $list, - 'total' => $paginator->total(), - 'current' => $paginator->currentPage(), - 'size' => $paginator->perPage() + 'records' => $records, + 'total' => $p->total(), + 'current' => $p->currentPage(), + 'size' => $p->perPage(), ]; } /** - * 格式化 Eloquent 模型:递归嵌套关联 + * 格式化 Eloquent 模型:主表属性 + 关联递归 * * @param Model $model - * @param callable $fieldFormatter + * @param callable $fmt * @return array */ - protected function formatModel(Model $model, callable $fieldFormatter): array - { - $formatted = []; - - // 主表字段 + protected function formatModel( + Model $model, + callable $fmt + ): array { + $out = []; + // 1) 主表字段 foreach ($model->getAttributes() as $key => $value) { - $formatted[$fieldFormatter($key)] = $value; + $out[$fmt($key)] = $value; } - - // 关联数据,递归嵌套 - foreach ($model->getRelations() as $relation => $relationData) { - if ($relationData instanceof Model) { - // 单个关联模型 - $formatted[$relation] = $this->formatModel($relationData, $fieldFormatter); - } elseif ($relationData instanceof Collection) { - // 关联集合 - $formatted[$relation] = $relationData - ->map(fn($item) => $this->transformData($item, null, $fieldFormatter)) + // 2) 关联递归 + foreach ($model->getRelations() as $rel => $relData) { + if ($relData instanceof Model) { + $out[$rel] = $this->formatModel($relData, $fmt); + } elseif ($relData instanceof Collection) { + $out[$rel] = $relData + ->map(fn($item) => $this->transformData($item, null, $fmt)) ->toArray(); + } else { + $out[$rel] = $relData; } } - - return $formatted; + return $out; } /** - * 格式化集合 + * 格式化集合数据:对每个元素递归调用 transformData * - * @param Collection $collection - * @param callable $fieldFormatter + * @param Collection $c + * @param callable $fmt * @return array */ - protected function formatCollection(Collection $collection, callable $fieldFormatter): array - { - return $collection->map(fn($item) => $this->transformData($item, null, $fieldFormatter)) - ->toArray(); + protected function formatCollection( + Collection $c, + callable $fmt + ): array { + return $c + ->map(fn($item) => $this->transformData($item, null, $fmt)) + ->toArray(); } /** - * 格式化普通数组 + * 格式化普通数组:递归处理可能出现的模型或集合 * - * @param array $array - * @param callable $fieldFormatter + * @param array $arr + * @param callable $fmt * @return array */ - protected function formatArray(array $array, callable $fieldFormatter): array - { - return array_map(fn($item) => $this->transformData($item, null, $fieldFormatter), $array); + protected function formatArray( + array $arr, + callable $fmt + ): array { + return array_map(function($item) use ($fmt) { + if (is_array($item) + || $item instanceof Model + || $item instanceof Collection + || $item instanceof LengthAwarePaginator + ) { + return $this->transformData($item, null, $fmt); + } + return $item; + }, $arr); } } \ No newline at end of file diff --git a/app/common/traits/FormatKvTrait.php b/app/common/traits/FormatKvTrait.php new file mode 100644 index 0000000..c3045a7 --- /dev/null +++ b/app/common/traits/FormatKvTrait.php @@ -0,0 +1,154 @@ +, + * fields: string[], + * include: string[] + * } + */ + protected function buildQueryConfig(Request $request, string $modelClass): array + { + $all = $request->all(); + + return [ + 'model' => $modelClass, + 'where' => $this->parseFilters($all), + 'order' => $this->parseSort($all['sort'] ?? ''), + 'fields' => $this->parseCsv($all['fields'] ?? ''), + 'include' => $this->parseCsv($all['include'] ?? ''), + ]; + } + + /** + * 把除 sort/fields/include/page/limit 以外的所有参数 + * 智能转换为 where 条件 + * + * 支持: + * - 等值:foo=bar + * - NOT:foo=!bar 或 foo=!bar1,bar2 + * - 范围:foo=2025-07-01,2025-07-15 → between + * - 列表:foo=a,b,c → in + * - NULL:foo=null + * - NOT NULL:foo=!null + * - 通配符:foo=xia* → like 'xia%' + * + * 关联字段直接写 relation.field=xxx + */ + protected function parseFilters(array $params): array + { + $where = []; + $reserved = ['sort','fields','include','page','limit']; + + foreach ($params as $key => $val) { + if (in_array($key, $reserved, true)) { + continue; + } + // NULL / NOT NULL + if ($val === 'null') { + $where[$key] = ['is null']; + continue; + } + if ($val === '!null') { + $where[$key] = ['is not null']; + continue; + } + // NOT equal or NOT IN + if (str_starts_with($val, '!')) { + $v = substr($val, 1); + if (str_contains($v, ',')) { + $list = array_map('trim', explode(',', $v)); + $where[$key] = ['not in', $list]; + } else { + $where[$key] = ['<>', $this->cast($v)]; + } + continue; + } + // BETWEEN or IN + if (str_contains($val, ',')) { + $parts = array_map('trim', explode(',', $val)); + if (count($parts) === 2 && $this->isRangeValue($parts[0], $parts[1])) { + $where[$key] = ['between', $parts]; + } else { + $where[$key] = ['in', $this->castArray($parts)]; + } + continue; + } + // LIKE + if (str_contains($val, '*') || str_contains($val, '%')) { + $pattern = str_replace('*', '%', $val); + $where[$key] = ['like', $pattern]; + continue; + } + // 默认 EQ + $where[$key] = $this->cast($val); + } + + return $where; + } + + /** + * 解析 sort=id,-created_at → ['id'=>'asc','created_at'=>'desc'] + */ + protected function parseSort(string $sort): array + { + $order = []; + foreach (explode(',', $sort) as $piece) { + $f = trim($piece); + if ($f === '') { + continue; + } + if ($f[0] === '-') { + $order[substr($f, 1)] = 'desc'; + } else { + $order[$f] = 'asc'; + } + } + return $order; + } + + /** + * 逗号分割成数组 + */ + protected function parseCsv(string $csv): array + { + if ($csv === '') { + return []; + } + return array_filter(array_map('trim', explode(',', $csv))); + } + + /** 简单类型转换:全数字→int/float,其余保留原字符串 */ + protected function cast(string $v): mixed + { + if (is_numeric($v)) { + return strpos($v, '.') !== false ? (float)$v : (int)$v; + } + return $v; + } + + /** 批量类型转换 */ + protected function castArray(array $arr): array + { + return array_map(fn($v) => $this->cast($v), $arr); + } + + /** 判断两端是否可做 BETWEEN(数字或 YYYY-MM-DD 日期) */ + protected function isRangeValue(string $a, string $b): bool + { + return (is_numeric($a) && is_numeric($b)) + || (preg_match('/^\d{4}-\d{2}-\d{2}$/', $a) + && preg_match('/^\d{4}-\d{2}-\d{2}$/', $b)); + } +} diff --git a/app/common/traits/crud/CrudControllerTrait.php b/app/common/traits/crud/CrudControllerTrait.php new file mode 100644 index 0000000..0fd4a0d --- /dev/null +++ b/app/common/traits/crud/CrudControllerTrait.php @@ -0,0 +1,126 @@ +service->serviceList($request); + $data = $this->transformData($raw, method_exists($this, 'indexFormat') + ? [$this, 'indexFormat'] + : null); + return $this->success($data); + } + + /** + * 获取指定资源详情(GET /resource/{id}) + * + * @param Request $request 请求对象 + * @param int|string $id 资源 ID + * @return mixed 返回资源详情数据 + */ + public function show(Request $request) + { + $raw = $this->service->serviceShow($request); + $data = $this->transformData($raw, method_exists($this, 'showFormat') + ? [$this, 'showFormat'] + : null); + return $this->success($data); + } + + /** + * 创建新资源(POST /resource) + * + * @param Request $request 请求对象 + * @return mixed 返回创建成功的数据或信息 + */ + public function store(Request $request) + { + $res = $this->service->serviceStore($request); + return $this->success($res, '创建成功'); + } + + /** + * 更新指定资源(PUT/PATCH /resource/{id}) + * + * @param Request $request 请求对象 + * @return mixed 返回更新成功的数据或信息 + */ + public function update(Request $request) + { + $res = $this->service->serviceUpdate($request); + return $this->success($res, '更新成功'); + } + + /** + * 删除指定资源(DELETE /resource/{id}) + * + * @param Request $request 请求对象 + * @return mixed 返回删除操作结果 + */ + public function destroy(Request $request) + { + $count = $this->service->serviceDestroy($request); + return $this->success([], "删除了 {$count} 条"); + } + + + // ---------------------------------------------------------------- + // 以下三个钩子方法,**在使用此 Trait 的 Controller** 中实现 + // ---------------------------------------------------------------- + + /** + * 字段名格式化:例如全部下划线 + * + * @param string $field + * @return string + */ + // protected function fieldFormatter(string $field): string + // { + // return snake_case($field); + // } + + /** + * 字段级后置加工规则 + * + * @return array + * 键为 “点号嵌套”或 “通配符” 字段路径, + * 值为 fn($value, $row)=>mixed + */ + // protected function getFieldTransforms(): array + // { + // return [ + // 'name' => fn($v,$row)=>strtoupper($v), + // 'orders.*.total' => fn($v,$row)=>number_format($v,2), + // ]; + // } + + /** + * 结果级后置加工:接收整个数组,可任意增删改 + * + * @param array $data + * @return array + */ + // protected function postFormat(array $data): array + // { + // // 例如重命名 records 为 items + // if (isset($data['records'])) { + // $data['items'] = $data['records']; + // unset($data['records']); + // } + // return $data; + // } +} \ No newline at end of file diff --git a/app/common/traits/crud/CrudLogicTrait.php b/app/common/traits/crud/CrudLogicTrait.php new file mode 100644 index 0000000..4e2b794 --- /dev/null +++ b/app/common/traits/crud/CrudLogicTrait.php @@ -0,0 +1,96 @@ +modelClass; + return CrudUtil::paginate(CrudUtil::buildQuery($config), $limit, $page); + } + + /** + * 全量列表 + */ + public function logicListAll(array $config) + { + $config['model'] = $this->modelClass; + return CrudUtil::getAll(CrudUtil::buildQuery($config)); + } + + /** + * 查询单条 + */ + public function logicShow(array $config) + { + return CrudUtil::findOneBy(CrudUtil::buildQuery($config)); + } + + /** + * 任意条件查询单条(可带 dataLimit) + */ + // public function logicShowBy(array $where, array $with = [], array $dataLimit = []) + // { + // return CrudUtil::findOneBy($this->modelClass, $where, $with, $dataLimit); + // } + + /** + * 创建(可带 dataLimit) + */ + public function logicCreate(array $data, array $dataLimit = []) + { + return CrudUtil::create($this->modelClass, $data, $dataLimit); + } + + /** + * 主键更新(可带 dataLimit) + */ + // public function logicUpdateByPk($id, array $data, array $dataLimit = []) + // { + // return CrudUtil::updateByPk($this->modelClass, $id, $data, $dataLimit); + // } + + /** + * 任意条件更新(可带 dataLimit) + */ + public function logicUpdateBy(array $config, array $data) + { + return CrudUtil::updateBy(CrudUtil::buildQuery($config), $data); + } + + /** + * 主键删除(批量) + */ + // public function logicDeleteByPk(array $ids) + // { + // return CrudUtil::deleteByPk($this->modelClass, $ids); + // } + + /** + * 任意条件删除(可带 dataLimit) + */ + public function logicDeleteBy(array $config) + { + return CrudUtil::deleteBy(CrudUtil::buildQuery($config)); + } + + /** + * 复杂查询回调 + */ + public function logicComplex(callable $callback) + { + return CrudUtil::executeComplex($this->modelClass, $callback); + } +} diff --git a/app/common/traits/crud/CrudServiceTrait.php b/app/common/traits/crud/CrudServiceTrait.php new file mode 100644 index 0000000..765042f --- /dev/null +++ b/app/common/traits/crud/CrudServiceTrait.php @@ -0,0 +1,209 @@ +complexQuery($request->get()); + } else { + $config = $this->buildQueryConfig($request, 'index'); + $limit = (int)$request->get('limit', 15); + $page = (int)$request->get('page', 1); + $result = $this->logicList($config, $limit, $page); + } + return $this->postProcess('List', $result); + } + + /** 全量列表 */ + public function serviceListAll(Request $request) + { + if (method_exists($this, 'complexQuery')) { + $result = $this->complexQuery($request->get()); + } else { + $config = $this->buildQueryConfig($request, 'default'); + $result = $this->logicListAll($config); + } + return $this->postProcess('List', $result); + } + + /** 查看单条 */ + public function serviceShow(Request $request) + { + $config = $this->buildQueryConfig($request, 'show'); + $item = $this->logicShow($config); + return $this->postProcess('Show', $item); + } + + /** 创建 */ + public function serviceStore(Request $request) + { + $data = $request->post(); + $dl = $this->getDataLimitFromRequest($request); + $created = $this->logicCreate($data, $dl); + return $this->postProcess('Store', $created); + } + + /** 更新 */ + public function serviceUpdate(Request $request) + { + $data= $request->post(); + $config = $this->buildQueryConfig($request, 'update'); + $updated = $this->logicUpdateBy($config, $data); + return $this->postProcess('Update', $updated); + } + + /** 删除 */ + public function serviceDestroy(Request $request) + { + $config = $this->buildQueryConfig($request, 'destroy'); + $deleted = $this->logicDeleteBy($config); + return $this->postProcess('Destroy', $deleted); + } + + /** ----------------- 公共辅助方法 ----------------- */ + + /** + * 构造 CrudUtil 的查询配置1 + * + * 前端可以通过以下参数控制: + * - filter_= → where + * - sort_by=&order= + * - fields=id,name,email → select + * - with=roles,posts → 关联 + * - withFields=roles:id,name;... → 关联字段 + * - dataLimitType=personal|auth + * - dataLimitField=admin_id + */ + protected function buildQueryConfig(Request $request, string $scene = 'default'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'default': + $config['where']['status'] = 1; + $config['order']['sort'] = ['desc']; + $config['order']['id'] = ['desc']; + break; + case 'index': + $config['where']['status'] = 1; + $config['order']['sort'] = ['desc']; + $config['order']['id'] = ['desc']; + break; + case 'show': + $config['where']['id'] = $p['id']; + $config['where']['status'] = 1; + break; + case 'store': + break; + case 'update': + $config['where']['id'] = $p['id']; + break; + case 'destroy': + $config['where']['id'] = $p['id']; + break; + } + + return $config; + + // // 1. where 从 filter_ 前缀自动映射 + // $where = []; + // foreach ($p as $key => $val) { + // if (strpos($key, 'filter_') === 0 && $val !== '') { + // $col = substr($key, 7); + // // 支持 %like% 查询 + // $where[$col] = strpos($val, '%') !== false + // ? ['like', $val] + // : $val; + // } + // } + + // // 2. 其余配置 + // return [ + // 'model' => $this->modelClass, + // 'where' => $where, + // 'order' => [ + // $p['sort_by'] ?? 'id' => $p['order'] ?? 'desc' + // ], + // 'fields' => !empty($p['fields']) + // ? explode(',', $p['fields']) + // : [], + // 'with' => !empty($p['with']) + // ? explode(',', $p['with']) + // : [], + // 'withFields' => $this->parseWithFields( + // $p['withFields'] ?? '' + // ), + // 'dataLimit' => $this->getDataLimitFromRequest($request), + // ]; + } + + /** 解析 withFields 字符串 "rel:field,field;..." */ + protected function parseWithFields(string $str): array + { + if (!$str) { + return []; + } + $out = []; + foreach (explode(';', $str) as $pair) { + [$rel, $flds] = explode(':', $pair); + $out[$rel] = explode(',', $flds); + } + return $out; + } + + /** 从请求读取 dataLimitType, dataLimitField */ + protected function getDataLimitFromRequest(Request $req): array + { + $type = $req->get('dataLimitType'); + if (!$type) { + return []; + } + return [ + 'type' => $type, + 'field' => $req->get('dataLimitField', 'admin_id'), + ]; + } + + /** + * 处理复杂查询 + * 覆盖此方法,实现复杂查询逻辑 + */ + // protected function complexQuery(array $params): array + // { + // throw new \LogicException('请在子类实现 complexQuery'); + // } + + /** + * 根据操作类型,调用对应的后置钩子 + * + * @param string $op 操作:List, Show, Store, Update, Destroy + * @param mixed $data 原始返回值 + * @return mixed 处理后的返回值 + */ + protected function postProcess(string $op, mixed $data): mixed + { + $hook = "afterService{$op}"; + return method_exists($this, $hook) + ? $this->{$hook}($data) + : $data; + } +} \ No newline at end of file diff --git a/app/common/traits/crud/CrudServiceTrait_bak2.php b/app/common/traits/crud/CrudServiceTrait_bak2.php new file mode 100644 index 0000000..a75b28f --- /dev/null +++ b/app/common/traits/crud/CrudServiceTrait_bak2.php @@ -0,0 +1,270 @@ +get('complex_query') && method_exists($this, 'complexQuery')) { + $data = $this->complexQuery($request->get()); + } else { + $cfg = $this->buildQueryConfig($request, 'list'); + $limit = (int)$request->get('limit', 15); + $page = (int)$request->get('page', 1); + $data = $this->logicList($cfg, $limit, $page); + } + return $this->postProcess('List', $data); + } + + /** 查看单条 */ + public function serviceShow(Request $request, $id) + { + $item = $this->logicShow($id, []); + return $this->postProcess('Show', $item); + } + + /** 创建 */ + public function serviceStore(Request $request) + { + $data = $request->post(); + $dl = $this->getDataLimitFromRequest($request); + $item = $this->logicCreate($data, $dl); + return $this->postProcess('Store', $item); + } + + /** 更新 */ + public function serviceUpdate(Request $request) + { + $key = (new $this->modelClass)->getKeyName(); + $id = (int)$request->post($key); + $data = $request->post(); + $dl = $this->getDataLimitFromRequest($request); + $item = $this->logicUpdateByPk($id, $data, $dl); + return $this->postProcess('Update', $item); + } + + /** 删除 */ + public function serviceDestroy(Request $request) + { + $key = (new $this->modelClass)->getKeyName(); + $ids = (array)$request->post($key, []); + $result = $this->logicDeleteByPk($ids); + return $this->postProcess('Destroy', $result); + } + + // ---------------- 统一后置钩子 ---------------- // + + /** + * 各操作后触发 afterService{Op}() + * @param string $op List|Show|Store|Update|Destroy + */ + protected function postProcess(string $op, mixed $data): mixed + { + $hook = "afterService{$op}"; + return method_exists($this, $hook) + ? $this->{$hook}($data) + : $data; + } + + // ---------------- 复杂查询入口 ---------------- // + + /** + * 子类 override 实现完全定制的复杂查询 + * @param array $params 全部请求参数 + */ + protected function complexQuery(array $params) + { + throw new \LogicException('请在子类实现 complexQuery()'); + } + + // ------------- 辅助:数据权限 & withFields 解析 ------------- // + + /** 从请求读取 dataLimitType & dataLimitField → ['type'=>..,'field'=>..] */ + protected function getDataLimitFromRequest(Request $req): array + { + $type = $req->get('dataLimitType'); + if (!$type) { + return []; + } + return [ + 'type' => $type, + 'field' => $req->get('dataLimitField', 'admin_id'), + ]; + } + + /** 解析 withFields 字符串 "rel:field,field;rel2:..." */ + protected function parseWithFields(string $str): array + { + if (!$str) { + return []; + } + $out = []; + foreach (explode(';', $str) as $pair) { + [$rel, $flds] = explode(':', $pair); + $out[$rel] = explode(',', $flds); + } + return $out; + } + + // ------------- 扁平化 Request → 查询配置 ------------- // + + /** + * 构建完整配置 + * 返回 [ + * where=>[], with=>[], order=>[], withFields=>?, fields=>?, dataLimit=>[] + * ] + */ + protected function buildQueryConfig(Request $request, string $scene = 'list'): array + { + return [ + 'where' => $this->buildFilters($request, $scene), + 'with' => $this->buildRelations($request, $scene), + 'order' => $this->buildOrder($request, $scene), + 'withFields' => $this->buildWithFields($request,$scene), + 'fields' => $this->buildFields($request, $scene), + 'dataLimit' => $this->buildDataLimit($request, $scene), + ]; + } + + /** + * 通用合并方法:取公共配置 + 场景专属配置,若均为 array 则 array_merge + * + * @param mixed $common 公共配置 + * @param Request $request + * @param string $scene + * @param string $type Filters|Relations|Order|WithFields|Fields|DataLimit + */ + private function applySceneConfig( + mixed $common, + Request $request, + string $scene, + string $type + ): mixed { + $method = 'apply' . ucfirst($scene) . $type; + if (! method_exists($this, $method)) { + return $common; + } + $sceneCfg = $this->{$method}($common, $request); + if (is_array($common) && is_array($sceneCfg)) { + return array_merge($common, $sceneCfg); + } + return $sceneCfg; + } + + /** 构建 WHERE:commonFilters + apply{Scene}Filters */ + protected function buildFilters(Request $r, string $scene): array + { + $common = $this->commonFilters($r); + return (array)$this->applySceneConfig($common, $r, $scene, 'Filters'); + } + + /** 构建 with 关联 */ + protected function buildRelations(Request $r, string $scene): array + { + $common = $this->commonRelations(); + return (array)$this->applySceneConfig($common, $r, $scene, 'Relations'); + } + + /** 构建 order 排序 */ + protected function buildOrder(Request $r, string $scene): array + { + $common = $this->commonOrder(); + return (array)$this->applySceneConfig($common, $r, $scene, 'Order'); + } + + /** 构建 withFields(关联模型字段白名单) */ + protected function buildWithFields(Request $r, string $scene): ?array + { + $common = []; + return $this->applySceneConfig($common, $r, $scene, 'WithFields'); + } + + /** 构建 fields(主表字段白名单) */ + protected function buildFields(Request $r, string $scene): ?array + { + $common = []; + return $this->applySceneConfig($common, $r, $scene, 'Fields'); + } + + /** 构建 dataLimit(数据权限) */ + protected function buildDataLimit(Request $r, string $scene): array + { + $common = []; + return (array)$this->applySceneConfig($common, $r, $scene, 'DataLimit'); + } + + + // ------------- 可重写的“公共 + 场景” 方法 ------------- // + + // --- common* 系列(全局默认) --- + protected function commonFilters(Request $r): array { return []; } + protected function commonRelations(): array { return []; } + protected function commonOrder(): array { return []; } + + // // --- list* 系列 --- + // protected function applyListFilters(array $c, Request $r): array { return $this->listFilters($r); } + // protected function listFilters(Request $r): array { return []; } + + // protected function applyListRelations(array $c, Request $r): array { return $this->listRelations(); } + // protected function listRelations(): array { return []; } + + // protected function applyListOrder(array $c, Request $r): array { return $this->listOrder($r); } + // protected function listOrder(Request $r): array { return ['created_at'=>'desc']; } + + // protected function applyListWithFields(array $c, Request $r): array{ return $this->listWithFields($r); } + // protected function listWithFields(Request $r): array { return []; } + + // protected function applyListFields(array $c, Request $r): array { return $this->listFields($r); } + // protected function listFields(Request $r): array { return []; } + + // protected function applyListDataLimit(array $c, Request $r): array { return $this->listDataLimit($r); } + // protected function listDataLimit(Request $r): array { return []; } + + // // --- detail* 系列 --- + // protected function applyDetailFilters(array $c, Request $r): array { return $this->detailFilters($r); } + // protected function detailFilters(Request $r): array { return ['id'=>$r->get('id')]; } + + // protected function applyDetailRelations(array $c, Request $r): array { return $this->detailRelations(); } + // protected function detailRelations(): array { return []; } + + // protected function applyDetailOrder(array $c, Request $r): array { return $this->detailOrder($r); } + // protected function detailOrder(Request $r): array { return []; } + + // protected function applyDetailWithFields(array $c, Request $r): array{ return $this->detailWithFields($r); } + // protected function detailWithFields(Request $r): array { return []; } + + // protected function applyDetailFields(array $c, Request $r): array { return $this->detailFields($r); } + // protected function detailFields(Request $r): array { return []; } + + // protected function applyDetailDataLimit(array $c, Request $r): array { return $this->listDataLimit($r); } + + // // --- export* 系列(如有需要可 override) --- + // protected function applyExportFilters(array $c, Request $r): array { return $this->exportFilters($r); } + // protected function exportFilters(Request $r): array { return []; } +} \ No newline at end of file diff --git a/app/common/utils/CrudUtil.php b/app/common/utils/CrudUtil.php new file mode 100644 index 0000000..58e8302 --- /dev/null +++ b/app/common/utils/CrudUtil.php @@ -0,0 +1,406 @@ + User::class, + * 'where' => [ + * 'status' => 1, + * 'name' => ['like','%张%'], + * 'roles.name' => '管理员', // 关联表字段过滤 + * 'posts.created_at'=> ['>','2025-01-01'], + * ], + * 'order' => ['id'=>'desc'], + * 'fields' => ['id','name','email'], + * 'with' => ['roles','posts'], + * 'withFields' => [ + * 'roles' => ['id','name'], // 关联表字段选择 + * 'posts' => ['id','title','user_id'], + * ], + * ]; + */ + public static function buildQuery(array $config): Builder + { + /** @var Model $model */ + $model = new $config['model']; + $q = $model->newQuery(); + + // 1. eager load + if (!empty($config['with'])) { + $q->with($config['with']); + } + + // 2. 关联表字段选择 + if (!empty($config['withFields'])) { + foreach ($config['withFields'] as $rel => $fields) { + $q->with([$rel => function ($qr) use ($fields) { + $fk = $qr->getModel()->getForeignKey(); + $qr->select(array_unique(array_merge($fields, [$fk]))); + }]); + } + } + + // 3. 本表字段选择 + if (!empty($config['fields'])) { + $q->select($config['fields']); + } + + // 4. where 条件(支持 dot-notation 关联过滤) + if (!empty($config['where'])) { + foreach ($config['where'] as $col => $val) { + if (strpos($col, '.') !== false) { + [$rel, $relCol] = explode('.', $col, 2); + $q->whereHas($rel, function ($qr) use ($relCol, $val) { + self::applyWhere($qr, $relCol, $val); + }); + } else { + self::applyWhere($q, $col, $val); + } + } + } + + // 5. 排序 + if (!empty($config['order'])) { + foreach ($config['order'] as $col => $dir) { + $q->orderBy($col, strtolower($dir) === 'asc' ? 'asc' : 'desc'); + } + } + + // 6. 数据权限过滤 + if (!empty($config['dataLimit'])) { + self::applyDataLimit($q, $config['dataLimit']); + } + + return $q; + } + + /** + * 分页查询 + * + * @param Builder $query + * @param int $perPage + * @param int $page + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public static function paginate(Builder $q, int $perPage, int $page) + { + return $q->paginate($perPage, ['*'], 'page', $page); + } + + /** + * 非分页查询 + * + * @param Builder $query + * @return \Illuminate\Database\Eloquent\Collection + */ + public static function getAll(Builder $q) + { + return $q->get(); + } + + /** + * 执行复杂查询——仅在显式调用时生效 + * + * @param string $modelClass 模型类 + * @param callable $callback function(Builder $q) { ... } + * @return mixed 由 callback 返回的结果 + */ + public static function executeComplex(string $modelClass, callable $cb) + { + /** @var Model $model */ + $model = new $modelClass; + return $cb($model->newQuery()); + } + + /** + * 主键单条查询 + * + * @param string $modelClass + * @param mixed $id + * @param array $with + * @return Model + * @throws BusinessException + */ + // public static function findByPk(string $modelClass, $id, array $with = []): Model + // { + // $m = $modelClass::with($with)->find($id); + // if (!$m) { + // throw new BusinessException("记录 ID={$id} 不存在", 404); + // } + // return $m; + // } + + /** + * 任意条件单条查询,支持 dataLimit + * + * @param string $modelClass + * @param array $where + * @param array $with + * @param array $dataLimit + * @return Model + * @throws BusinessException + */ + // public static function findOneBy( + // string $modelClass, + // array $where, + // array $with = [], + // array $dataLimit = [] + // ): Model { + // $config = compact('modelClass'); + // $config['model'] = $modelClass; + // $config['where'] = $where; + // $config['with'] = $with; + // $config['dataLimit'] = $dataLimit; + + // $m = self::buildQuery($config)->first(); + // if (!$m) { + // throw new BusinessException("未找到符合条件的记录", 404); + // } + // return $m; + // } + public static function findOneBy(Builder $q): Model + { + return $q->first(); + } + + /** + * 创建,支持 dataLimit + * + * @param string $modelClass + * @param array $data + * @param array $dataLimit + * @return Model + * @throws BusinessException + */ + public static function create( + string $modelClass, + array $data, + array $dataLimit = [] + ): Model { + // dataLimit 检查/注入 + if (!empty($dataLimit)) { + self::applyDataLimitOnMutate($data, $dataLimit); + } + return $modelClass::create($data); + } + + /** + * 主键更新,支持 dataLimit + * + * @param string $modelClass + * @param mixed $id + * @param array $data + * @param array $dataLimit + * @return Model + * @throws BusinessException + */ + // public static function updateByPk( + // string $modelClass, + // $id, + // array $data, + // array $dataLimit = [] + // ): Model { + // /** @var Model $m */ + // $m = $modelClass::find($id); + // if (!$m) { + // throw new BusinessException("记录 ID={$id} 不存在", 404); + // } + // // dataLimit 校验 + // if (!empty($dataLimit)) { + // self::applyDataLimitOnMutate($m, $dataLimit, $data); + // } + // $m->fill($data)->save(); + // return $m; + // } + + /** + * 非主键条件更新(批量),支持 dataLimit + * + * @param string $modelClass + * @param array $where + * @param array $data + * @param array $dataLimit + * @return int 受影响行数 + */ + // public static function updateBy( + // string $modelClass, + // array $where, + // array $data, + // array $dataLimit = [] + // ): int { + // $config = ['model'=>$modelClass, 'where'=>$where, 'dataLimit'=>$dataLimit]; + // return self::buildQuery($config)->update($data); + // } + public static function updateBy(Builder $q, array $data): int + { + return $q->update($data); + } + + /** + * 主键删除(支持批量) + * + * @param string $modelClass + * @param array $ids + * @return int 删除条数 + */ + // public static function deleteByPk(string $modelClass, array $ids): int + // { + // return $modelClass::destroy($ids); + // } + + /** + * 非主键条件删除,支持 dataLimit + * + * @param string $modelClass + * @param array $where + * @param array $dataLimit + * @return int 删除条数 + */ + // public static function deleteBy( + // string $modelClass, + // array $where, + // array $dataLimit = [] + // ): int { + // $config = ['model'=>$modelClass, 'where'=>$where, 'dataLimit'=>$dataLimit]; + // return self::buildQuery($config)->delete(); + // } + public static function deleteBy(Builder $q): int + { + return $q->delete(); + } + + + //=================== Internal Methods ===================// + + /** + * 应用单字段 where 过滤 + * + * @param Builder $q + * @param string $col + * @param mixed $val + * @return void + */ + protected static function applyWhere(Builder $q, string $col, $val): void + { + if (is_array($val) && count($val) === 2) { + [$op, $v] = $val; + switch (strtolower($op)) { + case 'like': $q->where($col,'like',$v); break; + case 'in': $q->whereIn($col,(array)$v); break; + case 'not in': $q->whereNotIn($col,(array)$v); break; + case 'null': $q->whereNull($col); break; + case 'not null': $q->whereNotNull($col); break; + case 'between': $q->whereBetween($col,(array)$v); break; + default: $q->where($col,$op,$v); + } + } else { + $q->where($col, $val); + } + } + + /** + * 应用数据权限过滤到查询(buildQuery 场景) + */ + protected static function applyDataLimit(Builder $q, array $dataLimit): void + { + if (Auth::isSuperAdmin()) { + return; + } + $type = $dataLimit['type'] ?? null; + $field = $dataLimit['field'] ?? 'admin_id'; + + if ($type === 'personal') { + $q->where($field, admin_id()); + } elseif ($type === 'auth') { + $q->whereIn($field, Auth::getScopeAdminIds(true)); + } + } + + /** + * 应用数据权限到“创建/更新”操作 + * + * - $target 可能是 array|Model 对象 + * - $newData 仅在 updateByPk 场景时传入,用以校验变更归属 + */ + protected static function applyDataLimitOnMutate(&$target, array $dataLimit, array $newData = []): void + { + if (Auth::isSuperAdmin()) { + return; + } + $type = $dataLimit['type'] ?? null; + $field = $dataLimit['field'] ?? 'admin_id'; + + // 读取当前归属:create 时 $target 是 array,update 时是 Model + $current = is_array($target) ? null : $target->{$field}; + + // 1. personal → 只能自己 + if ($type === 'personal') { + // create: 强制设置 + if (is_array($target)) { + $target[$field] = admin_id(); + } + // update: 校验当前归属 + elseif ($current !== admin_id()) { + throw new BusinessException("无权操作该记录", 403); + } + // 若要修改归属,也必须和自己一致 + if (isset($newData[$field]) && $newData[$field] !== admin_id()) { + throw new BusinessException("无权变更归属字段", 403); + } + } + + // 2. auth → 自己及下属 + if ($type === 'auth') { + $scope = Auth::getScopeAdminIds(true); + // create: 若前端指定归属,则校验范围;否则默认自己 + if (is_array($target)) { + if (isset($target[$field])) { + if (!in_array($target[$field], $scope)) { + throw new BusinessException("无权为指定用户操作", 403); + } + } else { + $target[$field] = admin_id(); + } + } + // update: 校验当前归属 + elseif (!in_array($current, $scope)) { + throw new BusinessException("无权操作该记录", 403); + } + // 校验更新中指定的新归属 + if (isset($newData[$field]) && !in_array($newData[$field], $scope)) { + throw new BusinessException("无权变更归属字段", 403); + } + } + } +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index f9b026d..79d65c7 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -13,6 +13,8 @@ class IndexController { protected $noNeedLogin = ['index', 'view', 'perm']; + + protected $noNeedHeaderData = ['index', 'view', 'perm']; public function index(Request $request) { @@ -58,6 +60,7 @@ class IndexController Cache::delete("user_resources_{$data['user_id']}"); return json(['code' => 0, 'msg' => 'success']); } catch (\Throwable $e) { + var_dump($e); return json(['code' => 1, 'msg' => $e->getMessage()]); } } diff --git a/app/middleware/AuthCheck.php b/app/middleware/AuthCheck.php index c085901..3c504aa 100644 --- a/app/middleware/AuthCheck.php +++ b/app/middleware/AuthCheck.php @@ -6,18 +6,50 @@ use ReflectionClass; use Webman\MiddlewareInterface; use Webman\Http\Response; use Webman\Http\Request; +use app\common\service\BasicDataService; class AuthCheck implements MiddlewareInterface { public function process(Request $request, callable $handler) : Response { $controller = new ReflectionClass($request->controller); - $noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? []; + $properties = $controller->getDefaultProperties(); + $noNeedLogin = $properties['noNeedLogin'] ?? []; + $noNeedAuth = $properties['noNeedAuth'] ?? []; + $noNeedHeaderData = $properties['noNeedHeaderData'] ?? []; - if (in_array($request->action, $noNeedLogin)) { - // 白名单 - return $handler($request); - } else { + // header数据 + if (!in_array($request->action, $noNeedHeaderData)) { //如果需要header数据 + try { + $corpAbbr = $request->header('Fdesk-Corp-Abbr'); + $systemCode = $request->header('Fdesk-System-Code'); + $loginType = $request->header('Fdesk-Login-Type'); + $basicDataService = new BasicDataService(); + if ($basicDataService->checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)) { + $request->login_type = $loginType; + $request->system_code = $systemCode; + $request->corp_abbr = $corpAbbr; + $request->basic_data = [ + 'login_type' => $loginType, + 'system_code' => $systemCode, + 'corp_abbr' => $corpAbbr + ]; + } + return $handler($request); + } catch (\Throwable $th) { + throw $th; + $body = [ + 'status' => 'fail', + 'code' => 401, + 'message' => $th->getMessage(), + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + } + + // 不登陆&不鉴权 + if (!in_array($request->action, $noNeedLogin)) { //如果需要登陆&鉴权 try { $authHeader = $request->header('Fdesk-Auth-Token'); if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { @@ -31,6 +63,7 @@ class AuthCheck implements MiddlewareInterface } $token = $matches[1]; $request->user = (new Jwt())->verifyToken($token); + return $handler($request); } catch (\Throwable $e) { $body = [ 'status' => 'fail', @@ -40,7 +73,24 @@ class AuthCheck implements MiddlewareInterface ]; return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); } - return $handler($request); } + + // 登陆&不鉴权 + // if (!in_array($request->action, $noNeedAuth)) { //如果需要登陆&不鉴权 + // try { + + // return $handler($request); + // } catch (\Throwable $e) { + // $body = [ + // 'status' => 'fail', + // 'code' => 401, + // 'message' => $e->getMessage(), + // 'data' => [], + // ]; + // return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + // } + // } + + return $handler($request); } } \ No newline at end of file diff --git a/app/middleware/PermissionMiddleware.php b/app/middleware/PermissionMiddleware.php deleted file mode 100644 index 9642b52..0000000 --- a/app/middleware/PermissionMiddleware.php +++ /dev/null @@ -1,33 +0,0 @@ -permission_code = $permission_code; - } - - public function process(Request $request, callable $next): Response - { - // 假设有 auth() helper 方法能获取当前登录用户ID - $user_id = $request->session()->get('user_id'); - if (!$user_id) { - return json(['code' => 401, 'msg' => '未登录']); - } - - if ($this->permission_code && !AuthService::check($user_id, $this->permission_code)) { - return json(['code' => 403, 'msg' => '没有权限']); - } - - return $next($request); - } -} diff --git a/config/middleware.php b/config/middleware.php index ae0ad6e..6447d2c 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -16,7 +16,6 @@ return [ '' => [ \app\middleware\AccessControl::class, - \app\middleware\BasicData::class, \app\middleware\AuthCheck::class ] ]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index d970528..e9cc611 100644 --- a/config/route.php +++ b/config/route.php @@ -58,6 +58,13 @@ Route::group('/admin', function () { Route::any('/update', [app\admin\controller\RoleController::class, 'update']); Route::any('/delete', [app\admin\controller\RoleController::class, 'delete']); }); + Route::group('/corp', function () { + Route::any('/list', [app\admin\controller\CorpController::class, 'index']); + Route::any('/show', [app\admin\controller\CorpController::class, 'show']); + // Route::any('/create', [app\admin\controller\CorpController::class, 'store']); + Route::any('/update', [app\admin\controller\CorpController::class, 'update']); + // Route::any('/delete', [app\admin\controller\CorpController::class, 'destroy']); + }); }); From f982dfee95083a70ba88c953fc66863e932ad2d8 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Fri, 18 Jul 2025 17:55:35 +0800 Subject: [PATCH 062/120] =?UTF-8?q?=E6=A0=87=E7=AD=BE=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=AE=A2=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/TagGroupController.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/crm/controller/TagGroupController.php b/app/crm/controller/TagGroupController.php index 3cf24e1..2ac30e8 100644 --- a/app/crm/controller/TagGroupController.php +++ b/app/crm/controller/TagGroupController.php @@ -3,8 +3,10 @@ namespace app\crm\controller; use app\crm\controller\BaseController; +use app\crm\model\Customer; use app\crm\model\CustomerTag; use app\crm\model\TagGroup; +use app\crm\model\Tag; use app\crm\utils\Util; use config\constants\Constants; use support\Request; @@ -15,6 +17,8 @@ class TagGroupController extends BaseController // 列表 public function list(Request $request) { + $uid = $request->user->uid; + $scene = $request->input("scene"); $list = TagGroup::with('tags')->get(); $languageArr = Constants::LANGUAGE; $language = 'zh_CN'; @@ -28,6 +32,10 @@ class TagGroupController extends BaseController if (in_array($language, $languageArr)) { $tag['name'] = $tag['names'][$language]; } + if($scene == 'customers'){ + // 统计每个标签关联的客户数量,增加user_id条件 + $tag['count'] = $tag->customers()->where('user_id', $uid)->count(); + } } } From 81a6b17f809e87b026609b8fac2dd875fa35f2e9 Mon Sep 17 00:00:00 2001 From: "HESU\\HESU" <1696136552@qq.com> Date: Mon, 21 Jul 2025 14:29:58 +0800 Subject: [PATCH 063/120] =?UTF-8?q?=E6=A0=87=E7=AD=BE=E7=AD=9B=E9=80=89?= =?UTF-8?q?=E5=AE=A2=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 51737b1..e411984 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -23,6 +23,7 @@ class CustomerService $recycle = $params['recycle'] ?? ''; $follow = $params['follow'] ?? ''; $follow_last = $params['follow_last'] ?? ''; + $tagId = $params['tag_id'] ?? []; $where = []; $user_id =$params['user_id']; if(empty($user_id) || $user_id == 0){ @@ -58,9 +59,16 @@ class CustomerService $query = Customer::where($where); if(!empty($tagId)){ - $query->whereHas('tags', function($query) use ($tagId){ - $query->where('tag.id', $tagId); - }); + //查询有$tagId 里任意一个标签的客户 + //$query->whereHas('tags', function($query) use ($tagId){ + // $query->whereIn ('tag.id', $tagId); + //}); + //查询有$tagId 里所有标签的客户 + foreach ($tagId as $id) { + $query->whereHas('tags', function($query) use ($id){ + $query->where('tag.id', $id); + }); + } } if (!Util::isEmpty($keyword)) { $query->where(function ($query) use ($keyword) { From 45679bc5e4364c1c9816f419da392083eb05838b Mon Sep 17 00:00:00 2001 From: chenlan <18339827479@163.com> Date: Mon, 21 Jul 2025 15:09:06 +0800 Subject: [PATCH 064/120] =?UTF-8?q?crud=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/RoleController.php | 185 ++++++++++-------- app/admin/service/CorpService.php | 61 ++---- app/admin/service/RoleService.php | 31 +++ app/common/model/Corp.php | 12 +- app/common/service/BasicDataService.php | 2 +- app/common/service/RoleService.php | 55 ------ app/common/traits/RequestTrait.php | 38 ++++ .../traits/crud/CrudControllerTrait.php | 64 ++++-- app/common/traits/crud/CrudServiceTrait.php | 24 ++- app/common/validate/CorpValidate.php | 35 ++++ app/common/validate/RoleValidate.php | 4 +- app/middleware/AuthCheck.php | 56 +----- app/middleware/BasicData.php | 12 +- config/middleware.php | 1 + config/route.php | 16 +- 15 files changed, 318 insertions(+), 278 deletions(-) create mode 100644 app/admin/service/RoleService.php delete mode 100644 app/common/service/RoleService.php create mode 100644 app/common/traits/RequestTrait.php create mode 100644 app/common/validate/CorpValidate.php diff --git a/app/admin/controller/RoleController.php b/app/admin/controller/RoleController.php index 59ccb54..691e070 100644 --- a/app/admin/controller/RoleController.php +++ b/app/admin/controller/RoleController.php @@ -2,97 +2,116 @@ namespace app\admin\controller; -use app\common\validate\RoleValidate; -use app\common\service\RoleService; -use support\Request; +use app\common\controller\BaseController; +use app\admin\service\RoleService; +use app\common\traits\crud\CrudControllerTrait; -class RoleController extends \app\common\controller\BaseController +class RoleController extends BaseController { - /** - * 获取角色列表 - */ - public function list(Request $request) + + use CrudControllerTrait; + + protected $service; + + public function __construct() { - try { - $params = $request->all(); - $result = (new RoleService())->getRoleList($params); - // 定义后处理器 - $postProcessor = function($formattedData) { - if (isset($formattedData['created_at'])) - $formattedData['created_at'] = date('Y-m-d H:i:s', $formattedData['created_at']); - if (isset($formattedData['updated_at'])) - $formattedData['updated_at'] = date('Y-m-d H:i:s', $formattedData['updated_at']); - return $formattedData; - }; - return $this->success($this->transformData($result, $postProcessor), '角色列表获取成功'); - } catch (\Throwable $e) { - return $this->error($e->getMessage()); - } + $this->service = new RoleService(); } - /** - * 获取角色详情 - */ - public function detail(Request $request) + protected function indexFormat($data) { - try { - $roleId = $request->get('role_id'); - if (!$roleId) { - return $this->error('缺少角色ID', 422); - } - $result = (new RoleService())->getRoleDetail($roleId); - return $this->success($result, '角色详情获取成功'); - } catch (\Throwable $e) { - return $this->error($e->getMessage()); - } + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + return $data; } - /** - * 增加角色 - */ - public function create(Request $request) - { - try { - $data = $this->validate(new RoleValidate(), $request->post(), 'role_create'); - $result = (new RoleService())->createRole($data); - return $this->success($result, '角色创建成功'); - } catch (\think\exception\ValidateException $e) { - return $this->error($e->getError(), 422); - } catch (\Throwable $e) { - return $this->error($e->getMessage()); - } - } + // /** + // * 获取角色列表 + // */ + // public function list(Request $request) + // { + // try { + // $params = $request->all(); + // $result = (new RoleService())->getRoleList($params); + // // 定义后处理器 + // $postProcessor = function($formattedData) { + // if (isset($formattedData['created_at'])) + // $formattedData['created_at'] = date('Y-m-d H:i:s', $formattedData['created_at']); + // if (isset($formattedData['updated_at'])) + // $formattedData['updated_at'] = date('Y-m-d H:i:s', $formattedData['updated_at']); + // return $formattedData; + // }; + // return $this->success($this->transformData($result, $postProcessor), '角色列表获取成功'); + // } catch (\Throwable $e) { + // return $this->error($e->getMessage()); + // } + // } - /** - * 修改角色 - */ - public function update(Request $request) - { - try { - $data = $this->validate(new RoleValidate(), $request->post(), 'role_update'); - $result = (new RoleService())->updateRole($data); - return $this->success($result, '角色更新成功'); - } catch (\think\exception\ValidateException $e) { - return $this->error($e->getError(), 422); - } catch (\Throwable $e) { - return $this->error($e->getMessage()); - } - } + // /** + // * 获取角色详情 + // */ + // public function detail(Request $request) + // { + // try { + // $roleId = $request->get('role_id'); + // if (!$roleId) { + // return $this->error('缺少角色ID', 422); + // } + // $result = (new RoleService())->getRoleDetail($roleId); + // return $this->success($result, '角色详情获取成功'); + // } catch (\Throwable $e) { + // return $this->error($e->getMessage()); + // } + // } - /** - * 删除角色 - */ - public function delete(Request $request) - { - try { - $roleId = $request->get('role_id'); - if (!$roleId) { - return $this->error('缺少角色ID', 422); - } - (new RoleService())->deleteRole($roleId); - return $this->success([], '角色删除成功'); - } catch (\Throwable $e) { - return $this->error($e->getMessage()); - } - } + // /** + // * 增加角色 + // */ + // public function create(Request $request) + // { + // try { + // $data = $this->validate(new RoleValidate(), $request->post(), 'role_create'); + // $result = (new RoleService())->createRole($data); + // return $this->success($result, '角色创建成功'); + // } catch (\think\exception\ValidateException $e) { + // return $this->error($e->getError(), 422); + // } catch (\Throwable $e) { + // return $this->error($e->getMessage()); + // } + // } + + // /** + // * 修改角色 + // */ + // public function update(Request $request) + // { + // try { + // $data = $this->validate(new RoleValidate(), $request->post(), 'role_update'); + // $result = (new RoleService())->updateRole($data); + // return $this->success($result, '角色更新成功'); + // } catch (\think\exception\ValidateException $e) { + // return $this->error($e->getError(), 422); + // } catch (\Throwable $e) { + // return $this->error($e->getMessage()); + // } + // } + + // /** + // * 删除角色 + // */ + // public function delete(Request $request) + // { + // try { + // $roleId = $request->get('role_id'); + // if (!$roleId) { + // return $this->error('缺少角色ID', 422); + // } + // (new RoleService())->deleteRole($roleId); + // return $this->success([], '角色删除成功'); + // } catch (\Throwable $e) { + // return $this->error($e->getMessage()); + // } + // } } diff --git a/app/admin/service/CorpService.php b/app/admin/service/CorpService.php index b1cc0bf..5767ea6 100644 --- a/app/admin/service/CorpService.php +++ b/app/admin/service/CorpService.php @@ -2,9 +2,9 @@ namespace app\admin\service; +use app\common\validate\CorpValidate; use app\common\traits\crud\CrudServiceTrait; use app\common\model\Corp; -use support\Request; class CorpService { @@ -15,6 +15,19 @@ class CorpService */ protected string $modelClass = Corp::class; + /** + * 验证器 + */ + protected $validator = CorpValidate::class; + + /** + * 获取验证器 + */ + public function getValidator() + { + return new $this->validator; + } + /** * 如果需要自定义,比如强制加一个 where['status']=1, @@ -27,33 +40,6 @@ class CorpService // return $config; // } - /** - * 如果需要自定义“哪些字段可筛选”或额外联表, - * 只需重写 buildQueryConfig: - */ - // protected function buildQueryConfig(Request $request): array - // { - // $config = parent::buildQueryConfig($request); - // // 强制只看 status=1 的记录 - // $config['where']['status'] = 1; - // // 自动 eager load company relation - // $config['with'][] = 'company'; - // return $config; - // } - - /** - * 复杂查询,覆盖 complexQuery 方法 - */ - // protected function complexQuery(array $params): array - // { - // return $this->logicComplex(function($q) use ($params) { - // // 自定义 join、where、select - // return $q->where('status', 1) - // ->orderBy('created_at','desc') - // ->paginate($params['limit'] ?? 15); - // })->toArray(); - // } - // /** // * 复杂查询示例: // * 只要在 URL 里添加 ?complex_query=1, @@ -100,23 +86,4 @@ class CorpService // $res['data'] = array_map(fn($i)=>[...], $res['data']); // return $res; // } - - // protected function afterServiceShow($item) - // { - // // 对详情对象处理 - // $item['extra']=123; - // return $item; - // } - - // afterServiceStore/Update/Destroy 同理 - - - // protected function afterServiceList(mixed $result): array - // { - // if ($result['records']) { - // $result['records'] = array_map(fn($i) => [...$i], $result['records']); - // } - - // return $result; - // } } \ No newline at end of file diff --git a/app/admin/service/RoleService.php b/app/admin/service/RoleService.php new file mode 100644 index 0000000..248bc3d --- /dev/null +++ b/app/admin/service/RoleService.php @@ -0,0 +1,31 @@ +validator; + } +} \ No newline at end of file diff --git a/app/common/model/Corp.php b/app/common/model/Corp.php index 52c160e..8820c80 100644 --- a/app/common/model/Corp.php +++ b/app/common/model/Corp.php @@ -46,4 +46,14 @@ class Corp extends Model public $timestamps = true; -} + protected $dateFormat = 'U'; + + + protected $fillable = [ + 'name', + 'abbr', + 'tenant_key', + 'sort', + 'status' + ]; +} \ No newline at end of file diff --git a/app/common/service/BasicDataService.php b/app/common/service/BasicDataService.php index e77a4e0..d5ff0ce 100644 --- a/app/common/service/BasicDataService.php +++ b/app/common/service/BasicDataService.php @@ -20,7 +20,7 @@ class BasicDataService public function checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr) { if (!$loginType && !$systemCode && !$corpAbbr) { - throw new BusinessException(htmlpage()); + throw new BusinessException('header缺少必要参数'); } // 检查并缓存登录类型 diff --git a/app/common/service/RoleService.php b/app/common/service/RoleService.php deleted file mode 100644 index 24c14cf..0000000 --- a/app/common/service/RoleService.php +++ /dev/null @@ -1,55 +0,0 @@ -roleLogic = (new RoleLogic()); - } - - /** - * 获取角色列表 - */ - public function getRoleList(array $params) - { - return $this->roleLogic->getRoleList($params); - } - - /** - * 获取角色详情 - */ - public function getRoleDetail(int $roleId) - { - return $this->roleLogic->getRoleDetail($roleId); - } - - /** - * 创建角色 - */ - public function createRole(array $data) - { - return $this->roleLogic->createRole($data); - } - - /** - * 更新角色 - */ - public function updateRole(array $data) - { - return $this->roleLogic->updateRole($data); - } - - /** - * 删除角色 - */ - public function deleteRole(int $roleId) - { - return $this->roleLogic->deleteRole($roleId); - } -} diff --git a/app/common/traits/RequestTrait.php b/app/common/traits/RequestTrait.php new file mode 100644 index 0000000..8fefcc4 --- /dev/null +++ b/app/common/traits/RequestTrait.php @@ -0,0 +1,38 @@ +scene($scene); + } + // 执行数据验证 + if (!$validator->check($data)) { + // 验证失败,抛出业务异常,异常信息为验证器返回的错误信息 + throw new BusinessException($validator->getError()); + } + // 验证成功,返回原始数据 + return $data; + } + + +} \ No newline at end of file diff --git a/app/common/traits/crud/CrudControllerTrait.php b/app/common/traits/crud/CrudControllerTrait.php index 0fd4a0d..c5cdfdc 100644 --- a/app/common/traits/crud/CrudControllerTrait.php +++ b/app/common/traits/crud/CrudControllerTrait.php @@ -11,22 +11,26 @@ use support\Request; trait CrudControllerTrait { /** - * 获取资源列表(GET /resource) + * 获取列表 * * @param Request $request 请求对象 * @return mixed 返回分页数据及总数 */ public function index(Request $request) { - $raw = $this->service->serviceList($request); - $data = $this->transformData($raw, method_exists($this, 'indexFormat') - ? [$this, 'indexFormat'] - : null); - return $this->success($data); + try { + $raw = $this->service->serviceList($request); + $data = $this->transformData($raw, method_exists($this, 'indexFormat') + ? [$this, 'indexFormat'] + : null); + return $this->success($data); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } } /** - * 获取指定资源详情(GET /resource/{id}) + * 获取详情 * * @param Request $request 请求对象 * @param int|string $id 资源 ID @@ -34,47 +38,65 @@ trait CrudControllerTrait */ public function show(Request $request) { - $raw = $this->service->serviceShow($request); - $data = $this->transformData($raw, method_exists($this, 'showFormat') - ? [$this, 'showFormat'] - : null); - return $this->success($data); + try { + $raw = $this->service->serviceShow($request); + $data = $this->transformData($raw, method_exists($this, 'showFormat') + ? [$this, 'showFormat'] + : null); + return $this->success($data); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } } /** - * 创建新资源(POST /resource) + * 创建 * * @param Request $request 请求对象 * @return mixed 返回创建成功的数据或信息 */ public function store(Request $request) { - $res = $this->service->serviceStore($request); - return $this->success($res, '创建成功'); + try { + $data = $this->validate($this->service->getValidator(), $request->post(), 'create'); + $res = $this->service->serviceStore($request, $data); + return $this->success($res, '创建成功'); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } } /** - * 更新指定资源(PUT/PATCH /resource/{id}) + * 更新 * * @param Request $request 请求对象 * @return mixed 返回更新成功的数据或信息 */ public function update(Request $request) { - $res = $this->service->serviceUpdate($request); - return $this->success($res, '更新成功'); + try { + $data = $this->validate($this->service->getValidator(), $request->post(), 'update'); + $res = $this->service->serviceUpdate($request, $data); + return $this->success([], '更新成功'); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } } /** - * 删除指定资源(DELETE /resource/{id}) + * 删除 * * @param Request $request 请求对象 * @return mixed 返回删除操作结果 */ public function destroy(Request $request) { - $count = $this->service->serviceDestroy($request); - return $this->success([], "删除了 {$count} 条"); + try { + $count = $this->service->serviceDestroy($request); + return $this->success([], "成功删除了 {$count} 条"); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } } diff --git a/app/common/traits/crud/CrudServiceTrait.php b/app/common/traits/crud/CrudServiceTrait.php index 765042f..c042b5f 100644 --- a/app/common/traits/crud/CrudServiceTrait.php +++ b/app/common/traits/crud/CrudServiceTrait.php @@ -17,6 +17,14 @@ trait CrudServiceTrait /** 具体 Service 里定义:protected string $modelClass = YourModel::class; */ + /** + * 获取验证器 + */ + public function getValidator() + { + return $this->validator; + } + /** 分页列表 */ public function serviceList(Request $request) { @@ -52,18 +60,18 @@ trait CrudServiceTrait } /** 创建 */ - public function serviceStore(Request $request) + public function serviceStore(Request $request, array $data = []) { - $data = $request->post(); + // $data = $request->post(); $dl = $this->getDataLimitFromRequest($request); $created = $this->logicCreate($data, $dl); return $this->postProcess('Store', $created); } /** 更新 */ - public function serviceUpdate(Request $request) + public function serviceUpdate(Request $request, array $data = []) { - $data= $request->post(); + // $data= $request->post(); $config = $this->buildQueryConfig($request, 'update'); $updated = $this->logicUpdateBy($config, $data); return $this->postProcess('Update', $updated); @@ -100,13 +108,13 @@ trait CrudServiceTrait switch ($scene) { case 'default': $config['where']['status'] = 1; - $config['order']['sort'] = ['desc']; - $config['order']['id'] = ['desc']; + $config['order']['sort'] = 'desc'; + $config['order']['id'] = 'desc'; break; case 'index': $config['where']['status'] = 1; - $config['order']['sort'] = ['desc']; - $config['order']['id'] = ['desc']; + $config['order']['sort'] = 'desc'; + $config['order']['id'] = 'desc'; break; case 'show': $config['where']['id'] = $p['id']; diff --git a/app/common/validate/CorpValidate.php b/app/common/validate/CorpValidate.php new file mode 100644 index 0000000..5889ef9 --- /dev/null +++ b/app/common/validate/CorpValidate.php @@ -0,0 +1,35 @@ + 'require|number', + 'name' => 'require|max:255', + 'abbr' => 'require|in:hesu,xinyouhe,paika,simate,mbaf', + 'tenant_key' => 'string', + 'sort' => 'number', + 'status' => 'require|in:0,1' + ]; + + protected $message = [ + 'id.require' => 'ID不能为空', + 'id.number' => 'ID必须是数字', + 'name.require' => '企业名称不能为空', + 'name.max' => '企业名称长度不能超过255个字符', + 'abbr.require' => '企业缩写不能为空', + 'abbr.in' => '企业缩写必须是 hesu,xinyouhe,paika,simate,mbaf 中的一个', + 'tenant_key.string' => '飞书企业标识必须是字符串', + 'sort.number' => '排序值必须是数字', + 'status.require' => '状态不能为空', + 'status.in' => '状态必须是0或1' + ]; + + protected $scene = [ + 'create' => ['name', 'abbr', 'tenant_key', 'sort', 'status'], + 'update' => ['id', 'name', 'abbr', 'tenant_key', 'sort', 'status'] + ]; +} \ No newline at end of file diff --git a/app/common/validate/RoleValidate.php b/app/common/validate/RoleValidate.php index 330fae6..afd69cc 100644 --- a/app/common/validate/RoleValidate.php +++ b/app/common/validate/RoleValidate.php @@ -26,7 +26,7 @@ class RoleValidate extends Validate ]; protected $scene = [ - 'role_create' => ['system_id', 'name', 'desc', 'sort'], - 'role_update' => ['id', 'system_id', 'name', 'desc', 'sort', 'status'], + 'create' => ['system_id', 'name', 'desc', 'sort'], + 'update' => ['id', 'system_id', 'name', 'desc', 'sort', 'status'], ]; } diff --git a/app/middleware/AuthCheck.php b/app/middleware/AuthCheck.php index 3c504aa..a03e632 100644 --- a/app/middleware/AuthCheck.php +++ b/app/middleware/AuthCheck.php @@ -6,7 +6,6 @@ use ReflectionClass; use Webman\MiddlewareInterface; use Webman\Http\Response; use Webman\Http\Request; -use app\common\service\BasicDataService; class AuthCheck implements MiddlewareInterface { @@ -16,40 +15,11 @@ class AuthCheck implements MiddlewareInterface $properties = $controller->getDefaultProperties(); $noNeedLogin = $properties['noNeedLogin'] ?? []; $noNeedAuth = $properties['noNeedAuth'] ?? []; - $noNeedHeaderData = $properties['noNeedHeaderData'] ?? []; - - // header数据 - if (!in_array($request->action, $noNeedHeaderData)) { //如果需要header数据 - try { - $corpAbbr = $request->header('Fdesk-Corp-Abbr'); - $systemCode = $request->header('Fdesk-System-Code'); - $loginType = $request->header('Fdesk-Login-Type'); - $basicDataService = new BasicDataService(); - if ($basicDataService->checkAndCacheBaseParams($loginType, $systemCode, $corpAbbr)) { - $request->login_type = $loginType; - $request->system_code = $systemCode; - $request->corp_abbr = $corpAbbr; - $request->basic_data = [ - 'login_type' => $loginType, - 'system_code' => $systemCode, - 'corp_abbr' => $corpAbbr - ]; - } - return $handler($request); - } catch (\Throwable $th) { - throw $th; - $body = [ - 'status' => 'fail', - 'code' => 401, - 'message' => $th->getMessage(), - 'data' => [], - ]; - return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); - } - } - - // 不登陆&不鉴权 - if (!in_array($request->action, $noNeedLogin)) { //如果需要登陆&鉴权 + + if (in_array($request->action, $noNeedLogin)) { + // 白名单 + return $handler($request); + } else { try { $authHeader = $request->header('Fdesk-Auth-Token'); if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { @@ -75,22 +45,6 @@ class AuthCheck implements MiddlewareInterface } } - // 登陆&不鉴权 - // if (!in_array($request->action, $noNeedAuth)) { //如果需要登陆&不鉴权 - // try { - - // return $handler($request); - // } catch (\Throwable $e) { - // $body = [ - // 'status' => 'fail', - // 'code' => 401, - // 'message' => $e->getMessage(), - // 'data' => [], - // ]; - // return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); - // } - // } - return $handler($request); } } \ No newline at end of file diff --git a/app/middleware/BasicData.php b/app/middleware/BasicData.php index 472456e..9df57e7 100644 --- a/app/middleware/BasicData.php +++ b/app/middleware/BasicData.php @@ -12,9 +12,9 @@ class BasicData implements MiddlewareInterface public function process(Request $request, callable $handler): Response { $controller = new ReflectionClass($request->controller); - $noNeedHeader = $controller->getDefaultProperties()['noNeedHeader'] ?? []; + $noNeedHeaderData = $controller->getDefaultProperties()['noNeedHeaderData'] ?? []; - if (in_array($request->action, $noNeedHeader) || in_array($request->path(), config('whitelist.basi_cdata'))) { + if (in_array($request->action, $noNeedHeaderData)) { // 白名单 return $handler($request); } else { @@ -35,7 +35,13 @@ class BasicData implements MiddlewareInterface } return $handler($request); } catch (\Throwable $th) { - throw $th; + $body = [ + 'status' => 'fail', + 'code' => 403, + 'message' => $th->getMessage(), + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); } } } diff --git a/config/middleware.php b/config/middleware.php index 6447d2c..ae0ad6e 100644 --- a/config/middleware.php +++ b/config/middleware.php @@ -16,6 +16,7 @@ return [ '' => [ \app\middleware\AccessControl::class, + \app\middleware\BasicData::class, \app\middleware\AuthCheck::class ] ]; \ No newline at end of file diff --git a/config/route.php b/config/route.php index e9cc611..700fb4b 100644 --- a/config/route.php +++ b/config/route.php @@ -28,6 +28,7 @@ use Webman\Route; // 调试 Route::any('/perm', [app\controller\IndexController::class, 'perm']); +// 公共 Route::group('/common', function () { Route::group('/auth', function () { Route::any('/login', [app\common\controller\AuthController::class, 'login']); @@ -50,20 +51,23 @@ Route::group('/common', function () { }); }); +// 后台管理 Route::group('/admin', function () { + // 角色 Route::group('/role', function () { - Route::any('/list', [app\admin\controller\RoleController::class, 'list']); - Route::any('/detail', [app\admin\controller\RoleController::class, 'detail']); - Route::any('/create', [app\admin\controller\RoleController::class, 'create']); + Route::any('/list', [app\admin\controller\RoleController::class, 'index']); + Route::any('/show', [app\admin\controller\RoleController::class, 'show']); + Route::any('/store', [app\admin\controller\RoleController::class, 'store']); Route::any('/update', [app\admin\controller\RoleController::class, 'update']); - Route::any('/delete', [app\admin\controller\RoleController::class, 'delete']); + Route::any('/destroy', [app\admin\controller\RoleController::class, 'destroy']); }); + // 公司 Route::group('/corp', function () { Route::any('/list', [app\admin\controller\CorpController::class, 'index']); Route::any('/show', [app\admin\controller\CorpController::class, 'show']); - // Route::any('/create', [app\admin\controller\CorpController::class, 'store']); + Route::any('/store', [app\admin\controller\CorpController::class, 'store']); Route::any('/update', [app\admin\controller\CorpController::class, 'update']); - // Route::any('/delete', [app\admin\controller\CorpController::class, 'destroy']); + Route::any('/destroy', [app\admin\controller\CorpController::class, 'destroy']); }); }); From 149de9d6fb1ed6429a46b6d121cf1ce29e6a07ce Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 21 Jul 2025 15:10:14 +0800 Subject: [PATCH 065/120] Merge branch 'develop' into feature/mzl_dev_curd --- app/common/controller/UserController.php | 53 +++ app/common/logic/PermLogic.php | 2 +- app/common/logic/UserLogic.php | 29 ++ app/common/model/Role.php | 2 +- app/common/model/User.php | 7 +- app/common/service/AuthService.php | 11 +- app/common/service/UserService.php | 62 +++ app/controller/IndexController.php | 10 + app/crm/controller/BaseController.php | 51 +++ app/crm/controller/CarTypeController.php | 34 ++ app/crm/controller/CustomerController.php | 527 ++++++++++++++++++++++ app/crm/controller/FollowController.php | 141 ++++++ app/crm/controller/TagGroupController.php | 80 ++++ app/crm/logic/CustomerLogic.php | 15 + app/crm/logic/FollowLogic.php | 8 + app/crm/model/CarType.php | 36 ++ app/crm/model/Channel.php | 48 ++ app/crm/model/Country.php | 41 ++ app/crm/model/Customer.php | 82 ++++ app/crm/model/CustomerHis.php | 68 +++ app/crm/model/CustomerTag.php | 36 ++ app/crm/model/Follow.php | 50 ++ app/crm/model/Tag.php | 55 +++ app/crm/model/TagGroup.php | 50 ++ app/crm/service/CustomerService.php | 163 +++++++ app/crm/service/FollowService.php | 65 +++ app/crm/utils/Util.php | 121 +++++ app/crm/validate/FollowValidate.php | 21 + app/ticket/service/CategorieService.php | 2 +- config/constants/Constants.php | 100 ++++ config/route.php | 34 +- resource/translations/ar/messages.php | 31 ++ resource/translations/en/messages.php | 31 ++ resource/translations/ru/messages.php | 31 ++ resource/translations/zh/messages.php | 31 ++ 35 files changed, 2112 insertions(+), 16 deletions(-) create mode 100644 app/crm/controller/BaseController.php create mode 100644 app/crm/controller/CarTypeController.php create mode 100644 app/crm/controller/CustomerController.php create mode 100644 app/crm/controller/FollowController.php create mode 100644 app/crm/controller/TagGroupController.php create mode 100644 app/crm/logic/CustomerLogic.php create mode 100644 app/crm/logic/FollowLogic.php create mode 100644 app/crm/model/CarType.php create mode 100644 app/crm/model/Channel.php create mode 100644 app/crm/model/Country.php create mode 100644 app/crm/model/Customer.php create mode 100644 app/crm/model/CustomerHis.php create mode 100644 app/crm/model/CustomerTag.php create mode 100644 app/crm/model/Follow.php create mode 100644 app/crm/model/Tag.php create mode 100644 app/crm/model/TagGroup.php create mode 100644 app/crm/service/CustomerService.php create mode 100644 app/crm/service/FollowService.php create mode 100644 app/crm/utils/Util.php create mode 100644 app/crm/validate/FollowValidate.php diff --git a/app/common/controller/UserController.php b/app/common/controller/UserController.php index efab4a1..2a22145 100644 --- a/app/common/controller/UserController.php +++ b/app/common/controller/UserController.php @@ -22,4 +22,57 @@ class UserController extends BaseController } } + /** + * 用户信息 + */ + public function getUser(Request $request) + { + try { + $uid = $request->user->uid; + $corp_abbr = $request->basic_data['corp_abbr']; + $result = (new UserService())->getUser($uid,$corp_abbr); + return $this->success($result, '获取用户信息'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 编辑用户 + */ + public function edit(Request $request) + { + try { + $uid = $request->user->uid; + $data = $request->all(); + $result = (new UserService())->edit($uid,$data); + unset($result['password']); + unset($result['union_id']); + unset($result['open_id']); + unset($result['tenant_key']); + return $this->success($result, '编辑成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 修改密码 + * @param Request $request + * @return \support\Response + * @throws \Random\RandomException + */ + public function savePassword(Request $request) + { + try { + $uid = $request->user->uid; + $data = $request->all(); + $result = (new UserService())->savePassword($uid,$data);; + return $this->success($result, '编辑成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + } \ No newline at end of file diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index b30e6f8..674fbc8 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -102,7 +102,7 @@ class PermLogic // component 字段规则: // 1. 存在子菜单,固定 '/index/index' // 2. 否则为当前完整路径(或其它你想要的) - $componentValue = $hasChildrenMenu ? '/index/index' : $currentPath; + $componentValue = ($hasChildrenMenu && !$node->url && $node->type !== 'directory') ? '/index/index' : $currentPath; $tree[] = [ 'id' => $node->id, diff --git a/app/common/logic/UserLogic.php b/app/common/logic/UserLogic.php index 1d6dc93..4b58247 100644 --- a/app/common/logic/UserLogic.php +++ b/app/common/logic/UserLogic.php @@ -24,4 +24,33 @@ class UserLogic return $user; } + + /** + * 用户信息 + */ + public function edit(int $uid, array $data) + { + $user = User::find($uid); + + if (!$user) { + throw new BusinessException('账户不存在', 404); + } + + $user->fill($data); + $user->save(); + + return $user; + } + + /** + * 修改密码 + */ + public function savePassword(int $uid, array $data) + { + $password = password_hash($data['password_new'], PASSWORD_DEFAULT); + $res = User::where('id',$uid)->update(['password' => $password]); + + return $res; + } + } \ No newline at end of file diff --git a/app/common/model/Role.php b/app/common/model/Role.php index c9eaabd..ea84200 100644 --- a/app/common/model/Role.php +++ b/app/common/model/Role.php @@ -53,6 +53,6 @@ class Role extends Model // 角色拥有的资源(权限点、菜单) public function resources() { - return $this->belongsToMany(Resource::class, 'role_resources', 'role_id', 'resource_id')->orderBy('sort', 'asc'); + return $this->belongsToMany(Resource::class, 'role_resources', 'role_id', 'resource_id'); } } diff --git a/app/common/model/User.php b/app/common/model/User.php index e9b4627..5d1b8f0 100644 --- a/app/common/model/User.php +++ b/app/common/model/User.php @@ -54,6 +54,7 @@ class User extends Model 'sn', 'name', 'avatar', + 'sex', 'password', 'phone', 'email', @@ -63,7 +64,11 @@ class User extends Model 'union_id', 'user_id', 'open_id', - 'tenant_key' + 'tenant_key', + 'country_id', + 'position', + 'socialize', + 'socialize_pic', ]; // 用户所有角色 diff --git a/app/common/service/AuthService.php b/app/common/service/AuthService.php index 6de72a2..aa8f4eb 100644 --- a/app/common/service/AuthService.php +++ b/app/common/service/AuthService.php @@ -31,7 +31,7 @@ class AuthService return match ($loginType) { 'feishu' => $logic->feishuLogin($data), - // 'qrcode' => $logic->qrCodeLogin($data), + 'qrcode' => $logic->qrCodeLogin($data), default => $logic->uPwdLogin($data), }; } @@ -86,13 +86,4 @@ class AuthService { (new AuthLogic())->blockToken($token, $systemCode); } - - - // public function perms() - // { - // $permService = new PermService(); - // $menuTree = $permService->getMenuTree($uid); - // $permissionCodes = $permService->getPermissionCodes($uid); - // return (new PermService())->getMenuTree($this->userId) - // } } \ No newline at end of file diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 98375bc..85684d3 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -3,6 +3,10 @@ namespace app\common\service; use app\common\logic\UserLogic; +use app\common\model\Corp; +use app\common\model\User; +use app\crm\model\Country; +use support\exception\BusinessException; class UserService { @@ -37,4 +41,62 @@ class UserService 'permissions' => $permissionCodes ]; } + + + /** + * 获取用户信息(包括菜单和权限) + */ + public function getUser(int $uid, $corp_abbr) + { + $user = $this->logic->userInfo($uid); + $corp_name = Corp::where('abbr', $corp_abbr)->value('name'); + $ossservice = new OssService(); + return [ + 'id' => $user->id, + 'name' => $user->name, + 'position' => $user->position, + 'socialize' => $user->socialize, + 'socialize_pic' => $user->socialize_pic, + 'socialize_pic_full' => (!empty($user->socialize_pic))?$ossservice->generatePrivateUrl($user->socialize_pic):'', + 'country_id' => $user->country_id, + 'country_txt' => Country::where('id',$user->country_id)->value('cn_addr'), + 'avatar' => $user->avatar, + 'avatar_full' => (!empty($user->avatar))?$ossservice->generatePrivateUrl($user->avatar):'', + 'phone' => $user->phone, + 'corp_name' => $corp_name, + 'email' => $user->email, + 'status' => $user->status, + 'roles' => $user->roles->pluck('name') + ]; + } + + /** + * 编辑用户信息 + */ + public function edit(int $uid, array $data) + { + unset($data['password']); + unset($data['phone']); + unset($data['email']); + unset($data['sn']); + + $user = $this->logic->edit($uid,$data); + return $user; + } + + /** + * 修改用户密码 + */ + public function savePassword(int $uid, array $data) + { + // 对密码进行哈希处理 + $password = $data['password']; + $user = User::find($uid); + if (!password_verify($password, $user->password)) { + throw new BusinessException('密码错误'); + } + + $user = $this->logic->savePassword($uid,$data); + return $user; + } } \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 79d65c7..866d3d5 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -64,4 +64,14 @@ class IndexController return json(['code' => 1, 'msg' => $e->getMessage()]); } } + + + // test function + public function test(Request $request) + { + $user = User::find(1); + $user->roles()->sync([1]); + $user->systems()->sync([1]); + return json(['code' => 0, 'msg' => 'success']); + } } \ No newline at end of file diff --git a/app/crm/controller/BaseController.php b/app/crm/controller/BaseController.php new file mode 100644 index 0000000..1b824e0 --- /dev/null +++ b/app/crm/controller/BaseController.php @@ -0,0 +1,51 @@ +scene($scene); + } + // 执行数据验证 + if (!$validator->check($data)) { + // 验证失败,抛出业务异常,异常信息为验证器返回的错误信息 + throw new BusinessException($validator->getError()); + } + // 验证成功,返回原始数据 + return $data; + } + + public function page($result){ + $current = $result->currentPage(); + $total = $result->total(); + $size = $result->perPage(); + $result = $result->toArray(); + $result['current'] = $current; + $result['total'] = $total; + $result['size'] = $size; + return $result; + } +} \ No newline at end of file diff --git a/app/crm/controller/CarTypeController.php b/app/crm/controller/CarTypeController.php new file mode 100644 index 0000000..6156876 --- /dev/null +++ b/app/crm/controller/CarTypeController.php @@ -0,0 +1,34 @@ +user['language'] != 'zh_CN'){ + foreach ($list as &$value) { + $value['name'] = $value['name_en']; + } + }elseif ($request->user['language'] == 'ru'){ + foreach ($list as &$value) { + $value['name'] = $value['name_ru']; + } + } + + return $this->success($list); + } + +} \ No newline at end of file diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php new file mode 100644 index 0000000..8eee3ac --- /dev/null +++ b/app/crm/controller/CustomerController.php @@ -0,0 +1,527 @@ + $value) { + $grade[] = [ + 'id' => $key, + 'name' => $value, + ]; + } + return $this->success($grade); + } + + /** + * 商机 + * @param Request $request + * @return \support\Response + */ + public function getMagnitude(Request $request) + { + $grade = []; + $magnitudeConstants = Constants::MAGNITUDE_EN; + if('zh_CN' == 'zh_CN'){ + $magnitudeConstants = Constants::MAGNITUDE; + }elseif ('zh_CN' == 'ru'){ + $magnitudeConstants = Constants::MAGNITUDE_RU; + } + foreach ($magnitudeConstants as $key => $value) { + $grade[] = [ + 'id' => $key, + 'name' => $value, + ]; + } + return $this->success($grade); + } + + /** + * 客户渠道 + * @param Request $request + * @return \support\Response + */ + public function getChannel(Request $request) + { + $list = Channel::getAll(); + + if('zh_CN' != 'zh_CN'){ + foreach ($list as &$value) { + $value['name'] = $value['name_en']; + } + }elseif ('zh_CN' == 'ru'){ + foreach ($list as &$value) { + $value['name'] = $value['name_ru']; + } + } + + return $this->success($list); + + } + + /** + * 国家 + * @param Request $request + * @return \support\Response + */ + public function getCountry(Request $request) + { + $list = Country::getAll(); + + if('zh_CN' != 'zh_CN'){ + foreach ($list as &$value) { + $value['name'] = $value['name_en']; + } + }elseif ('zh_CN' == 'ru'){ + foreach ($list as &$value) { + $value['name'] = $value['name_ru']; + } + } + + return $this->success($list); + + } + + /** + * 待跟进客户列表 + * @param Request $request + * @return \support\Response + */ + public function waitFollow(Request $request) + { + $userId = $request->user->uid; + $filter = Util::getParam($request, 'filter'); + $page = $request->input('page', 1); + $perPage = $request->input('per_page', 10); + $where = [ + ['user_id', '=', $userId], + ['corp_abbr', '=', $request->basic_data['corp_abbr']], + ['follow_next', '<>', 0], + ]; + + if($filter == 0){ + $where[] = ['follow_next', '<=', time() + 3600 * 24]; + } elseif ($filter == 1) { + $where[] = ['follow_next', '<=', time() + 3600 * 24 * 3]; + } elseif ($filter == 2) { + $where[] = ['follow_next', '<=', time() + 3600 * 24 * 7]; + } + $customer = Customer::where($where) + ->whereNotNull('follow_next') + ->orderBy('follow_next') + ->paginate($perPage, ['*'], 'page', $page); + + foreach ($customer as &$item) { + $item['follow_remaining'] = $item->follow_next-time(); + $item['follow_next'] = date('Y-m-d H:i:s',$item->follow_next); + } + return $this->success($this->page($customer)); + } + + /** + * 客户列表 + * @param Request $request + * @return \support\Response + */ + public function list(Request $request) + { + try { + $param = $request->all(); + $param['user_id'] = $request->user->uid; + $param['corp_abbr'] = $request->basic_data['corp_abbr']; + $result = (new CustomerService())->list($param); + + return $this->success($this->page($result)); + } catch (\Throwable $e) { + return $this->error($e->getMessage().$e->getFile().$e->getLine()); + } + } + + /** + * 客户恢复 + * @param Request $request + * @return \support\Response + */ + public function restore(Request $request) + { + $id = Util::getParam($request,'id', ''); + if (Util::isEmpty($id)) { + return $this->error(trans('id_empty')); + } + $userId = $request->user->uid; + $where = [ + 'id' => $id, + 'user_id' => $userId, + ]; + $res = Customer::where($where)->restore(); + if($res){ + return $this->success($res, trans('success')); + }else{ + return $this->error(trans('fail')); + } + } + + + /** + * 客户详情 + * @param Request $request + * @return \support\Response + */ + public function detail(Request $request) + { + $id = Util::getParam($request, 'id'); + if (Util::isEmpty($id)) { + return $this->error('id' . trans('not_null')); + } + +// $res = Customer::with('tags')->select('id', 'user_id', 'grade', 'channel_id', 'car_type_id', 'country_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'address', 'lat', 'lng', 'follow_next', 'follow_remark', 'sex') +// ->where('id', $id) +// ->first(); + $res = (new CustomerService)->detail($request->all()); + if ($res) { + $res->toArray(); + } else { + return $this->error('记录不存在'); + } + + $res = CustomerService::getCustomer($res, 'zh_CN', true); + + return $this->success($res); + } + + /** + * 客户录入 + * @param Request $request + * @return \support\Response + */ + public function save(Request $request) + { + $id = Util::getParam($request, 'id'); + $channel_id = Util::getParam($request, 'channel_id'); + if (Util::isEmpty($channel_id)) { + return $this->error(trans('channel_not_null')); + } + $country_id = Util::getParam($request, 'country_id', 0); + if (Util::isEmpty($country_id)) { +// return $this->error('country_id'.trans('not_null')); + } +// $car_type_id = Util::getParam($request, 'car_type_id'); +// if (Util::isEmpty($car_type_id)) { +// return $this->error(trans('car_type_not_null')); +// } + $contacts = Util::getParam($request, 'contacts'); + $email = Util::getParam($request, 'email'); + $phone = Util::getParam($request, 'phone'); + if (Util::isEmpty($contacts) && Util::isEmpty($email) && Util::isEmpty($phone)) { + return $this->error(trans('contacts_not_null')); + } + + $data = [ + 'user_id' => $request->user->uid, + 'corp_abbr' => $request->basic_data['corp_abbr'], + 'grade' => Util::getParam($request, 'grade', 0), + 'channel_id' => $channel_id, + 'country_id' => $country_id, + 'car_type_id' => $car_type_id, + 'magnitude' => Util::getParam($request, 'magnitude', 0), + 'email' => $email, + 'phone' => $phone, + 'WhatsApp' => Util::getParam($request, 'WhatsApp', ''), + 'contacts' => $contacts, + 'describe' => Util::getParam($request, 'describe'), + 'lat' => Util::getParam($request, 'lat', 0), + 'lng' => Util::getParam($request, 'lng', 0), + 'address' => Util::getParam($request, 'address', ''), + 'sex' => Util::getParam($request, 'sex', 0), + ]; + + if (Util::isEmpty($id)) { + $res = Customer::create($data); + } else { + unset($data['grade']); + $res = Customer::where('id', $id) + ->update($data); +// Log::info('update', ['res' => $res]); + (!$res) ?: $res = Customer::find($id)->toArray(); + } + + if ($res) { + return $this->success($res, trans('save_success')); + } else { + return $this->error(trans('save_fail')); + } + } + + + + /** + * 更新下次跟进时间 + * @param Request $request + * @return \support\Response + */ + public function followNext(Request $request) + { + $id = Util::getParam($request, 'id'); + if (Util::isEmpty($id)) { + return $this->error('id' . trans('not_null')); + } + $data = [ + 'follow_next' => Util::getParam($request, 'follow_next', 0), + 'follow_remark' => Util::getParam($request, 'follow_remark', 0), + 'warn' => 0, + ]; + + $res = Customer::where('id', $id) + ->update($data); + + if ($res) { + return $this->success($res, trans('save_success')); + } else { + return $this->error(trans('save_fail')); + } + } + + /** + * 删除客户 + * @param Request $request + * @return \support\Response + */ + public function delete(Request $request) + { + $id = Util::getParam($request, 'id'); + $force = Util::getParam($request, 'force',''); + + $userId = $request->user->uid; + $where = [ + 'id' => $id, + 'user_id' => $userId, + ]; + $customer = Customer::where($where)->withTrashed()->first(); + if(empty($customer)){ + return $this->error( trans('fail')); + } + if($force == 'force'){ + $res = $customer->forceDelete(); + }else{ + $res = $customer->delete(); + } + if ($res) { + return $this->success($res, trans('success')); + } else { + return $this->error(trans('fail')); + } + } + + /** + * 统计客户 + * @param Request $request + * @return \support\Response + */ + public function statistics(Request $request) + { + $created = Util::getParam($request, 'created'); + if (Util::isEmpty($created)) { + return $this->error('created' . trans('not_null')); + } + $userId = $request->user->uid; + //$created_at = $request->user['created_at']; + $created_at = User::find($userId)->value('created_at'); + $created_at = strtotime($created_at); + $day = ceil((time() - $created_at + 1) / 60 / 60 / 24); + $where[] = ['user_id', '=', $userId]; + $where[] = ['corp_abbr', '=', $request->basic_data['corp_abbr']]; + $start = strtotime($created . '-01'); + $year = date('Y', $start); + $month = date('n', $start); //获取月份 + $end = mktime(00, 00, 00, $month + 1, 1, $year); + $where[] = ['created_at', '>=', $start]; + $where[] = ['created_at', '<', $end]; + $customer = Customer::where($where)->count(); + + //初步接触 + $whereGrade = $where; + $whereGrade[] = ['grade', '<=', 2]; + $customerInit = Customer::where($whereGrade)->count(); + + //跟进中 + $customeroFllow = Customer::where($where)->whereIn('grade', [3, 4])->count(); + + //已成交 + $whereGrade = $where; + $whereGrade[] = ['grade', '=', 5]; + $customerDeal = Customer::where($whereGrade)->count(); + + //拜访数 + $whereVisit = $where; + $whereVisit[] = ['type', '=', 0]; + $visit = Follow::where($whereVisit)->distinct('customer_id')->count(); + + //跟进数 + $whereVisit = $where; + $whereVisit[] = ['type', '=', 1]; + $follow = Follow::where($whereVisit)->distinct('customer_id')->count(); + + $res = [ + 'customer' => $customer, //客户数 + 'visit' => $visit, //拜访 + 'follow' => $follow, //跟进 + 'day' => $day, //注册天数 + 'customer_init' => $customerInit, //初步接触 + 'customero_fllow' => $customeroFllow, //跟进中 + 'customer_deal' => $customerDeal, //已成交 + ]; + if ($res) { + return $this->success($res, trans('success')); + } else { + return $this->error(trans('fail')); + } + } + + /** + * 领取公海客户 + * @param Request $request + * @return \support\Response + */ + public function claim(Request $request) + { + $customerId = Util::getParam($request, 'customer_id'); + $userId = $request->user->uid; + if (Util::isEmpty($customerId)) { + return $this->error(trans('customer_id').trans('not_null')); + } + $data = [ + 'user_id' => $request->user->uid + ]; + $customer= Customer::withTrashed()->find($customerId); + + if(empty($customer) || $customer->user_id != 0){ + return $this->error( trans('claim_fail')); + } + + $customer->restore(); + $res = $customer->update($data); + + //记录客户关系变化 + $his = CustomerHis::create([ + 'customer_id' => $customerId, + 'user_his' => 0, + 'user_id' => $userId, + ]); + + if ($res) { + return $this->success($res, trans('success')); + } else { + return $this->error(trans('fail')); + } + } + + + + /** + * 批量转让 + * @param Request $request + * @return \support\Response + */ + public function cede(Request $request) + { + $customerId = Util::getParam($request, 'customer_id'); + $user_sn = Util::getParam($request, 'user_sn'); + $userId = $request->user->uid; + if (Util::isEmpty($user_sn)) { + return $this->error('ID'.trans('not_null')); + } + + $user = User::where(['sn' => $user_sn])->first(); + if (Util::isEmpty($user)) { + return $this->error( trans('user_none')); + } + + if(Util::isEmpty($customerId)){ + $data = [ + 'user_id' => $userId + ]; + $query= Customer::where($data); + }else{ + $query= Customer::whereIn('id',explode(',',$customerId)); + } + $customer = $query->get(); + + foreach ($customer as $item) { + //记录客户关系变化 + $his = CustomerHis::create([ + 'customer_id' => $item->id, + 'user_his' => $item->user_id, + 'user_id' => $user['id'], + ]); + } + $res = $query->update(['user_id' => $user->id]); + + + if ($res) { + return $this->success($res, trans('success')); + } else { + return $this->error(trans('fail')); + } + } + + /** + * 跟进状态 + * @param Request $request + * @return \support\Response + */ + public function grade(Request $request) + { + $id = Util::getParam($request, 'id'); + $grade = Util::getParam($request, 'grade'); + if (Util::isEmpty($id)) { + return $this->error( 'id' . trans('not_null')); + } + if (Util::isEmpty($id)) { + return $this->error( 'grade' . trans('not_null')); + } + $data = [ + 'grade' => $grade, + ]; + $res = Customer::where('id', $id) + ->update($data); + + if ($res) { + return $this->success($res, trans('save_success')); + } else { + return $this->error(trans('save_fail')); + } + } +} \ No newline at end of file diff --git a/app/crm/controller/FollowController.php b/app/crm/controller/FollowController.php new file mode 100644 index 0000000..a23749c --- /dev/null +++ b/app/crm/controller/FollowController.php @@ -0,0 +1,141 @@ + $value) { + $grade[] = [ + 'status' => $key, + 'text' => $value, + ]; + } + return $this->success($grade); + } + + + /** + * 跟进列表 + * @param Request $request + * @return \support\Response + */ + public function list(Request $request) + { + try { + $data = $this->validate(new FollowValidate(), $request->get(), 'list'); + $request->setGet('corp_abbr', $request->basic_data['corp_abbr']); + $result = (new FollowService())->list($request->all(), $request->user->uid); + return $this->success($result); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 跟进详情 + * @param Request $request + * @return \support\Response + */ + public function detail(Request $request) + { + //跟进id + $id = Util::getParam($request, 'id'); + if (Util::isEmpty($id)) { + return $this->error('id' . trans('not_null')); + } + + $res = Follow::select('id', 'user_id', 'customer_id', 'type', 'picture', 'remark', 'audio', 'audio_text', 'lat', 'lng', 'address') + ->where('id', $id) + ->first(); + if ($res) { + $res->toArray(); + } else { + return $this->error('记录不存在'); + } + + $res = FollowService::getFollow($res); + + return $this->success($res); + } + + /** + * 客户录入 + * @param Request $request + * @return \support\Response + */ + public function save(Request $request) + { + $customer_id = Util::getParam($request, 'customer_id'); + + if (Util::isEmpty($customer_id)) { + return $this->error('customer_id' . trans('not_null')); + } + $type = Util::getParam($request, 'type'); + if (Util::isEmpty($type)) { + return $this->error('type' . trans('not_null')); + } + $picture = Util::getParam($request, 'picture'); + if (Util::isEmpty($picture)) { + return $this->error(trans('picture_not_null')); + } + + $data = [ + 'user_id' => $request->user->uid, + 'corp_abbr' => $request->basic_data['corp_abbr'], + 'customer_id' => $customer_id, + 'type' => $type, + 'picture' => $picture, + 'remark' => Util::getParam($request, 'remark', ''), + 'audio' => Util::getParam($request, 'audio', ''), + 'audio_text' => Util::getParam($request, 'audio_text', ''), + 'lat' => Util::getParam($request, 'lat', 0), + 'lng' => Util::getParam($request, 'lng', 0), + 'address' => Util::getParam($request, 'address'), + 'follow_time' => strtotime(Util::getParam($request, 'firstFollowtime', 0)), + ]; + + $res = Follow::create($data); + + $customer = Customer::find($customer_id); + $customer->follow_last = strtotime($res->created_at); + $customer->follow_next = null; + $customer->warn = 0; + $customer->save(); + + $follow_next = Util::getParam($request, 'follow_next', 0); + $follow_remark = Util::getParam($request, 'follow_remark', 0); + if ($res && (!empty($follow_next))) { + $data = [ + 'follow_next' => $follow_next, + 'follow_remark' => $follow_remark, + 'warn' => 0, + ]; + $res = Customer::where('id', $customer_id) + ->update($data); + } + + if ($res) { + return $this->success($res, trans('save_success')); + } else { + return $this->error(trans('save_fail')); + } + } + +} \ No newline at end of file diff --git a/app/crm/controller/TagGroupController.php b/app/crm/controller/TagGroupController.php new file mode 100644 index 0000000..2ac30e8 --- /dev/null +++ b/app/crm/controller/TagGroupController.php @@ -0,0 +1,80 @@ +user->uid; + $scene = $request->input("scene"); + $list = TagGroup::with('tags')->get(); + $languageArr = Constants::LANGUAGE; + $language = 'zh_CN'; + foreach ($list as &$item) { + $item['names'] = unserialize($item['names']); + if (in_array($language, $languageArr)) { + $item['name'] = $item['names'][$language]; + } + foreach ($item['tags'] as &$tag) { + $tag['names'] = unserialize($tag['names']); + if (in_array($language, $languageArr)) { + $tag['name'] = $tag['names'][$language]; + } + if($scene == 'customers'){ + // 统计每个标签关联的客户数量,增加user_id条件 + $tag['count'] = $tag->customers()->where('user_id', $uid)->count(); + } + } + } + + return $this->success($list); + } + + + /** + * 更新客户标签关系 + * @param Request $request + * @return \support\Response + */ + public function updatePivot(Request $request) + { + $customerId = Util::getParam($request, 'customer_id'); + $groupId = Util::getParam($request, 'group_id'); + $tagId = Util::getParam($request, 'tag_id'); + if (Util::isEmpty($customerId)) { + return $this->error( trans('customer_id') . trans('not_null')); + } + if (Util::isEmpty($groupId)) { + return $this->error( trans('tag_group_id') . trans('not_null')); + } + + + try { + if(empty($tagId)){ + $res = CustomerTag::where(['customer_id' => $customerId, 'group_id' => $groupId])->delete(); + }else{ + $res = CustomerTag::updateOrCreate(['customer_id' => $customerId, 'group_id' => $groupId], ['tag_id' => $tagId]); + } + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + + if($res){ + return $this->success($res, trans('success')); + }else{ + return $this->error( trans('fail')); + } + } +} \ No newline at end of file diff --git a/app/crm/logic/CustomerLogic.php b/app/crm/logic/CustomerLogic.php new file mode 100644 index 0000000..4e0619d --- /dev/null +++ b/app/crm/logic/CustomerLogic.php @@ -0,0 +1,15 @@ +select('id','name','name_en','status')->where('status',0)->toArray(); + return $list; + } +} \ No newline at end of file diff --git a/app/crm/model/Channel.php b/app/crm/model/Channel.php new file mode 100644 index 0000000..ac02c71 --- /dev/null +++ b/app/crm/model/Channel.php @@ -0,0 +1,48 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 +// 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'updated_at' => 'timestamp', // 指定序列化格式 + 'created_at' => 'timestamp', // 指定序列化格式 + ]; + + public static function getAll() + { + $list = self::get()->select('id','name','name_en','status')->where('status',1)->toArray(); + return $list; + } +} \ No newline at end of file diff --git a/app/crm/model/Country.php b/app/crm/model/Country.php new file mode 100644 index 0000000..f1fffd1 --- /dev/null +++ b/app/crm/model/Country.php @@ -0,0 +1,41 @@ +get(); + return $list; + } +} \ No newline at end of file diff --git a/app/crm/model/Customer.php b/app/crm/model/Customer.php new file mode 100644 index 0000000..af5c571 --- /dev/null +++ b/app/crm/model/Customer.php @@ -0,0 +1,82 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 + ]; + + /** + * 重新格式化时间 + * @param DateTimeInterface $date + * @return string + */ + protected function serializeDate(DateTimeInterface $date): string + { + return $date->format(Carbon::parse($date)->toDateTimeString()); + } + + /** + * 客户的标签 + */ + public function tags() + { + return $this->belongsToMany(Tag::class, 'crm_customer_tag', 'customer_id', 'tag_id') + ->withTimestamps(); // 如果需要记录关联时间 + } + + + // 定义与 变更历史 的一对多关系 + public function his() + { + return $this->hasMany(CustomerHis::class, 'customer_id', 'id'); + } + + // 定义反向多对一关联 + public function user() + { + return $this->belongsTo(User::class, 'user_id', 'id'); + } +} \ No newline at end of file diff --git a/app/crm/model/CustomerHis.php b/app/crm/model/CustomerHis.php new file mode 100644 index 0000000..65d7de6 --- /dev/null +++ b/app/crm/model/CustomerHis.php @@ -0,0 +1,68 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 +// 'created_at' => 'timestamp', // 指定序列化格式 + ]; + + /** + * 重新格式化时间 + * @param DateTimeInterface $date + * @return string + */ + protected function serializeDate(DateTimeInterface $date): string + { + return $date->format(Carbon::parse($date)->toDateTimeString()); + } + + // 定义反向多对一关联 + public function customer() + { + return $this->belongsTo(Customer::class, 'customer_id', 'id'); + } + + // 禁用 updated_at 的自动更新 + public function setUpdatedAt($value) + { + return $this; // 不做任何操作 + } +} \ No newline at end of file diff --git a/app/crm/model/CustomerTag.php b/app/crm/model/CustomerTag.php new file mode 100644 index 0000000..3a0c567 --- /dev/null +++ b/app/crm/model/CustomerTag.php @@ -0,0 +1,36 @@ + 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 + 'follow_time' => 'datetime:Y-m-d H:i:s', // 指定序列化格式 +// 'updated_at' => 'timestamp', // 指定序列化格式 +// 'created_at' => 'timestamp', // 指定序列化格式 + ]; +} \ No newline at end of file diff --git a/app/crm/model/Tag.php b/app/crm/model/Tag.php new file mode 100644 index 0000000..e58bb87 --- /dev/null +++ b/app/crm/model/Tag.php @@ -0,0 +1,55 @@ +belongsTo(TagGroup::class, 'group_id', 'id'); + } + + public function customers() + { + return $this->belongsToMany(Customer::class, 'crm_customer_tag', 'tag_id', 'customer_id'); + } +} \ No newline at end of file diff --git a/app/crm/model/TagGroup.php b/app/crm/model/TagGroup.php new file mode 100644 index 0000000..5b5e28e --- /dev/null +++ b/app/crm/model/TagGroup.php @@ -0,0 +1,50 @@ +hasMany(Tag::class, 'group_id', 'id'); + } +} \ No newline at end of file diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php new file mode 100644 index 0000000..e411984 --- /dev/null +++ b/app/crm/service/CustomerService.php @@ -0,0 +1,163 @@ +input('page', 1); +// $perPage = $request->input('per_page', 10); + $perPage = $params['size'] ?? 10; + $page = $params['current'] ?? 1; + $keyword = $params['keyword'] ?? ''; + $recycle = $params['recycle'] ?? ''; + $follow = $params['follow'] ?? ''; + $follow_last = $params['follow_last'] ?? ''; + $tagId = $params['tag_id'] ?? []; + $where = []; + $user_id =$params['user_id']; + if(empty($user_id) || $user_id == 0){ + $where[] = ['user_id', '=', $user_id]; + } + + $seas = $params['seas'] ?? ''; + if($seas == 'seas'){ + $where[] = ['user_id', 0]; + } + if($recycle == 'recycle'){ + $where[] = ['user_id', $user_id]; + } + + + $grade = []; + if (!Util::isEmpty($follow) && $follow == 0) { + $where[] = ['grade', '<=', 2]; + } elseif (!Util::isEmpty($follow) && $follow == 1) { + $grade = [3, 4]; + } elseif (!Util::isEmpty($follow) && $follow == 2) { + $where[] = ['grade', '=', 5]; + } + + if (!Util::isEmpty($follow_last) && $follow_last == 0) { + $where[] = ['follow_last', '>', strtotime('-1 day')]; + } elseif (!Util::isEmpty($follow_last) && $follow_last == 1) { + $where[] = ['follow_last', '>', strtotime('-3 day')]; + } elseif (!Util::isEmpty($follow_last) && $follow_last == 2) { + $where[] = ['follow_last', '>', strtotime('-7 day')]; + } + + $query = Customer::where($where); + + if(!empty($tagId)){ + //查询有$tagId 里任意一个标签的客户 + //$query->whereHas('tags', function($query) use ($tagId){ + // $query->whereIn ('tag.id', $tagId); + //}); + //查询有$tagId 里所有标签的客户 + foreach ($tagId as $id) { + $query->whereHas('tags', function($query) use ($id){ + $query->where('tag.id', $id); + }); + } + } + if (!Util::isEmpty($keyword)) { + $query->where(function ($query) use ($keyword) { + $query->where('contacts', 'like', "%{$keyword}%") + ->orWhere('phone', 'like', "%{$keyword}%"); + }); + } + if (!Util::isEmpty($grade)) { + $query->whereIn('grade', $grade); + } + + if ($recycle == 'recycle' || $seas == 'seas') { + $query->onlyTrashed(); + } + + $customer = Customer::where($where)->paginate($perPage, ['*'], 'page', $page); + foreach ($customer as &$item) { + $item = $this->getCustomer($item); + if($seas == 'seas'){ + $item['seas_time'] = time()-strtotime($item['deleted_at']); + } + } + unset($item); + + return $customer; + } + + + /** + * @param $customer + * @param $request + * @param $tag 是否返回客户标签 + * @return mixed + */ + public static function getCustomer($customer, $language = 'zh_CN', $tag = false) + { + $customer['grade_txt'] = Util::language($language, Constants::GRADE_EN[$customer['grade']], Constants::GRADE_ZH[$customer['grade']], Constants::GRADE_RU[$customer['grade']]); + $customer['magnitude_txt'] = Util::language($language, Constants::MAGNITUDE_EN[$customer['magnitude']], Constants::MAGNITUDE[$customer['magnitude']], Constants::MAGNITUDE_RU[$customer['magnitude']]); + $customer['sex_txt'] = Util::language($language, Constants::SEX_EN[$customer['sex']], Constants::SEX[$customer['sex']], Constants::SEX_RU[$customer['sex']]); + + if ($customer['grade']<=2) { + $customer['follow'] = 0; + $customer['follow_txt'] = Util::language($language,'Following','跟进中','Продолжение'); + }elseif ($customer['grade']<5) { + $customer['follow'] = 1; + $customer['follow_txt'] = Util::language($language,'transacted','已成交','Транзакция завершена'); + }else{ + $customer['follow'] = 2; + $customer['follow_txt'] = Util::language($language,'lost','已流失','Потерянный'); + } + + $where = [ + 'customer_id' => $customer['id'] + ]; + $begin_follow = Follow::select('id','created_at','updated_at') + ->where($where) + ->min('created_at'); + + + $customer['begin_follow'] = Util::timeTotDate($begin_follow); +// $customer['created_at'] = Util::timeTotDate($customer['created_at']); +// $customer['updated_at'] = Util::timeTotDate($customer['updated_at']); + + $customer = Util::formatDate($customer,['follow_last']); + $nameLang = $language=='en'?'name_en':($language=='ru'?'name_ru':'name'); + $countryLang = $language=='en'?'en_addr':'cn_addr'; +// $customer['car_type_name'] = empty($customer['car_type_id'])?'':CarType::where(['id'=>$customer['car_type_id']])->value($nameLang); + $customer['country_name'] = empty($customer['country_id'])?'':Country::where(['id'=>$customer['country_id']])->value($countryLang); + $customer['channel_name'] = empty($customer['channel_id'])?'':Channel::where(['id'=>$customer['channel_id']])->value($nameLang); +// $customer['last_follow'] = $last_follow; + + if ($tag) { + foreach ($customer['tags'] as &$tag) { + $tag['names'] = unserialize($tag['names']); + $tag['name'] = $tag['names'][$language]; + } + } + + return $customer; + } + + + public function detail($params) + { + $id =$params['id']; + $customer = Customer::with('tags')->select('id', 'user_id', 'grade', 'channel_id', 'car_type_id', 'country_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'address', 'lat', 'lng', 'follow_next', 'follow_remark', 'sex') + ->where('id', $id) + ->first(); + + return $customer; + } +} \ No newline at end of file diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php new file mode 100644 index 0000000..0d3fd8e --- /dev/null +++ b/app/crm/service/FollowService.php @@ -0,0 +1,65 @@ +input('page', 1); +// $perPage = $request->input('per_page', 10); + + //客户id + $customer_id = $param['customer_id']; + $corp_abbr = $param['corp_abbr']; + + $where = []; + $where['customer_id'] = $customer_id; + $where['corp_abbr'] = $corp_abbr; + + //用户角色 + + //跟进id + $id = $param['id'] ?? null; + if (!empty($id)) { + $where['id'] = $id; + } + + // 执行分页查询 + $data = Follow::where($where) + ->orderBy('id', 'desc')->get(); +// ->paginate($perPage, ['*'], 'page', $page); + + +// $host = $request->host(); +// $url = 'http:'.$request->url(); + foreach ($data as &$value) { + $value = self::getFollow($value); + } + + return $data; + } + + + public static function getFollow($follow) + { + $follow['type_txt'] = Constants::FOLLOW[$follow['type']]; + $follow['firstFollowtime'] = $follow['follow_time']; + $oss_picture = ''; + if (!empty($follow['picture'])) { + $ossservice = new OssService(); + $oss_picture = $ossservice->generatePrivateUrl($follow['picture'], 86400); + } + $follow['picture_full'] = $oss_picture; +// $follow = Util::formatDate($follow); + return $follow; + } +} \ No newline at end of file diff --git a/app/crm/utils/Util.php b/app/crm/utils/Util.php new file mode 100644 index 0000000..db6743b --- /dev/null +++ b/app/crm/utils/Util.php @@ -0,0 +1,121 @@ +input($param,$default); + if(empty($param) && $default!==''){ + $param = $default; + } + return $param; + } + + /** + * 失败 + */ + public static function fail($data = [],$message = '', $code = -1) + { + $result = [ + 'code' => $code, + 'message' => $message, + 'data' => $data + ]; + return json($result); + } + + /** + * 成功 + */ + public static function success($data = [],$message = '', $code = 0) + { + $result = [ + 'code' => $code, + 'message' => $message, + 'data' => $data + ]; + return json($result); + } + + /** + * 分页查询返回 + */ + public static function page($data,$message = '', $code = 0) + { + $result = [ + 'code' => $code, + 'message' => $message + ]; + + $result['data'] = [ + 'list' => $data->items(), // 当前页数据 + 'total' => $data->total(), // 总记录数 + 'current_page' => $data->currentPage(), + 'per_page' => $data->perPage(), + 'last_page' => $data->lastPage() + ]; + return json($result); + } + + /** + * 日期格式转化 + */ + public static function timeTotDate($time = 0, $format = 'Y-m-d H:i:s') + { + if (empty($time)) { + return ''; + } + return date($format, $time); + } + + /** + * 格式化创建时间 + */ + public static function formatDate($value,$key = ['created_at','updated_at']) + { + foreach ($key as $k){ + $value[$k]= self::timeTotDate($value[$k]); + } + return $value; + } + + /** + * 格式化创建时间 + */ + public static function language($language,$en,$zh_CN,$ru) + { + if ($language == 'zh_CN'){ + return $zh_CN; + }elseif ($language == 'ru'){ + return $ru; + } + return $en; + } + +} \ No newline at end of file diff --git a/app/crm/validate/FollowValidate.php b/app/crm/validate/FollowValidate.php new file mode 100644 index 0000000..a92c4a0 --- /dev/null +++ b/app/crm/validate/FollowValidate.php @@ -0,0 +1,21 @@ + 'require', + ]; + + protected $message = [ + 'customer_id.require' => '用户id不能为空', + ]; + + protected $scene = [ + 'list' => ['customer_id'], + ]; +} \ No newline at end of file diff --git a/app/ticket/service/CategorieService.php b/app/ticket/service/CategorieService.php index 4954c85..67d1d74 100644 --- a/app/ticket/service/CategorieService.php +++ b/app/ticket/service/CategorieService.php @@ -14,7 +14,7 @@ class CategorieService public function list($params) { $result = (new CategorieLogic())->list($params); - $value=$result[0]->toArray(); +// $value=$result[0]->toArray(); foreach ($result as $key => &$value) { $value['name'] = isset($value['translation']) ? $value['translation']['name'] : ''; unset($value['translation']); diff --git a/config/constants/Constants.php b/config/constants/Constants.php index 1e77a2b..6797078 100644 --- a/config/constants/Constants.php +++ b/config/constants/Constants.php @@ -34,4 +34,104 @@ class Constants self::TICKET_STATUS['REJECTED'] => 'Закрыть', ]; + //客户分级 USTOMER_GRADE + + public const GRADE = [ + 'INITIAL' => 0, // 初步接触 + 'COMMUNICATE' => 1, // 需求沟通 + 'PLAN' => 2, // 方案推进 + 'DEAL' => 3, // 成交阶段 + 'SALES' => 4, // 售后阶段 + 'LOSS' => 5, // 停滞或流失 + ]; + + public const GRADE_ZH = [ + self::GRADE['INITIAL'] => '初步接触', + self::GRADE['COMMUNICATE'] => '需求沟通', + self::GRADE['PLAN'] => '方案推进', + self::GRADE['DEAL'] => '成交阶段', + self::GRADE['SALES'] => '售后阶段', + self::GRADE['LOSS'] => '停滞或流失', + ]; + public const GRADE_EN = [ + self::GRADE['INITIAL'] => 'Initial contact', + self::GRADE['COMMUNICATE'] => 'Demand Chat', + self::GRADE['PLAN'] => 'promotion of the plan', + self::GRADE['DEAL'] => 'Transaction Stage', + self::GRADE['SALES'] => 'After Sales', + self::GRADE['LOSS'] => 'Stagnation or loss', + ]; + public const GRADE_RU = [ + self::GRADE['INITIAL'] => 'Первоначальные контакты', + self::GRADE['COMMUNICATE'] => 'Потребности в общении', + self::GRADE['PLAN'] => 'Продвижение программы', + self::GRADE['DEAL'] => 'Этап сделки', + self::GRADE['SALES'] => 'Послепродажный этап', + self::GRADE['LOSS'] => 'Застой или потеря', + ]; + + //客户重要性 + public const CUSTOMER_MAGNITUDE_INITIAL = 0; // 金牌商机 + public const CUSTOMER_MAGNITUDE_COMMUNICATE = 1; // 银牌商机 + public const CUSTOMER_MAGNITUDE_PLAN = 2; // 铜牌商机 + + public const MAGNITUDE = [ + self::CUSTOMER_MAGNITUDE_INITIAL => '金牌商机', + self::CUSTOMER_MAGNITUDE_COMMUNICATE => '银牌商机', + self::CUSTOMER_MAGNITUDE_PLAN => '铜牌商机', + ]; + public const MAGNITUDE_EN = [ + self::CUSTOMER_MAGNITUDE_INITIAL => 'Gold Medal', + self::CUSTOMER_MAGNITUDE_COMMUNICATE => 'Silver Medal', + self::CUSTOMER_MAGNITUDE_PLAN => 'Bronze Medal', + ]; + + public const MAGNITUDE_RU = [ + self::CUSTOMER_MAGNITUDE_INITIAL => 'Золотой бизнес', + self::CUSTOMER_MAGNITUDE_COMMUNICATE => 'Серебряный бизнес', + self::CUSTOMER_MAGNITUDE_PLAN => 'Бронзовый бизнес.', + ]; + + + //跟进类型 + public const CUSTOMER_FOLLOW_VISIT = 0; // 线下拜访 + public const CUSTOMER_FOLLOW_UP = 1; // 线上跟进 + + public const FOLLOW = [ + self::CUSTOMER_FOLLOW_VISIT => '线下拜访', + self::CUSTOMER_FOLLOW_UP => '线上跟进', + ]; + public const FOLLOW_EN = [ + self::CUSTOMER_FOLLOW_VISIT => 'Visit', + self::CUSTOMER_FOLLOW_UP => 'On line', + ]; + + //跟进类型 + public const CUSTOMER_SEX_UNKNOWN = 0; // 未知 + public const CUSTOMER_SEX_MAN = 1; // 男 + public const CUSTOMER_SEX_WOMAN = 2; // 女 + + public const SEX = [ + self::CUSTOMER_SEX_UNKNOWN => '未知', + self::CUSTOMER_SEX_MAN => '男', + self::CUSTOMER_SEX_WOMAN => '女', + ]; + public const SEX_EN = [ + self::CUSTOMER_SEX_UNKNOWN => 'unknown', + self::CUSTOMER_SEX_MAN => 'man', + self::CUSTOMER_SEX_WOMAN => 'woman', + ]; + + public const SEX_RU = [ + self::CUSTOMER_SEX_UNKNOWN => 'неизвестный', + self::CUSTOMER_SEX_MAN => 'мужской', + self::CUSTOMER_SEX_WOMAN => 'женский', + ]; + + public const SIGN = [ + ['id' => 'xinyouhe', 'name' => '新友合'], + ['id' => 'mbaf', 'name' => 'MAGIC BRIDGE AUTO FZE'] + ]; + + public const LANGUAGE = ['en', 'zh_CN','ru']; } \ No newline at end of file diff --git a/config/route.php b/config/route.php index 700fb4b..4ed7455 100644 --- a/config/route.php +++ b/config/route.php @@ -25,8 +25,6 @@ use Webman\Route; // POST /admin/user/{user_id}/kickout // body: {"tokens": ["..."]} // 踢下线 // POST /admin/user/{user_id}/kickout_all // 一键踢全部 -// 调试 -Route::any('/perm', [app\controller\IndexController::class, 'perm']); // 公共 Route::group('/common', function () { @@ -43,6 +41,7 @@ Route::group('/common', function () { Route::group('/user', function () { Route::any('/info', [app\common\controller\UserController::class, 'info']); + Route::any('/get', [app\common\controller\UserController::class, 'getUser']); }); Route::group('/preset', function () { @@ -98,3 +97,34 @@ Route::group('/ticket', function () { }); +//crm +Route::group('/crm', function () { + //跟进 + Route::any('/follow/list', [app\crm\controller\FollowController::class, 'list']); + Route::any('/follow/detail', [app\crm\controller\FollowController::class, 'detail']); + Route::any('/follow/add', [app\crm\controller\FollowController::class, 'save']); + Route::any('/follow/getFollowType', [app\crm\controller\FollowController::class, 'getFollowType']); + + //客户 + Route::any('/customer/list', [app\crm\controller\CustomerController::class, 'list']); + Route::any('/customer/detail', [app\crm\controller\CustomerController::class, 'detail']); + Route::any('/customer/add', [app\crm\controller\CustomerController::class, 'save']); + Route::any('/customer/claim', [app\crm\controller\CustomerController::class, 'claim']); + Route::any('/customer/restore', [app\crm\controller\CustomerController::class, 'restore']); + Route::any('/customer/delete', [app\crm\controller\CustomerController::class, 'delete']); + Route::any('/customer/waitFollow', [app\crm\controller\CustomerController::class, 'waitFollow']); + Route::any('/customer/statistics', [app\crm\controller\CustomerController::class, 'statistics']); + Route::any('/customer/getChannel', [app\crm\controller\CustomerController::class, 'getChannel']); + Route::any('/customer/getCountry', [app\crm\controller\CustomerController::class, 'getCountry']); + Route::any('/customer/followNext', [app\crm\controller\CustomerController::class, 'followNext']); + + Route::any('/tagGroup/list', [app\crm\controller\TagGroupController::class, 'list']); + Route::any('/tagGroup/updatePivot', [app\crm\controller\TagGroupController::class, 'updatePivot']); + + Route::any('/upload/image', [app\common\controller\UploadController::class, 'uploadImage']); + + + Route::any('/user/edit', [app\common\controller\UserController::class, 'edit']); + Route::any('/user/savePassword', [app\common\controller\UserController::class, 'savePassword']); +}); + diff --git a/resource/translations/ar/messages.php b/resource/translations/ar/messages.php index a701f87..7430697 100644 --- a/resource/translations/ar/messages.php +++ b/resource/translations/ar/messages.php @@ -2,4 +2,35 @@ return [ 'status_not_modified' => 'الدولة لا يمكن تعديلها عكسيا', + 'success' => ' النجاح', + 'fail' => ' الفشل', + 'login_success' => ' تم تسجيل الدخول بنجاح', + 'password_error' => ' كلمة المرور غير صحيحة', + 'user_none' => ' المستخدم غير موجود', + 'user_id' => ' معرف المستخدم', + 'user_login' => ' الرجاء تسجيل الدخول أولاً', + 'not_null' => ' لا يمكن أن يكون فارغًا', + 'code_error' => ' رمز التحقق غير صحيح', + 'format_error' => ' تنسيق غير صحيح', + 'email_repeat' => ' البريد الإلكتروني مكرر', + 'email_error' => ' تنسيق البريد الإلكتروني غير صحيح', + 'save_success' => ' تم الحفظ بنجاح', + 'save_fail' => ' فشل الحفظ', + 'register_success' => ' تم التسجيل بنجاح', + 'register_fail' => ' فشل التسجيل', + 'language_success' => ' تم تغيير اللغة بنجاح', + 'language_fail' => ' فشل تغيير اللغة', + 'param_error' => ' خطأ في المعلمات', + 'required_not_null' => ' الحقول المطلوبة لا يمكن أن تكون فارغة', + 'picture_not_null' => ' لا يمكن أن تكون الصورة فارغة', + 'contacts_not_null' => ' لا يمكن أن يكون جهة الاتصال أو البريد الإلكتروني أو الهاتف فارغين جميعًا في نفس الوقت', + 'car_type_not_null' => ' لا يمكن أن تكون التفضيلات فارغة', + 'channel_not_null' => ' لا يمكن أن تكون القناة فارغة', + 'email_consistent' => ' البريد الإلكتروني غير متطابق', + 'sign_not_null' => ' لم يتم تعيين الشركة التابعة', + 'password_short' => ' لا يمكن أن تكون كلمة المرور أقل من 6 أحرف', + 'id_empty' => ' لا يمكن أن يكون المعرف فارغًا', + 'customer_id' => ' معرف العميل', + 'tag_group_id' => ' معرف مجموعة العلامات', + 'claim_fail' => ' لا يمكن استلام العملاء الذين لديهم مالك بالفعل', ]; \ No newline at end of file diff --git a/resource/translations/en/messages.php b/resource/translations/en/messages.php index c676eb2..342ffad 100644 --- a/resource/translations/en/messages.php +++ b/resource/translations/en/messages.php @@ -2,4 +2,35 @@ return [ 'status_not_modified' => 'The status cannot be reversed for modification', + 'success' => ' success', + 'fail' => ' fail', + 'login_success' => ' login success', + 'password_error' => ' password error', + 'user_none' => ' user none', + 'user_id' => ' user ID', + 'user_login' => ' Please log in first', + 'not_null' => ' not null', + 'code_error' => ' code error', + 'format_error' => ' format error', + 'email_repeat' => ' email repeat', + 'email_error' => ' Email format error', + 'save_success' => ' save success', + 'save_fail' => ' save fail', + 'register_success' => ' register success', + 'register_fail' => ' register fail', + 'language_success' => ' Successfully switched languages', + 'language_fail' => ' Language switching failed', + 'param_error' => ' param error', + 'required_not_null' => ' required not_ ull', + 'picture_not_null' => ' picture not null', + 'contacts_not_null' => ' Contact person, email, and phone cannot be empty at the same time', + 'car_type_not_null' => ' preference_not_null', + 'channel_not_null' => ' channel not null', + 'email_consistent' => ' Inconsistent email addresses', + 'sign_not_null' => ' No affiliated company has been set', + 'password_short' => ' Password cannot be less than 6 characters', + 'id_empty' => ' ID cannot be empty', + 'customer_id' => ' customer ID', + 'tag_group_id' => ' tag group ID', + 'claim_fail' => ' Cannot claim ownership of customers', ]; \ No newline at end of file diff --git a/resource/translations/ru/messages.php b/resource/translations/ru/messages.php index 96ab3a4..3a1839f 100644 --- a/resource/translations/ru/messages.php +++ b/resource/translations/ru/messages.php @@ -2,4 +2,35 @@ return [ 'status_not_modified' => 'Статус не может быть изменен в обратном направлении', + 'success' => ' Успех', + 'fail' => ' Неудача', + 'login_success' => ' Регистрация завершена', + 'password_error' => ' Ошибка пароля', + 'user_none' => ' Пользователь не существует', + 'user_id' => ' Идентификатор пользователя', + 'user_login' => ' Пожалуйста, войдите.', + 'not_null' => ' Нельзя быть пустым', + 'code_error' => ' Ошибка кода проверки', + 'format_error' => ' Ошибка формата', + 'email_repeat' => ' Почтовый ящик повторяется', + 'email_error' => ' Ошибка формата почтового ящика', + 'save_success' => ' Сохранить успешно', + 'save_fail' => ' Ошибка сохранения', + 'register_success' => ' Регистрация успешно', + 'register_fail' => ' Ошибка регистрации', + 'language_success' => ' Переключить язык успешно', + 'language_fail' => ' Ошибка переключения языка', + 'param_error' => ' Ошибка параметра', + 'required_not_null' => ' Обязательное заполнение не может быть пустым.', + 'picture_not_null' => ' Фотографии не могут быть пустыми', + 'contacts_not_null' => ' Контакты, почтовый ящик, телефон не может быть одновременно пустым', + 'car_type_not_null' => ' Предпочтения не могут быть пустыми', + 'channel_not_null' => ' Каналы не могут быть пустыми', + 'email_consistent' => ' Почтовый ящик не совпадает', + 'sign_not_null' => ' Компания не создана', + 'password_short' => ' Пароль не может быть меньше 6.', + 'id_empty' => ' ID не может быть пустым', + 'customer_id' => ' Идентификатор клиента', + 'tag_group_id' => ' Идентификатор группы меток', + 'claim_fail' => ' Невозможность получения принадлежащего клиенту', ]; \ No newline at end of file diff --git a/resource/translations/zh/messages.php b/resource/translations/zh/messages.php index 229d7ee..96c3cb7 100644 --- a/resource/translations/zh/messages.php +++ b/resource/translations/zh/messages.php @@ -2,4 +2,35 @@ return [ 'status_not_modified' => '状态不能逆向修改', + 'success' => ' 成功', + 'fail' => ' 失败', + 'login_success' => ' 登录成功', + 'password_error' => ' 密码错误', + 'user_none' => ' 用户不存在', + 'user_id' => ' 用户ID', + 'user_login' => ' 请先登录', + 'not_null' => ' 不能为空', + 'code_error' => ' 验证码错误', + 'format_error' => ' 格式错误', + 'email_repeat' => ' 邮箱重复', + 'email_error' => ' 邮箱格式错误', + 'save_success' => ' 保存成功', + 'save_fail' => ' 保存失败', + 'register_success' => ' 注册成功', + 'register_fail' => ' 注册失败', + 'language_success' => ' 切换语言成功', + 'language_fail' => ' 切换语言失败', + 'param_error' => ' 参数错误', + 'required_not_null' => ' 必填项不能为空', + 'picture_not_null' => ' 图片不能为空', + 'contacts_not_null' => ' 联系人,邮箱,电话不能同时为空', + 'car_type_not_null' => ' 偏好不能为空', + 'channel_not_null' => ' 渠道不能为空', + 'email_consistent' => ' 邮箱不一致', + 'sign_not_null' => ' 未设置所属公司', + 'password_short' => ' 密码不能少于6位', + 'id_empty' => ' id不能为空', + 'customer_id' => ' 客户ID', + 'tag_group_id' => ' 标签组ID', + 'claim_fail' => ' 不能领取有归属客户', ]; \ No newline at end of file From be96c742ff2ed28856ee70667e04cd01e6d3a322 Mon Sep 17 00:00:00 2001 From: chenlan <18339827479@163.com> Date: Mon, 21 Jul 2025 16:43:43 +0800 Subject: [PATCH 066/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8F=9C=E5=8D=95url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/CrudLogic_bak.php | 106 ------- app/common/logic/PermLogic.php | 83 +++++- app/common/service/CrudService.php | 143 ---------- .../traits/crud/CrudControllerTrait.php | 2 +- .../traits/crud/CrudServiceTrait_bak2.php | 270 ------------------ app/common/utils/TableInspector.php | 57 ++++ app/common/utils/scaffold/Generate.php | 181 ++++++++++++ .../scaffold/templates/ControllerStub.stub | 0 .../utils/scaffold/templates/ModelStub.stub | 0 .../utils/scaffold/templates/ServiceStub.stub | 31 ++ .../scaffold/templates/ValidateStub.stub | 29 ++ app/common/validate/CorpValidate.php | 2 +- app/common/validate/RoleValidate.php | 2 +- app/controller/IndexController.php | 31 +- 14 files changed, 408 insertions(+), 529 deletions(-) delete mode 100644 app/common/logic/CrudLogic_bak.php delete mode 100644 app/common/service/CrudService.php delete mode 100644 app/common/traits/crud/CrudServiceTrait_bak2.php create mode 100644 app/common/utils/TableInspector.php create mode 100644 app/common/utils/scaffold/Generate.php create mode 100644 app/common/utils/scaffold/templates/ControllerStub.stub create mode 100644 app/common/utils/scaffold/templates/ModelStub.stub create mode 100644 app/common/utils/scaffold/templates/ServiceStub.stub create mode 100644 app/common/utils/scaffold/templates/ValidateStub.stub diff --git a/app/common/logic/CrudLogic_bak.php b/app/common/logic/CrudLogic_bak.php deleted file mode 100644 index 91a4c16..0000000 --- a/app/common/logic/CrudLogic_bak.php +++ /dev/null @@ -1,106 +0,0 @@ -modelClass = $modelClass; - } - - /** - * 列表(带分页) - * @param array $config CrudUtil::buildQuery 接受的配置 - * @param int $limit - * @param int $page - */ - public function list(array $config, int $limit, int $page) - { - $config['model'] = $this->modelClass; - $query = CrudUtil::buildQuery($config); - return CrudUtil::paginate($query, $limit, $page); - } - - /** - * 列表(不分页) - */ - public function listAll(array $config) - { - $config['model'] = $this->modelClass; - $query = CrudUtil::buildQuery($config); - return CrudUtil::getAll($query); - } - - /** - * 根据主键查询单条 - */ - public function show($id, array $with = []) - { - return CrudUtil::findByPk($this->modelClass, $id, $with); - } - - /** - * 根据任意条件查询单条(可带 dataLimit) - */ - public function showBy(array $where, array $with = [], array $dataLimit = []) - { - return CrudUtil::findOneBy($this->modelClass, $where, $with, $dataLimit); - } - - /** - * 创建(可带 dataLimit) - */ - public function create(array $data, array $dataLimit = []) - { - return CrudUtil::create($this->modelClass, $data, $dataLimit); - } - - /** - * 主键更新(可带 dataLimit) - */ - public function updateByPk($id, array $data, array $dataLimit = []) - { - return CrudUtil::updateByPk($this->modelClass, $id, $data, $dataLimit); - } - - /** - * 任意条件更新(可带 dataLimit) - */ - public function updateBy(array $where, array $data, array $dataLimit = []) - { - return CrudUtil::updateBy($this->modelClass, $where, $data, $dataLimit); - } - - /** - * 主键删除 - */ - public function deleteByPk(array $ids) - { - return CrudUtil::deleteByPk($this->modelClass, $ids); - } - - /** - * 任意条件删除(可带 dataLimit) - */ - public function deleteBy(array $where, array $dataLimit = []) - { - return CrudUtil::deleteBy($this->modelClass, $where, $dataLimit); - } - - /** - * 复杂查询回调 - * @param callable $cb function(Builder $q){ ... } - */ - public function complex(callable $cb) - { - return CrudUtil::executeComplex($this->modelClass, $cb); - } -} \ No newline at end of file diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index 674fbc8..16b7feb 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -60,7 +60,7 @@ class PermLogic * @param string $parentPath * @return array */ - public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array + public function buildTree_bak($resources, int $parentId = 0, string $parentPath = ''): array { $tree = []; foreach ($resources as $node) { @@ -121,6 +121,87 @@ class PermLogic } return $tree; } + /** + * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单 component 处理 + * + * @param \Illuminate\Support\Collection $resources 全部资源集合 + * @param int $parentId 当前父级资源 ID + * @param string $parentPath 父级完整路径(用于递归) + * @return array + */ + public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array + { + $tree = []; + + foreach ($resources as $node) { + if ($node->parent_id !== $parentId) { + continue; + } + + // —— 拼接当前节点的完整路径 —— + $currentPath = $parentPath; + // 去掉末尾多余 slash + $currentPath = rtrim($currentPath, '/'); + + if (!empty($node->url)) { + // 如果当前节点有 url,则拼接 + $urlSegment = ltrim($node->url, '/'); + $currentPath = $currentPath === '' + ? $urlSegment + : "{$currentPath}/{$urlSegment}"; + } + + // —— 递归构建子节点 —— + $children = []; + if ($node->type === 'directory') { + $children = $this->buildTree($resources, $node->id, $currentPath); + } + + // —— 收集本级的 permission 权限列表 —— + $authList = $resources + ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) + ->map(fn($r) => [ + 'id' => $r->id, + 'title' => $r->name, + 'auth_mark' => $r->code, + ]) + ->values() + ->all(); + + // —— 判断是否有子菜单/目录 —— + $hasChildrenMenu = $resources + ->where('parent_id', $node->id) + ->whereIn('type', ['directory', 'menu']) + ->isNotEmpty(); + + // —— 计算 component 字段 —— + if ($hasChildrenMenu) { + // 只要有子项,就一律显示顶层 Layout 组件 + $componentValue = '/index/index'; + } else { + // 叶子节点:如果定义了 url,就使用拼接后的路径;否则用占位符(这里用空串,你可替换) + $componentValue = $currentPath !== '' ? $currentPath : ''; + } + + $tree[] = [ + 'id' => $node->id, + 'name' => $node->name, + 'path' => $node->url, + 'component' => $componentValue, + 'meta' => [ + 'title' => $node->name, + 'icon' => $node->icon, + 'keepAlive' => true, + 'authList' => $authList, + 'openType' => $node->open_type, + ], + 'children' => $children, + ]; + } + + return $tree; + } + /** * 获取用户所有权限code diff --git a/app/common/service/CrudService.php b/app/common/service/CrudService.php deleted file mode 100644 index 8d66a37..0000000 --- a/app/common/service/CrudService.php +++ /dev/null @@ -1,143 +0,0 @@ -logic = $logic; - } - - /** - * 列表接口 - * 支持前端直接传递: - * - filter_* → where - * - sort_by, order - * - fields (逗号分隔) - * - with (逗号分隔) - * - withFields (relation:field,field;...) - * - dataLimitType, dataLimitField - * - limit, page - * - complex_query=1 则调用 complexQuery() - */ - public function list(Request $request): array - { - $p = $request->get(); - - // 复杂查询开关 - if (!empty($p['complex_query']) && method_exists($this, 'complexQuery')) { - return $this->complexQuery($p); - } - - // 1. 构造 where - $where = []; - foreach ($p as $k => $v) { - if (strpos($k, 'filter_') === 0 && $v !== '') { - $col = substr($k, 7); - // 简单等值或 like - $where[$col] = strpos($v, '%') !== false ? ['like', $v] : $v; - } - } - - // 2. 构造其他配置 - $config = [ - 'where' => $where, - 'order' => [($p['sort_by'] ?? 'id') => ($p['order'] ?? 'desc')], - 'fields' => !empty($p['fields']) ? explode(',', $p['fields']) : [], - 'with' => !empty($p['with']) ? explode(',', $p['with']) : [], - 'withFields' => $this->parseWithFields($p['withFields'] ?? ''), - 'dataLimit' => !empty($p['dataLimitType']) ? [ - 'type' => $p['dataLimitType'], - 'field' => $p['dataLimitField'] ?? 'admin_id' - ] : [], - ]; - - $limit = (int)($p['limit'] ?? 15); - $page = (int)($p['page'] ?? 1); - - $pager = $this->logic->list($config, $limit, $page); - return ['total' => $pager->total(), 'data' => $pager->items()]; - } - - /** - * 详情 - */ - public function show($id) - { - return $this->logic->show($id, []); - } - - /** - * 创建 - */ - public function store(Request $request) - { - $data = $request->post(); - $dataLimit = $this->getDataLimitFromRequest($request); - return $this->logic->create($data, $dataLimit); - } - - /** - * 更新 - */ - public function update(Request $request) - { - $key = $this->logic->modelClass::make()->getKeyName(); - $id = (int)$request->post($key); - $data= $request->post(); - $dataLimit = $this->getDataLimitFromRequest($request); - return $this->logic->updateByPk($id, $data, $dataLimit); - } - - /** - * 删除 - */ - public function destroy(Request $request) - { - $key = $this->logic->modelClass::make()->getKeyName(); - $ids = (array)$request->post($key, []); - return $this->logic->deleteByPk($ids); - } - - /** ---------- helpers ---------- */ - - /** - * 解析 withFields 参数 "rel:field,field;rel2:..." - */ - protected function parseWithFields(string $str): array - { - if (!$str) return []; - $out = []; - foreach (explode(';', $str) as $pair) { - [$rel, $flds] = explode(':', $pair); - $out[$rel] = explode(',', $flds); - } - return $out; - } - - /** - * 从请求里读取 dataLimitType/Field - */ - protected function getDataLimitFromRequest(Request $req): array - { - $type = $req->get('dataLimitType'); - if (!$type) return []; - return [ - 'type' => $type, - 'field' => $req->get('dataLimitField', 'admin_id'), - ]; - } - - /** - * 子类可重写,做复杂联表查询等 - */ - protected function complexQuery(array $params): array - { - throw new \LogicException('请在子类实现 complexQuery 方法'); - } -} \ No newline at end of file diff --git a/app/common/traits/crud/CrudControllerTrait.php b/app/common/traits/crud/CrudControllerTrait.php index c5cdfdc..41d9cd5 100644 --- a/app/common/traits/crud/CrudControllerTrait.php +++ b/app/common/traits/crud/CrudControllerTrait.php @@ -58,7 +58,7 @@ trait CrudControllerTrait public function store(Request $request) { try { - $data = $this->validate($this->service->getValidator(), $request->post(), 'create'); + $data = $this->validate($this->service->getValidator(), $request->post(), 'store'); $res = $this->service->serviceStore($request, $data); return $this->success($res, '创建成功'); } catch (\Exception $e) { diff --git a/app/common/traits/crud/CrudServiceTrait_bak2.php b/app/common/traits/crud/CrudServiceTrait_bak2.php deleted file mode 100644 index a75b28f..0000000 --- a/app/common/traits/crud/CrudServiceTrait_bak2.php +++ /dev/null @@ -1,270 +0,0 @@ -get('complex_query') && method_exists($this, 'complexQuery')) { - $data = $this->complexQuery($request->get()); - } else { - $cfg = $this->buildQueryConfig($request, 'list'); - $limit = (int)$request->get('limit', 15); - $page = (int)$request->get('page', 1); - $data = $this->logicList($cfg, $limit, $page); - } - return $this->postProcess('List', $data); - } - - /** 查看单条 */ - public function serviceShow(Request $request, $id) - { - $item = $this->logicShow($id, []); - return $this->postProcess('Show', $item); - } - - /** 创建 */ - public function serviceStore(Request $request) - { - $data = $request->post(); - $dl = $this->getDataLimitFromRequest($request); - $item = $this->logicCreate($data, $dl); - return $this->postProcess('Store', $item); - } - - /** 更新 */ - public function serviceUpdate(Request $request) - { - $key = (new $this->modelClass)->getKeyName(); - $id = (int)$request->post($key); - $data = $request->post(); - $dl = $this->getDataLimitFromRequest($request); - $item = $this->logicUpdateByPk($id, $data, $dl); - return $this->postProcess('Update', $item); - } - - /** 删除 */ - public function serviceDestroy(Request $request) - { - $key = (new $this->modelClass)->getKeyName(); - $ids = (array)$request->post($key, []); - $result = $this->logicDeleteByPk($ids); - return $this->postProcess('Destroy', $result); - } - - // ---------------- 统一后置钩子 ---------------- // - - /** - * 各操作后触发 afterService{Op}() - * @param string $op List|Show|Store|Update|Destroy - */ - protected function postProcess(string $op, mixed $data): mixed - { - $hook = "afterService{$op}"; - return method_exists($this, $hook) - ? $this->{$hook}($data) - : $data; - } - - // ---------------- 复杂查询入口 ---------------- // - - /** - * 子类 override 实现完全定制的复杂查询 - * @param array $params 全部请求参数 - */ - protected function complexQuery(array $params) - { - throw new \LogicException('请在子类实现 complexQuery()'); - } - - // ------------- 辅助:数据权限 & withFields 解析 ------------- // - - /** 从请求读取 dataLimitType & dataLimitField → ['type'=>..,'field'=>..] */ - protected function getDataLimitFromRequest(Request $req): array - { - $type = $req->get('dataLimitType'); - if (!$type) { - return []; - } - return [ - 'type' => $type, - 'field' => $req->get('dataLimitField', 'admin_id'), - ]; - } - - /** 解析 withFields 字符串 "rel:field,field;rel2:..." */ - protected function parseWithFields(string $str): array - { - if (!$str) { - return []; - } - $out = []; - foreach (explode(';', $str) as $pair) { - [$rel, $flds] = explode(':', $pair); - $out[$rel] = explode(',', $flds); - } - return $out; - } - - // ------------- 扁平化 Request → 查询配置 ------------- // - - /** - * 构建完整配置 - * 返回 [ - * where=>[], with=>[], order=>[], withFields=>?, fields=>?, dataLimit=>[] - * ] - */ - protected function buildQueryConfig(Request $request, string $scene = 'list'): array - { - return [ - 'where' => $this->buildFilters($request, $scene), - 'with' => $this->buildRelations($request, $scene), - 'order' => $this->buildOrder($request, $scene), - 'withFields' => $this->buildWithFields($request,$scene), - 'fields' => $this->buildFields($request, $scene), - 'dataLimit' => $this->buildDataLimit($request, $scene), - ]; - } - - /** - * 通用合并方法:取公共配置 + 场景专属配置,若均为 array 则 array_merge - * - * @param mixed $common 公共配置 - * @param Request $request - * @param string $scene - * @param string $type Filters|Relations|Order|WithFields|Fields|DataLimit - */ - private function applySceneConfig( - mixed $common, - Request $request, - string $scene, - string $type - ): mixed { - $method = 'apply' . ucfirst($scene) . $type; - if (! method_exists($this, $method)) { - return $common; - } - $sceneCfg = $this->{$method}($common, $request); - if (is_array($common) && is_array($sceneCfg)) { - return array_merge($common, $sceneCfg); - } - return $sceneCfg; - } - - /** 构建 WHERE:commonFilters + apply{Scene}Filters */ - protected function buildFilters(Request $r, string $scene): array - { - $common = $this->commonFilters($r); - return (array)$this->applySceneConfig($common, $r, $scene, 'Filters'); - } - - /** 构建 with 关联 */ - protected function buildRelations(Request $r, string $scene): array - { - $common = $this->commonRelations(); - return (array)$this->applySceneConfig($common, $r, $scene, 'Relations'); - } - - /** 构建 order 排序 */ - protected function buildOrder(Request $r, string $scene): array - { - $common = $this->commonOrder(); - return (array)$this->applySceneConfig($common, $r, $scene, 'Order'); - } - - /** 构建 withFields(关联模型字段白名单) */ - protected function buildWithFields(Request $r, string $scene): ?array - { - $common = []; - return $this->applySceneConfig($common, $r, $scene, 'WithFields'); - } - - /** 构建 fields(主表字段白名单) */ - protected function buildFields(Request $r, string $scene): ?array - { - $common = []; - return $this->applySceneConfig($common, $r, $scene, 'Fields'); - } - - /** 构建 dataLimit(数据权限) */ - protected function buildDataLimit(Request $r, string $scene): array - { - $common = []; - return (array)$this->applySceneConfig($common, $r, $scene, 'DataLimit'); - } - - - // ------------- 可重写的“公共 + 场景” 方法 ------------- // - - // --- common* 系列(全局默认) --- - protected function commonFilters(Request $r): array { return []; } - protected function commonRelations(): array { return []; } - protected function commonOrder(): array { return []; } - - // // --- list* 系列 --- - // protected function applyListFilters(array $c, Request $r): array { return $this->listFilters($r); } - // protected function listFilters(Request $r): array { return []; } - - // protected function applyListRelations(array $c, Request $r): array { return $this->listRelations(); } - // protected function listRelations(): array { return []; } - - // protected function applyListOrder(array $c, Request $r): array { return $this->listOrder($r); } - // protected function listOrder(Request $r): array { return ['created_at'=>'desc']; } - - // protected function applyListWithFields(array $c, Request $r): array{ return $this->listWithFields($r); } - // protected function listWithFields(Request $r): array { return []; } - - // protected function applyListFields(array $c, Request $r): array { return $this->listFields($r); } - // protected function listFields(Request $r): array { return []; } - - // protected function applyListDataLimit(array $c, Request $r): array { return $this->listDataLimit($r); } - // protected function listDataLimit(Request $r): array { return []; } - - // // --- detail* 系列 --- - // protected function applyDetailFilters(array $c, Request $r): array { return $this->detailFilters($r); } - // protected function detailFilters(Request $r): array { return ['id'=>$r->get('id')]; } - - // protected function applyDetailRelations(array $c, Request $r): array { return $this->detailRelations(); } - // protected function detailRelations(): array { return []; } - - // protected function applyDetailOrder(array $c, Request $r): array { return $this->detailOrder($r); } - // protected function detailOrder(Request $r): array { return []; } - - // protected function applyDetailWithFields(array $c, Request $r): array{ return $this->detailWithFields($r); } - // protected function detailWithFields(Request $r): array { return []; } - - // protected function applyDetailFields(array $c, Request $r): array { return $this->detailFields($r); } - // protected function detailFields(Request $r): array { return []; } - - // protected function applyDetailDataLimit(array $c, Request $r): array { return $this->listDataLimit($r); } - - // // --- export* 系列(如有需要可 override) --- - // protected function applyExportFilters(array $c, Request $r): array { return $this->exportFilters($r); } - // protected function exportFilters(Request $r): array { return []; } -} \ No newline at end of file diff --git a/app/common/utils/TableInspector.php b/app/common/utils/TableInspector.php new file mode 100644 index 0000000..6b00995 --- /dev/null +++ b/app/common/utils/TableInspector.php @@ -0,0 +1,57 @@ +getDatabaseName(); + + $columns = Db::select(" + SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, + COLUMN_DEFAULT, COLUMN_COMMENT, EXTRA + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? + ORDER BY ORDINAL_POSITION + ", [$database, $table]); + + return array_map(function ($col) { + return [ + 'name' => $col->COLUMN_NAME, + 'type' => $col->DATA_TYPE, + 'column_type' => $col->COLUMN_TYPE, + 'nullable' => $col->IS_NULLABLE === 'YES', + 'default' => $col->COLUMN_DEFAULT, + 'comment' => $col->COLUMN_COMMENT, + 'extra' => $col->EXTRA, + ]; + }, $columns); + } + + /** + * 获取字段名列表 + */ + public static function getColumnNames(string $table, ?string $database = null): array + { + return array_column(self::getColumns($table, $database), 'name'); + } + + /** + * 获取字段注释 map:字段名 => 注释 + */ + public static function getColumnComments(string $table, ?string $database = null): array + { + $columns = self::getColumns($table, $database); + $map = []; + foreach ($columns as $col) { + $map[$col['name']] = $col['comment']; + } + return $map; + } +} \ No newline at end of file diff --git a/app/common/utils/scaffold/Generate.php b/app/common/utils/scaffold/Generate.php new file mode 100644 index 0000000..d0b8a7d --- /dev/null +++ b/app/common/utils/scaffold/Generate.php @@ -0,0 +1,181 @@ +options = $options; + } + + public function doGenerate() + { + $fs = new Filesystem(); + + // 模板路径 + $templateDir = __DIR__ . '/../templates/'; + $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/{$this->options['controller']['name']}Controller.php"; + $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/{$this->options['service']['name']}Service.php"; + $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/service/{$this->options['logic']['name']}Service.php"; + $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/{$this->options['model']['name']}.php"; + $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/{$this->options['validate']['name']}Validate.php"; + + // // 创建目录(如果不存在) + $fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller"); + $fs->mkdir(__DIR__ . "/../../../{$this->options['service']['module']}/service"); + $fs->mkdir(__DIR__ . "/../../../{$this->options['logic']['module']}/logic"); + $fs->mkdir(__DIR__ . "/../../../{$this->options['model']['module']}/model"); + $fs->mkdir(__DIR__ . "/../../../{$this->options['validate']['module']}/validate"); + + // // 删除已存在的文件并重新生成 + $this->deleteFileIfExists($controllerPath); + $this->deleteFileIfExists($servicePath); + $this->deleteFileIfExists($logicPath); + $this->deleteFileIfExists($modelPath); + $this->deleteFileIfExists($validatePath); + + // 生成控制器文件 + $ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath); + + // 生成服务类文件 + $ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath); + + // 生成逻辑类文件 + $LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath); + + // 生成模型类文件 + $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath); + + // 生成验证器类文件 + $ValidatorStub = $this->generateFile($templateDir, 'ValidatorStub.stub', $validatePath); + + return $ControllerStub . $ServiceStub . $LogicStub . $ModelStub . $ValidatorStub . "文件生成成功!
"; + } + + /** + * 设置字段 + * + * @param array $fields 字段数组 + */ + public function fillableFields() + { + $tableName = $this->options['table']; + $fields = TableInspector::getColumnNames($tableName); + $str = ''; + foreach ($fields as $key => $val) { + if ($key < (count($fields)-1)) { + $str .= "'{$val}', "; + } else { + $str .= "'{$val}'"; + } + } + $fields = trim($str, '"'); + return $fields; + } + + /** + * 根据模板生成文件 + * + * @param string $templateDir 模板目录 + * @param string $templateFile 模板文件名 + * @param string $outputPath 输出文件路径 + */ + protected function generateFile($templateDir, $templateFile, $outputPath) + { + $templatePath = $templateDir . $templateFile; + + if (!file_exists($templatePath)) { + echo "模板文件 {$templateFile} 不存在! \n"; + return; + } + + $content = file_get_contents($templatePath); + $content = str_replace('{{name}}', $this->convertName, $content); + + // 填入table名 + $content = str_replace('{{table}}', $this->dbPrefix.$this->name, $content); + + // 填入module名 + $content = str_replace('{{module}}', $this->options['controller']['module'], $content); + + // 填入字段 + $content = str_replace('{{fillableFields}}', $this->fillableFields(), $content); + + file_put_contents($outputPath, $content); + + return "生成文件: {$outputPath}
"; + } + + /** + * 如果文件存在,删除它 + * + * @param string $path 文件路径 + */ + protected function deleteFileIfExists($path) + { + if (file_exists($path)) { + unlink($path); + echo "删除已存在文件: {$path} \n"; + } + } + + /** + * 设置名称 + */ + public function setName($name = '') + { + $this->name = $name; + $this->convertName = ucfirst(camelize($name)); + } + + /** + * 设置模块名称 + */ + public function setModule($name = '') + { + $this->options['controller']['module'] = $name; + } +} + +/** +* 下划线转驼峰 +* 思路: +* step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符 +* step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符. +*/ +function camelize($uncamelized_words, $separator='_') { + $uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words)); + return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator ); +} + +/** +* 驼峰命名转下划线命名 +* 思路: +* 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写 +*/ +function uncamelize($camelCaps, $separator='_') { + return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps)); +} \ No newline at end of file diff --git a/app/common/utils/scaffold/templates/ControllerStub.stub b/app/common/utils/scaffold/templates/ControllerStub.stub new file mode 100644 index 0000000..e69de29 diff --git a/app/common/utils/scaffold/templates/ModelStub.stub b/app/common/utils/scaffold/templates/ModelStub.stub new file mode 100644 index 0000000..e69de29 diff --git a/app/common/utils/scaffold/templates/ServiceStub.stub b/app/common/utils/scaffold/templates/ServiceStub.stub new file mode 100644 index 0000000..bc01577 --- /dev/null +++ b/app/common/utils/scaffold/templates/ServiceStub.stub @@ -0,0 +1,31 @@ +validator; + } +} \ No newline at end of file diff --git a/app/common/utils/scaffold/templates/ValidateStub.stub b/app/common/utils/scaffold/templates/ValidateStub.stub new file mode 100644 index 0000000..9089654 --- /dev/null +++ b/app/common/utils/scaffold/templates/ValidateStub.stub @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/common/validate/CorpValidate.php b/app/common/validate/CorpValidate.php index 5889ef9..f1710d6 100644 --- a/app/common/validate/CorpValidate.php +++ b/app/common/validate/CorpValidate.php @@ -29,7 +29,7 @@ class CorpValidate extends Validate ]; protected $scene = [ - 'create' => ['name', 'abbr', 'tenant_key', 'sort', 'status'], + 'store' => ['name', 'abbr', 'tenant_key', 'sort', 'status'], 'update' => ['id', 'name', 'abbr', 'tenant_key', 'sort', 'status'] ]; } \ No newline at end of file diff --git a/app/common/validate/RoleValidate.php b/app/common/validate/RoleValidate.php index afd69cc..372159f 100644 --- a/app/common/validate/RoleValidate.php +++ b/app/common/validate/RoleValidate.php @@ -26,7 +26,7 @@ class RoleValidate extends Validate ]; protected $scene = [ - 'create' => ['system_id', 'name', 'desc', 'sort'], + 'store' => ['system_id', 'name', 'desc', 'sort'], 'update' => ['id', 'system_id', 'name', 'desc', 'sort', 'status'], ]; } diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 866d3d5..b3be9af 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -66,12 +66,31 @@ class IndexController } - // test function - public function test(Request $request) + public function c_() { - $user = User::find(1); - $user->roles()->sync([1]); - $user->systems()->sync([1]); - return json(['code' => 0, 'msg' => 'success']); + $options = [ + 'controller' => [ + 'module' => 'commonName', + 'name' => 'ControllerName' + ], + 'model' => [ + 'module' => 'commonName', + 'name' => 'modelName' + ], + 'service' => [ + 'module' => 'commonName', + 'name' => 'serviceName' + ], + 'logic' => [ + 'module' => 'commonName', + 'name' => 'logicName' + ], + 'validate' => [ + 'module' => 'commonName', + 'name' => 'validateName' + ], + 'table' => 'tableName', + 'fillable' => [] + ]; } } \ No newline at end of file From 7acf3fca99660145b6b961fd2a2c7f3d4fec6b22 Mon Sep 17 00:00:00 2001 From: chenlan <18339827479@163.com> Date: Mon, 21 Jul 2025 16:58:22 +0800 Subject: [PATCH 067/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A2=84=E9=80=89?= =?UTF-8?q?=E5=8F=82=E6=95=B0-=E7=B3=BB=E7=BB=9F=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/PresetController.php | 17 ++++++++++++++- app/common/logic/SystemsLogic.php | 14 +++++++++++++ app/common/service/SystemsService.php | 24 ++++++++++++++++++++++ config/route.php | 1 + 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 app/common/logic/SystemsLogic.php create mode 100644 app/common/service/SystemsService.php diff --git a/app/common/controller/PresetController.php b/app/common/controller/PresetController.php index 964b2a6..a4b2a81 100644 --- a/app/common/controller/PresetController.php +++ b/app/common/controller/PresetController.php @@ -5,11 +5,14 @@ namespace app\common\controller; use support\Request; use app\common\service\CorpService; use app\common\service\CorpFeishuAppService; +use app\common\service\SystemsService; class PresetController extends BaseController { - protected $noNeedLogin = ['corps', 'feishuApp']; + protected $noNeedLogin = ['corps', 'feishuApp', 'systems']; + + protected $noNeedHeaderData = ['corps', 'feishuApp', 'systems']; public function corps() { @@ -33,4 +36,16 @@ class PresetController extends BaseController return $this->error($e->getMessage()); } } + + + + public function systems() + { + try { + $result = (new SystemsService())->getList(); + return $this->success($result, '获取系统列表'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/common/logic/SystemsLogic.php b/app/common/logic/SystemsLogic.php new file mode 100644 index 0000000..5411264 --- /dev/null +++ b/app/common/logic/SystemsLogic.php @@ -0,0 +1,14 @@ +orderBy('sort', 'asc')->get(['id', 'name', 'code', 'icon', 'type']); + } +} \ No newline at end of file diff --git a/app/common/service/SystemsService.php b/app/common/service/SystemsService.php new file mode 100644 index 0000000..10b80a3 --- /dev/null +++ b/app/common/service/SystemsService.php @@ -0,0 +1,24 @@ +logic = (new SystemsLogic()); + } + + /** + * 获取系统list + */ + public function getList() + { + return $this->logic->getList(); + } +} \ No newline at end of file diff --git a/config/route.php b/config/route.php index 4ed7455..e5bb7f2 100644 --- a/config/route.php +++ b/config/route.php @@ -47,6 +47,7 @@ Route::group('/common', function () { Route::group('/preset', function () { Route::any('/corps', [app\common\controller\PresetController::class, 'corps']); Route::any('/feishu/app', [app\common\controller\PresetController::class, 'feishuApp']); + Route::any('/systems', [app\common\controller\PresetController::class, 'systems']); }); }); From c42236da7e6eb4579bb0902591058d762761cbb3 Mon Sep 17 00:00:00 2001 From: mzl Date: Mon, 21 Jul 2025 19:11:29 +0800 Subject: [PATCH 068/120] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90cur?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/utils/scaffold/Generate.php | 84 +++++++++---------- .../scaffold/templates/ControllerStub.stub | 33 ++++++++ .../utils/scaffold/templates/LogicStub.stub | 17 ++++ .../utils/scaffold/templates/ModelStub.stub | 49 +++++++++++ .../utils/scaffold/templates/ServiceStub.stub | 12 +-- .../scaffold/templates/ValidateStub.stub | 4 +- app/controller/IndexController.php | 44 ++++++---- composer.json | 3 +- composer.lock | 68 ++++++++++++++- config/route.php | 4 + 10 files changed, 248 insertions(+), 70 deletions(-) create mode 100644 app/common/utils/scaffold/templates/LogicStub.stub diff --git a/app/common/utils/scaffold/Generate.php b/app/common/utils/scaffold/Generate.php index d0b8a7d..687780d 100644 --- a/app/common/utils/scaffold/Generate.php +++ b/app/common/utils/scaffold/Generate.php @@ -7,19 +7,8 @@ use app\common\utils\TableInspector; class Generate { - // 表名 - protected $name = ''; - - // 控制器名 - protected $convertName = ''; - - // 模块名 - protected $moduleName = ''; - - // 表前缀 - protected $dbPrefix = 'hs_'; - + // 配置项 protected $options = []; public function __construct(array $options = []) @@ -43,7 +32,7 @@ class Generate $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/{$this->options['model']['name']}.php"; $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/{$this->options['validate']['name']}Validate.php"; - // // 创建目录(如果不存在) + // 创建目录(如果不存在) $fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller"); $fs->mkdir(__DIR__ . "/../../../{$this->options['service']['module']}/service"); $fs->mkdir(__DIR__ . "/../../../{$this->options['logic']['module']}/logic"); @@ -58,19 +47,19 @@ class Generate $this->deleteFileIfExists($validatePath); // 生成控制器文件 - $ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath); + $ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath, 'controller'); // 生成服务类文件 - $ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath); + $ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath, 'service'); // 生成逻辑类文件 - $LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath); + $LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath, 'logic'); // 生成模型类文件 - $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath); + $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath, 'model'); // 生成验证器类文件 - $ValidatorStub = $this->generateFile($templateDir, 'ValidatorStub.stub', $validatePath); + $ValidatorStub = $this->generateFile($templateDir, 'ValidatorStub.stub', $validatePath, 'validate'); return $ControllerStub . $ServiceStub . $LogicStub . $ModelStub . $ValidatorStub . "文件生成成功!
"; } @@ -103,7 +92,7 @@ class Generate * @param string $templateFile 模板文件名 * @param string $outputPath 输出文件路径 */ - protected function generateFile($templateDir, $templateFile, $outputPath) + protected function generateFile($templateDir, $templateFile, $outputPath, $fileType) { $templatePath = $templateDir . $templateFile; @@ -113,16 +102,38 @@ class Generate } $content = file_get_contents($templatePath); - $content = str_replace('{{name}}', $this->convertName, $content); - // 填入table名 - $content = str_replace('{{table}}', $this->dbPrefix.$this->name, $content); - - // 填入module名 - $content = str_replace('{{module}}', $this->options['controller']['module'], $content); - - // 填入字段 - $content = str_replace('{{fillableFields}}', $this->fillableFields(), $content); + switch ($fileType) { + case 'controller': + $content = str_replace('{{controllerModule}}', $this->options['controller']['module'], $content); + $content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content); + $content = str_replace('{{serviceName}}', $this->options['service']['name'], $content); + $content = str_replace('{{controllerName}}', $this->options['controller']['name'], $content); + break; + case 'service': + $content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content); + $content = str_replace('{{validateName}}', $this->options['validate']['name'], $content); + $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); + $content = str_replace('{{modelName}}', $this->options['model']['name'], $content); + $content = str_replace('{{serviceName}}', $this->options['service']['name'], $content); + break; + case 'logic': + $content = str_replace('{{logicModule}}', $this->options['logic']['module'], $content); + $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); + $content = str_replace('{{modelName}}', $this->options['model']['name'], $content); + $content = str_replace('{{logicName}}', $this->options['logic']['name'], $content); + break; + case 'model': + $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); + $content = str_replace('{{modelName}}', $this->options['model']['name'], $content); + $content = str_replace('{{table}}', $this->options['table'], $content); + $content = str_replace('{{fillable}}', $this->options['fillable'], $content); + break; + case 'validate': + $content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content); + $content = str_replace('{{validateName}}', $this->options['validate']['name'], $content); + break; + } file_put_contents($outputPath, $content); @@ -141,23 +152,6 @@ class Generate echo "删除已存在文件: {$path} \n"; } } - - /** - * 设置名称 - */ - public function setName($name = '') - { - $this->name = $name; - $this->convertName = ucfirst(camelize($name)); - } - - /** - * 设置模块名称 - */ - public function setModule($name = '') - { - $this->options['controller']['module'] = $name; - } } /** diff --git a/app/common/utils/scaffold/templates/ControllerStub.stub b/app/common/utils/scaffold/templates/ControllerStub.stub index e69de29..a74d97c 100644 --- a/app/common/utils/scaffold/templates/ControllerStub.stub +++ b/app/common/utils/scaffold/templates/ControllerStub.stub @@ -0,0 +1,33 @@ +service = new {{serviceName}}Service(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } +} \ No newline at end of file diff --git a/app/common/utils/scaffold/templates/LogicStub.stub b/app/common/utils/scaffold/templates/LogicStub.stub new file mode 100644 index 0000000..da1afdc --- /dev/null +++ b/app/common/utils/scaffold/templates/LogicStub.stub @@ -0,0 +1,17 @@ + [ - 'module' => 'commonName', - 'name' => 'ControllerName' + 'module' => 'admin', + 'name' => 'systems' ], 'model' => [ - 'module' => 'commonName', - 'name' => 'modelName' + 'module' => 'admin', + 'name' => 'systems' ], 'service' => [ - 'module' => 'commonName', - 'name' => 'serviceName' + 'module' => 'admin', + 'name' => 'systems' ], 'logic' => [ - 'module' => 'commonName', - 'name' => 'logicName' + 'module' => 'admin', + 'name' => 'systems' ], 'validate' => [ - 'module' => 'commonName', - 'name' => 'validateName' + 'module' => 'admin', + 'name' => 'systems' ], - 'table' => 'tableName', - 'fillable' => [] + 'table' => 'systems', + 'fillable' => [ + 'code', + 'name', + 'type', + 'desc', + 'icon', + 'domain_url', + 'is_role', + 'sort', + 'status' + ] ]; + + $result = (new Generate($options))->doGenerate(); + return $result; } } \ No newline at end of file diff --git a/composer.json b/composer.json index e4e63bb..9847f65 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,8 @@ "shopwwi/webman-filesystem": "1.1", "shopwwi/filesystem-oss": "^1.0", "webman/redis-queue": "^2.1", - "symfony/translation": "^7.3" + "symfony/translation": "^7.3", + "symfony/filesystem": "^7.3" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index b9b58c7..3736333 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "30cc3cbb162022972a21efed9b69d1e1", + "content-hash": "6c03076d4c7c86514f6b2d0073545bea", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -4284,6 +4284,72 @@ ], "time": "2024-09-25T14:21:43+00:00" }, + { + "name": "symfony/filesystem", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, { "name": "symfony/finder", "version": "v7.3.0", diff --git a/config/route.php b/config/route.php index e5bb7f2..d0e9290 100644 --- a/config/route.php +++ b/config/route.php @@ -26,6 +26,10 @@ use Webman\Route; // POST /admin/user/{user_id}/kickout_all // 一键踢全部 +Route::any('/c', [app\controller\IndexController::class, 'createResource']); +Route::any('/perm', [app\controller\IndexController::class, 'perm']); + + // 公共 Route::group('/common', function () { Route::group('/auth', function () { From 9d103a2c3401af11983023da9b3e1ea9a2500cc0 Mon Sep 17 00:00:00 2001 From: chenlan <18339827479@163.com> Date: Mon, 21 Jul 2025 19:39:49 +0800 Subject: [PATCH 069/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=94=9F=E6=88=90curd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/utils/scaffold/Generate.php | 35 ++++++++++--------- .../scaffold/templates/ValidateStub.stub | 2 +- app/controller/IndexController.php | 13 +------ 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/app/common/utils/scaffold/Generate.php b/app/common/utils/scaffold/Generate.php index 687780d..fee26b1 100644 --- a/app/common/utils/scaffold/Generate.php +++ b/app/common/utils/scaffold/Generate.php @@ -25,12 +25,12 @@ class Generate $fs = new Filesystem(); // 模板路径 - $templateDir = __DIR__ . '/../templates/'; - $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/{$this->options['controller']['name']}Controller.php"; - $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/{$this->options['service']['name']}Service.php"; - $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/service/{$this->options['logic']['name']}Service.php"; - $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/{$this->options['model']['name']}.php"; - $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/{$this->options['validate']['name']}Validate.php"; + $templateDir = __DIR__ . '/templates/'; + $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".ucfirst($this->options['controller']['name'])."Controller.php"; + $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".ucfirst($this->options['service']['name'])."Service.php"; + $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".ucfirst($this->options['logic']['name'])."Logic.php"; + $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".ucfirst($this->options['model']['name']).".php"; + $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".ucfirst($this->options['validate']['name'])."Validate.php"; // 创建目录(如果不存在) $fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller"); @@ -59,7 +59,7 @@ class Generate $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath, 'model'); // 生成验证器类文件 - $ValidatorStub = $this->generateFile($templateDir, 'ValidatorStub.stub', $validatePath, 'validate'); + $ValidatorStub = $this->generateFile($templateDir, 'ValidateStub.stub', $validatePath, 'validate'); return $ControllerStub . $ServiceStub . $LogicStub . $ModelStub . $ValidatorStub . "文件生成成功!
"; } @@ -107,31 +107,32 @@ class Generate case 'controller': $content = str_replace('{{controllerModule}}', $this->options['controller']['module'], $content); $content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content); - $content = str_replace('{{serviceName}}', $this->options['service']['name'], $content); - $content = str_replace('{{controllerName}}', $this->options['controller']['name'], $content); + $content = str_replace('{{serviceName}}', ucfirst($this->options['service']['name']), $content); + $content = str_replace('{{controllerName}}', ucfirst($this->options['controller']['name']), $content); break; case 'service': $content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content); - $content = str_replace('{{validateName}}', $this->options['validate']['name'], $content); + $content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content); + $content = str_replace('{{validateName}}', ucfirst($this->options['validate']['name']), $content); $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); - $content = str_replace('{{modelName}}', $this->options['model']['name'], $content); - $content = str_replace('{{serviceName}}', $this->options['service']['name'], $content); + $content = str_replace('{{modelName}}', ucfirst($this->options['model']['name']), $content); + $content = str_replace('{{serviceName}}', ucfirst($this->options['service']['name']), $content); break; case 'logic': $content = str_replace('{{logicModule}}', $this->options['logic']['module'], $content); $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); - $content = str_replace('{{modelName}}', $this->options['model']['name'], $content); - $content = str_replace('{{logicName}}', $this->options['logic']['name'], $content); + $content = str_replace('{{modelName}}', ucfirst($this->options['model']['name']), $content); + $content = str_replace('{{logicName}}', ucfirst($this->options['logic']['name']), $content); break; case 'model': $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); - $content = str_replace('{{modelName}}', $this->options['model']['name'], $content); + $content = str_replace('{{modelName}}', ucfirst($this->options['model']['name']), $content); $content = str_replace('{{table}}', $this->options['table'], $content); - $content = str_replace('{{fillable}}', $this->options['fillable'], $content); + $content = str_replace('{{fillable}}', $this->fillableFields(), $content); break; case 'validate': $content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content); - $content = str_replace('{{validateName}}', $this->options['validate']['name'], $content); + $content = str_replace('{{validateName}}', ucfirst($this->options['validate']['name']), $content); break; } diff --git a/app/common/utils/scaffold/templates/ValidateStub.stub b/app/common/utils/scaffold/templates/ValidateStub.stub index 01aa1b2..862b6f1 100644 --- a/app/common/utils/scaffold/templates/ValidateStub.stub +++ b/app/common/utils/scaffold/templates/ValidateStub.stub @@ -4,7 +4,7 @@ namespace app\{{validateModule}}\validate; use think\Validate; -class {{validateName}}Validator extends Validate +class {{validateName}}Validate extends Validate { // 规则 protected $rule = [ diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 53d6280..b694a23 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -90,18 +90,7 @@ class IndexController 'module' => 'admin', 'name' => 'systems' ], - 'table' => 'systems', - 'fillable' => [ - 'code', - 'name', - 'type', - 'desc', - 'icon', - 'domain_url', - 'is_role', - 'sort', - 'status' - ] + 'table' => 'systems' ]; $result = (new Generate($options))->doGenerate(); From 701389ddd8d0207d56ff3eaab445186e795c5f19 Mon Sep 17 00:00:00 2001 From: chenlan <18339827479@163.com> Date: Tue, 22 Jul 2025 09:32:18 +0800 Subject: [PATCH 070/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=A7=92=E8=89=B2&?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F&=E8=84=9A=E6=89=8B=E6=9E=B6=E5=86=99?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/SystemsController.php | 33 +++++++++ app/admin/logic/CorpLogic.php | 17 ----- app/admin/service/SystemsService.php | 31 ++++++++ app/common/logic/CorpLogic.php | 7 ++ app/common/logic/RoleLogic.php | 83 +--------------------- app/common/logic/SystemsLogic.php | 9 ++- app/common/model/Role.php | 8 +++ app/common/model/Systems.php | 37 +++------- app/common/utils/scaffold/Generate.php | 42 +++++------ app/common/validate/SystemsValidate.php | 29 ++++++++ app/controller/IndexController.php | 24 +++---- 11 files changed, 162 insertions(+), 158 deletions(-) create mode 100644 app/admin/controller/SystemsController.php delete mode 100644 app/admin/logic/CorpLogic.php create mode 100644 app/admin/service/SystemsService.php create mode 100644 app/common/validate/SystemsValidate.php diff --git a/app/admin/controller/SystemsController.php b/app/admin/controller/SystemsController.php new file mode 100644 index 0000000..9c300ac --- /dev/null +++ b/app/admin/controller/SystemsController.php @@ -0,0 +1,33 @@ +service = new SystemsService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } +} \ No newline at end of file diff --git a/app/admin/logic/CorpLogic.php b/app/admin/logic/CorpLogic.php deleted file mode 100644 index 99fa0ee..0000000 --- a/app/admin/logic/CorpLogic.php +++ /dev/null @@ -1,17 +0,0 @@ -validator; + } +} \ No newline at end of file diff --git a/app/common/logic/CorpLogic.php b/app/common/logic/CorpLogic.php index cac9672..60e46e6 100644 --- a/app/common/logic/CorpLogic.php +++ b/app/common/logic/CorpLogic.php @@ -2,10 +2,17 @@ namespace app\common\logic; +use app\common\traits\crud\CrudLogicTrait; use app\common\model\Corp; class CorpLogic { + use CrudLogicTrait; + + /** + * 指定当前要操作的模型 + */ + protected string $modelClass = Corp::class; public function getList() { diff --git a/app/common/logic/RoleLogic.php b/app/common/logic/RoleLogic.php index 5cb3ae7..bd8f121 100644 --- a/app/common/logic/RoleLogic.php +++ b/app/common/logic/RoleLogic.php @@ -8,86 +8,7 @@ use support\Log; class RoleLogic { - /** - * 获取角色列表 - */ - public function getRoleList(array $params) - { - // 假设我们需要根据system_id进行过滤,其他过滤条件也可以添加 - $query = Role::query(); + - if (isset($params['system_id'])) { - $query->where('system_id', $params['system_id']); - } - if (isset($params['status'])) { - $query->where('status', $params['status']); - } - - // 添加分页 - $roles = $query->paginate($params['per_page'] ?? 15); - - return $roles; - } - - /** - * 获取角色详情 - */ - public function getRoleDetail(int $roleId) - { - $role = Role::find($roleId); - if (!$role) { - throw new BusinessException('角色不存在', 404); - } - - return $role; - } - - /** - * 创建角色 - */ - public function createRole(array $data) - { - $role = new Role(); - $role->fill($data); // 根据字段填充数据 - try { - $role->save(); - } catch (\Throwable $e) { - Log::error('创建角色失败:'.$e->getMessage()); - throw new BusinessException('创建角色失败', 500); - } - - return $role; - } - - /** - * 更新角色 - */ - public function updateRole(array $data) - { - $role = Role::find($data['id']); - if (!$role) { - throw new BusinessException('角色不存在', 404); - } - - $role->update($data); // 更新字段 - return $role; - } - - /** - * 删除角色 - */ - public function deleteRole(int $roleId) - { - $role = Role::find($roleId); - if (!$role) { - throw new BusinessException('角色不存在', 404); - } - - try { - $role->delete(); - } catch (\Throwable $e) { - Log::error('删除角色失败:'.$e->getMessage()); - throw new BusinessException('删除角色失败', 500); - } - } + } diff --git a/app/common/logic/SystemsLogic.php b/app/common/logic/SystemsLogic.php index 5411264..93e40d0 100644 --- a/app/common/logic/SystemsLogic.php +++ b/app/common/logic/SystemsLogic.php @@ -2,13 +2,20 @@ namespace app\common\logic; +use app\common\traits\crud\CrudLogicTrait; use app\common\model\Systems; class SystemsLogic { + use CrudLogicTrait; + + /** + * 指定当前要操作的模型 + */ + protected string $modelClass = Systems::class; public function getList() { - return Systems::where('status', 1)->orderBy('sort', 'asc')->get(['id', 'name', 'code', 'icon', 'type']); + return Systems::where('status', 1)->orderBy('sort', 'asc')->get(['id', 'name', 'code', 'type', 'type']); } } \ No newline at end of file diff --git a/app/common/model/Role.php b/app/common/model/Role.php index ea84200..c758321 100644 --- a/app/common/model/Role.php +++ b/app/common/model/Role.php @@ -43,6 +43,14 @@ class Role extends Model public $timestamps = true; protected $dateFormat = 'U'; + + protected $fillable = [ + 'system_id', + 'name', + 'desc', + 'sort', + 'status' + ]; // 角色下所有用户 public function users() diff --git a/app/common/model/Systems.php b/app/common/model/Systems.php index f9181ec..ddb180e 100644 --- a/app/common/model/Systems.php +++ b/app/common/model/Systems.php @@ -4,21 +4,6 @@ namespace app\common\model; use support\Model; -/** - * systems 系统/应用表 - * @property integer $id 系统ID(主键) - * @property string $code 系统编码(唯一英文标识) - * @property string $name 系统名称 - * @property string $type 系统类型 - * @property string $desc 系统描述 - * @property string $icon 系统图标 - * @property string $domain_url 系统URL - * @property string $is_role 是否区分角色权限 - * @property integer $sort 排序 - * @property integer $status 状态 - * @property integer $created_at 创建时间 - * @property integer $updated_at 更新时间 - */ class Systems extends Model { /** @@ -50,15 +35,15 @@ class Systems extends Model public $timestamps = true; - // 系统下的资源 - public function resources() - { - return $this->hasMany(Resource::class, 'system_id', 'id'); - } + protected $dateFormat = 'U'; - // 拥有该系统访问权限的用户 - public function users() - { - return $this->belongsToMany(User::class, 'user_systems', 'system_id', 'user_id'); - } -} + /** + * 允许自动填充模型字段 + */ + protected $fillable = ['id', 'code', 'name', 'type', 'desc', 'icon', 'domain_url', 'is_role', 'sort', 'status', 'created_at', 'updated_at']; + + /** + * 不允许自动填充模型字段 + */ + protected $guarded = []; +} \ No newline at end of file diff --git a/app/common/utils/scaffold/Generate.php b/app/common/utils/scaffold/Generate.php index fee26b1..7cf52ac 100644 --- a/app/common/utils/scaffold/Generate.php +++ b/app/common/utils/scaffold/Generate.php @@ -64,27 +64,6 @@ class Generate return $ControllerStub . $ServiceStub . $LogicStub . $ModelStub . $ValidatorStub . "文件生成成功!
"; } - /** - * 设置字段 - * - * @param array $fields 字段数组 - */ - public function fillableFields() - { - $tableName = $this->options['table']; - $fields = TableInspector::getColumnNames($tableName); - $str = ''; - foreach ($fields as $key => $val) { - if ($key < (count($fields)-1)) { - $str .= "'{$val}', "; - } else { - $str .= "'{$val}'"; - } - } - $fields = trim($str, '"'); - return $fields; - } - /** * 根据模板生成文件 * @@ -141,6 +120,27 @@ class Generate return "生成文件: {$outputPath}
"; } + /** + * 设置字段 + * + * @param array $fields 字段数组 + */ + public function fillableFields() + { + $tableName = $this->options['table']; + $fields = TableInspector::getColumnNames($tableName); + $str = ''; + foreach ($fields as $key => $val) { + if ($key < (count($fields)-1)) { + $str .= "'{$val}', "; + } else { + $str .= "'{$val}'"; + } + } + $fields = trim($str, '"'); + return $fields; + } + /** * 如果文件存在,删除它 * diff --git a/app/common/validate/SystemsValidate.php b/app/common/validate/SystemsValidate.php new file mode 100644 index 0000000..d0783f5 --- /dev/null +++ b/app/common/validate/SystemsValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index b694a23..125ff1f 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -22,18 +22,17 @@ class IndexController return htmlpage(); } + /** + * 调试飞书 + */ public function view(Request $request) { return view('index/qrcode', ['name' => 'webman']); } - public function json(Request $request) - { - return json(['code' => 0, 'msg' => 'ok']); - } - - - // 临时增加用户权限 + /** + * 临时增加用户权限 + */ public function perm(Request $request) { try { @@ -61,12 +60,13 @@ class IndexController Cache::delete("user_resources_{$data['user_id']}"); return json(['code' => 0, 'msg' => 'success']); } catch (\Throwable $e) { - var_dump($e); return json(['code' => 1, 'msg' => $e->getMessage()]); } } - + /** + * 生成资源 + */ public function createResource() { $options = [ @@ -75,7 +75,7 @@ class IndexController 'name' => 'systems' ], 'model' => [ - 'module' => 'admin', + 'module' => 'common', 'name' => 'systems' ], 'service' => [ @@ -83,11 +83,11 @@ class IndexController 'name' => 'systems' ], 'logic' => [ - 'module' => 'admin', + 'module' => 'common', 'name' => 'systems' ], 'validate' => [ - 'module' => 'admin', + 'module' => 'common', 'name' => 'systems' ], 'table' => 'systems' From f9cafed07c534e48e03e2a78487fc974558df897 Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 09:52:06 +0800 Subject: [PATCH 071/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index 16b7feb..b6a2fc9 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -153,7 +153,7 @@ class PermLogic // —— 递归构建子节点 —— $children = []; - if ($node->type === 'directory') { + if (in_array($node->type, ['directory', 'menu'])) { $children = $this->buildTree($resources, $node->id, $currentPath); } From d00bac470360e9c9c4c24441eb201282ebd0bfec Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 10:21:41 +0800 Subject: [PATCH 072/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 75 +++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index b6a2fc9..275912b 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -54,55 +54,70 @@ class PermLogic /** - * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单component处理 - * @param \Illuminate\Support\Collection $resources - * @param int $parentId - * @param string $parentPath + * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单 component 处理 + * + * @param \Illuminate\Support\Collection $resources 全部资源集合 + * @param int $parentId 当前父级资源 ID + * @param string $parentPath 父级完整路径(用于递归) * @return array */ - public function buildTree_bak($resources, int $parentId = 0, string $parentPath = ''): array + public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array { $tree = []; + foreach ($resources as $node) { - if ($node->parent_id !== $parentId) continue; - - // 拼接完整路径 - $currentPath = $parentPath; - if ($currentPath && substr($currentPath, -1) === '/') { - $currentPath = rtrim($currentPath, '/'); + if ($node->parent_id !== $parentId) { + continue; } + + // —— 拼接当前节点的完整路径 —— + $currentPath = rtrim($parentPath, '/'); + if (!empty($node->url)) { - if (!$currentPath) { - $currentPath = $node->url; - } else { - $url = ltrim($node->url, '/'); - $currentPath = $currentPath . '/' . $url; - } + $urlSegment = ltrim($node->url, '/'); + $currentPath = $currentPath === '' + ? $urlSegment + : "{$currentPath}/{$urlSegment}"; } - // 子节点递归 + // —— 递归构建子节点 —— $children = []; - if ($node->type === 'directory') { + if (in_array($node->type, ['directory', 'menu'])) { $children = $this->buildTree($resources, $node->id, $currentPath); } - // 权限 + // —— 本级权限列表 —— $authList = $resources ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) ->map(fn($r) => [ 'id' => $r->id, 'title' => $r->name, 'auth_mark' => $r->code, - ])->values()->all(); + ]) + ->values() + ->all(); - // 判断是否有子级菜单(非权限) - $hasChildrenMenu = collect($resources)->where('parent_id', $node->id) - ->whereIn('type', ['directory', 'menu'])->isNotEmpty(); + // —— 判断是否有子目录或子菜单 —— + $hasChildrenMenu = $resources + ->where('parent_id', $node->id) + ->whereIn('type', ['directory', 'menu']) + ->isNotEmpty(); - // component 字段规则: - // 1. 存在子菜单,固定 '/index/index' - // 2. 否则为当前完整路径(或其它你想要的) - $componentValue = ($hasChildrenMenu && !$node->url && $node->type !== 'directory') ? '/index/index' : $currentPath; + // —— 计算 component 字段 —— + if ($hasChildrenMenu) { + if ($node->type === 'menu' && !empty($node->url)) { + // menu 类型且自身有 url,用自身 url(加前导 slash) + $componentValue = '/' . ltrim($node->url, '/'); + } else { + // 其它含子项的节点,固定 Layout 组件 + $componentValue = '/index/index'; + } + } else { + // 叶子节点:拼接路径,确保以 slash 开头 + $componentValue = $currentPath !== '' + ? '/' . ltrim($currentPath, '/') + : ''; + } $tree[] = [ 'id' => $node->id, @@ -119,8 +134,10 @@ class PermLogic 'children' => $children, ]; } + return $tree; } + /** * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单 component 处理 * @@ -129,7 +146,7 @@ class PermLogic * @param string $parentPath 父级完整路径(用于递归) * @return array */ - public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array + public function buildTree_bak($resources, int $parentId = 0, string $parentPath = ''): array { $tree = []; From 58aadbc1f5f7d369ccbd16b6b934e113391c0c16 Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 10:41:38 +0800 Subject: [PATCH 073/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index 275912b..b1ea2f3 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -52,7 +52,6 @@ class PermLogic return $all->unique('id')->values()->sortBy('sort'); } - /** * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单 component 处理 * @@ -66,13 +65,13 @@ class PermLogic $tree = []; foreach ($resources as $node) { - if ($node->parent_id !== $parentId) { + // 只处理目录和菜单,不把 permission 当作菜单节点 + if ($node->parent_id !== $parentId || $node->type === 'permission') { continue; } // —— 拼接当前节点的完整路径 —— $currentPath = rtrim($parentPath, '/'); - if (!empty($node->url)) { $urlSegment = ltrim($node->url, '/'); $currentPath = $currentPath === '' @@ -81,10 +80,8 @@ class PermLogic } // —— 递归构建子节点 —— - $children = []; - if (in_array($node->type, ['directory', 'menu'])) { - $children = $this->buildTree($resources, $node->id, $currentPath); - } + // 这里只会继续收目录和菜单,permission 永远不会进入此方法 + $children = $this->buildTree($resources, $node->id, $currentPath); // —— 本级权限列表 —— $authList = $resources @@ -98,10 +95,7 @@ class PermLogic ->all(); // —— 判断是否有子目录或子菜单 —— - $hasChildrenMenu = $resources - ->where('parent_id', $node->id) - ->whereIn('type', ['directory', 'menu']) - ->isNotEmpty(); + $hasChildrenMenu = ! empty($children); // —— 计算 component 字段 —— if ($hasChildrenMenu) { From 5e75822e5626c811879759360d1cc01342dcc34f Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 10:48:09 +0800 Subject: [PATCH 074/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index b1ea2f3..a915ea1 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -126,6 +126,8 @@ class PermLogic 'openType' => $node->open_type, ], 'children' => $children, + 'is_hide' => $node->is_hide, + 'is_hide_tab' => $node->is_hide_tab ]; } From d532b85b910d00b9bdf77ad866b4044f830b8e37 Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 10:57:37 +0800 Subject: [PATCH 075/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index a915ea1..1fb4058 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -124,10 +124,10 @@ class PermLogic 'keepAlive' => true, 'authList' => $authList, 'openType' => $node->open_type, + 'is_hide' => $node->is_hide == 0 ? false : true, + 'is_hide_tab' => $node->is_hide_tab == 0 ? false : true ], - 'children' => $children, - 'is_hide' => $node->is_hide, - 'is_hide_tab' => $node->is_hide_tab + 'children' => $children ]; } From abafe841f82172c3f1e8b7b8c157ffbc9b9c9b08 Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 11:00:32 +0800 Subject: [PATCH 076/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index 1fb4058..515bec9 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -124,8 +124,8 @@ class PermLogic 'keepAlive' => true, 'authList' => $authList, 'openType' => $node->open_type, - 'is_hide' => $node->is_hide == 0 ? false : true, - 'is_hide_tab' => $node->is_hide_tab == 0 ? false : true + 'isHide' => $node->is_hide == 0 ? false : true, + 'isHideTab' => $node->is_hide_tab == 0 ? false : true ], 'children' => $children ]; From 4eca8c46c5e07ec2d1d928779f06536b7a0a0836 Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 11:34:47 +0800 Subject: [PATCH 077/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/utils/scaffold/Generate.php | 36 ++++++++++++++++---------- app/controller/IndexController.php | 22 ++++++++-------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/app/common/utils/scaffold/Generate.php b/app/common/utils/scaffold/Generate.php index 7cf52ac..6735997 100644 --- a/app/common/utils/scaffold/Generate.php +++ b/app/common/utils/scaffold/Generate.php @@ -26,11 +26,11 @@ class Generate // 模板路径 $templateDir = __DIR__ . '/templates/'; - $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".ucfirst($this->options['controller']['name'])."Controller.php"; - $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".ucfirst($this->options['service']['name'])."Service.php"; - $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".ucfirst($this->options['logic']['name'])."Logic.php"; - $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".ucfirst($this->options['model']['name']).".php"; - $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".ucfirst($this->options['validate']['name'])."Validate.php"; + $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".$this->rName($this->options['controller']['name'])."Controller.php"; + $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".$this->rName($this->options['service']['name'])."Service.php"; + $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".$this->rName($this->options['logic']['name'])."Logic.php"; + $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".$this->rName($this->options['model']['name']).".php"; + $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".$this->rName($this->options['validate']['name'])."Validate.php"; // 创建目录(如果不存在) $fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller"); @@ -86,32 +86,32 @@ class Generate case 'controller': $content = str_replace('{{controllerModule}}', $this->options['controller']['module'], $content); $content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content); - $content = str_replace('{{serviceName}}', ucfirst($this->options['service']['name']), $content); - $content = str_replace('{{controllerName}}', ucfirst($this->options['controller']['name']), $content); + $content = str_replace('{{serviceName}}', $this->rName($this->options['service']['name']), $content); + $content = str_replace('{{controllerName}}', $this->rName($this->options['controller']['name']), $content); break; case 'service': $content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content); $content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content); - $content = str_replace('{{validateName}}', ucfirst($this->options['validate']['name']), $content); + $content = str_replace('{{validateName}}', $this->rName($this->options['validate']['name']), $content); $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); - $content = str_replace('{{modelName}}', ucfirst($this->options['model']['name']), $content); - $content = str_replace('{{serviceName}}', ucfirst($this->options['service']['name']), $content); + $content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content); + $content = str_replace('{{serviceName}}', $this->rName($this->options['service']['name']), $content); break; case 'logic': $content = str_replace('{{logicModule}}', $this->options['logic']['module'], $content); $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); - $content = str_replace('{{modelName}}', ucfirst($this->options['model']['name']), $content); - $content = str_replace('{{logicName}}', ucfirst($this->options['logic']['name']), $content); + $content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content); + $content = str_replace('{{logicName}}', $this->rName($this->options['logic']['name']), $content); break; case 'model': $content = str_replace('{{modelModule}}', $this->options['model']['module'], $content); - $content = str_replace('{{modelName}}', ucfirst($this->options['model']['name']), $content); + $content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content); $content = str_replace('{{table}}', $this->options['table'], $content); $content = str_replace('{{fillable}}', $this->fillableFields(), $content); break; case 'validate': $content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content); - $content = str_replace('{{validateName}}', ucfirst($this->options['validate']['name']), $content); + $content = str_replace('{{validateName}}', $this->rName($this->options['validate']['name']), $content); break; } @@ -153,6 +153,14 @@ class Generate echo "删除已存在文件: {$path} \n"; } } + + /** + * 设置名称 + */ + public function rName($name = '') + { + return ucfirst(camelize($name)); + } } /** diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 125ff1f..6ca6eae 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -71,26 +71,26 @@ class IndexController { $options = [ 'controller' => [ - 'module' => 'admin', - 'name' => 'systems' + 'module' => 'inquiry', + 'name' => 'inquiry_log' ], 'model' => [ - 'module' => 'common', - 'name' => 'systems' + 'module' => 'inquiry', + 'name' => 'inquiry_log' ], 'service' => [ - 'module' => 'admin', - 'name' => 'systems' + 'module' => 'inquiry', + 'name' => 'inquiry_log' ], 'logic' => [ - 'module' => 'common', - 'name' => 'systems' + 'module' => 'inquiry', + 'name' => 'inquiry_log' ], 'validate' => [ - 'module' => 'common', - 'name' => 'systems' + 'module' => 'inquiry', + 'name' => 'inquiry_log' ], - 'table' => 'systems' + 'table' => 'inquiry_log' ]; $result = (new Generate($options))->doGenerate(); From e5ab965e656950953a2245bc9136c1b734dcc296 Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 15:13:27 +0800 Subject: [PATCH 078/120] =?UTF-8?q?=E8=AF=A2=E5=8D=95=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/model/Country.php | 49 ++ app/common/model/InquiryLog.php | 82 +++ app/common/utils/CrudUtil.php | 108 +-- app/common/validate/InquiryLogValidate.php | 29 + app/controller/IndexController.php | 12 +- app/inquiry/controller/CountryController.php | 33 + .../controller/InquiryLogController.php | 694 ++++++++++++++++++ app/inquiry/logic/CountryLogic.php | 17 + app/inquiry/logic/InquiryLogLogic.php | 17 + app/inquiry/service/CountryService.php | 31 + app/inquiry/service/InquiryLogService.php | 103 +++ config/route.php | 9 + 12 files changed, 1103 insertions(+), 81 deletions(-) create mode 100644 app/common/model/Country.php create mode 100644 app/common/model/InquiryLog.php create mode 100644 app/common/validate/InquiryLogValidate.php create mode 100644 app/inquiry/controller/CountryController.php create mode 100644 app/inquiry/controller/InquiryLogController.php create mode 100644 app/inquiry/logic/CountryLogic.php create mode 100644 app/inquiry/logic/InquiryLogLogic.php create mode 100644 app/inquiry/service/CountryService.php create mode 100644 app/inquiry/service/InquiryLogService.php diff --git a/app/common/model/Country.php b/app/common/model/Country.php new file mode 100644 index 0000000..80439a4 --- /dev/null +++ b/app/common/model/Country.php @@ -0,0 +1,49 @@ +hasOne(User::class, 'id', 'enter_user_id'); + } + + public function recipient() + { + return $this->hasOne(User::class, 'id', 'recipient'); + } + + // public function enter() + // { + // return $this->belongsTo(User::class, 'enter_user_id'); + // } + + // public function recipient() + // { + // return $this->belongsTo(User::class, 'recipient'); + // } + + public function country() + { + return $this->hasOne(Country::class, 'id', 'country'); + } +} \ No newline at end of file diff --git a/app/common/utils/CrudUtil.php b/app/common/utils/CrudUtil.php index 58e8302..22b5202 100644 --- a/app/common/utils/CrudUtil.php +++ b/app/common/utils/CrudUtil.php @@ -152,43 +152,22 @@ class CrudUtil * @return Model * @throws BusinessException */ - // public static function findByPk(string $modelClass, $id, array $with = []): Model - // { - // $m = $modelClass::with($with)->find($id); - // if (!$m) { - // throw new BusinessException("记录 ID={$id} 不存在", 404); - // } - // return $m; - // } + public static function findByPk(string $modelClass, $id, array $with = []): Model + { + $m = $modelClass::with($with)->find($id); + if (!$m) { + throw new BusinessException("记录 ID={$id} 不存在", 404); + } + return $m; + } /** * 任意条件单条查询,支持 dataLimit * - * @param string $modelClass - * @param array $where - * @param array $with - * @param array $dataLimit + * @param Builder $query * @return Model * @throws BusinessException */ - // public static function findOneBy( - // string $modelClass, - // array $where, - // array $with = [], - // array $dataLimit = [] - // ): Model { - // $config = compact('modelClass'); - // $config['model'] = $modelClass; - // $config['where'] = $where; - // $config['with'] = $with; - // $config['dataLimit'] = $dataLimit; - - // $m = self::buildQuery($config)->first(); - // if (!$m) { - // throw new BusinessException("未找到符合条件的记录", 404); - // } - // return $m; - // } public static function findOneBy(Builder $q): Model { return $q->first(); @@ -225,43 +204,32 @@ class CrudUtil * @return Model * @throws BusinessException */ - // public static function updateByPk( - // string $modelClass, - // $id, - // array $data, - // array $dataLimit = [] - // ): Model { - // /** @var Model $m */ - // $m = $modelClass::find($id); - // if (!$m) { - // throw new BusinessException("记录 ID={$id} 不存在", 404); - // } - // // dataLimit 校验 - // if (!empty($dataLimit)) { - // self::applyDataLimitOnMutate($m, $dataLimit, $data); - // } - // $m->fill($data)->save(); - // return $m; - // } + public static function updateByPk( + string $modelClass, + $id, + array $data, + array $dataLimit = [] + ): Model { + /** @var Model $m */ + $m = $modelClass::find($id); + if (!$m) { + throw new BusinessException("记录 ID={$id} 不存在", 404); + } + // dataLimit 校验 + if (!empty($dataLimit)) { + self::applyDataLimitOnMutate($m, $dataLimit, $data); + } + $m->fill($data)->save(); + return $m; + } /** * 非主键条件更新(批量),支持 dataLimit * - * @param string $modelClass - * @param array $where + * @param Builder $query * @param array $data - * @param array $dataLimit * @return int 受影响行数 */ - // public static function updateBy( - // string $modelClass, - // array $where, - // array $data, - // array $dataLimit = [] - // ): int { - // $config = ['model'=>$modelClass, 'where'=>$where, 'dataLimit'=>$dataLimit]; - // return self::buildQuery($config)->update($data); - // } public static function updateBy(Builder $q, array $data): int { return $q->update($data); @@ -274,27 +242,17 @@ class CrudUtil * @param array $ids * @return int 删除条数 */ - // public static function deleteByPk(string $modelClass, array $ids): int - // { - // return $modelClass::destroy($ids); - // } + public static function deleteByPk(string $modelClass, array $ids): int + { + return $modelClass::destroy($ids); + } /** * 非主键条件删除,支持 dataLimit * - * @param string $modelClass - * @param array $where - * @param array $dataLimit + * @param Builder $query * @return int 删除条数 */ - // public static function deleteBy( - // string $modelClass, - // array $where, - // array $dataLimit = [] - // ): int { - // $config = ['model'=>$modelClass, 'where'=>$where, 'dataLimit'=>$dataLimit]; - // return self::buildQuery($config)->delete(); - // } public static function deleteBy(Builder $q): int { return $q->delete(); diff --git a/app/common/validate/InquiryLogValidate.php b/app/common/validate/InquiryLogValidate.php new file mode 100644 index 0000000..ca266b1 --- /dev/null +++ b/app/common/validate/InquiryLogValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 6ca6eae..50a3280 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -72,25 +72,25 @@ class IndexController $options = [ 'controller' => [ 'module' => 'inquiry', - 'name' => 'inquiry_log' + 'name' => 'country' ], 'model' => [ 'module' => 'inquiry', - 'name' => 'inquiry_log' + 'name' => 'country' ], 'service' => [ 'module' => 'inquiry', - 'name' => 'inquiry_log' + 'name' => 'country' ], 'logic' => [ 'module' => 'inquiry', - 'name' => 'inquiry_log' + 'name' => 'country' ], 'validate' => [ 'module' => 'inquiry', - 'name' => 'inquiry_log' + 'name' => 'country' ], - 'table' => 'inquiry_log' + 'table' => 'country' ]; $result = (new Generate($options))->doGenerate(); diff --git a/app/inquiry/controller/CountryController.php b/app/inquiry/controller/CountryController.php new file mode 100644 index 0000000..bb709d4 --- /dev/null +++ b/app/inquiry/controller/CountryController.php @@ -0,0 +1,33 @@ +service = new CountryService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } +} \ No newline at end of file diff --git a/app/inquiry/controller/InquiryLogController.php b/app/inquiry/controller/InquiryLogController.php new file mode 100644 index 0000000..9b48395 --- /dev/null +++ b/app/inquiry/controller/InquiryLogController.php @@ -0,0 +1,694 @@ +service = new InquiryLogService(); + } + + /** + * 列表页数据格式化 + * + * @param array $data + * @return array + */ + // protected function indexFormat($data) + // { + // if (isset($data['created_at'])) + // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + // if (isset($data['updated_at'])) + // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + // return $data; + // } + + + // 列表 + public function list(Request $request) + { + if ($request->method() == 'POST') { + $data = $request->all(); + $limit = isset($data['limit']) && $data['limit'] ? $data['limit'] : 10; + + // 状态 + $roleId = $request->user->role_id; + $where = []; + if (in_array($roleId, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::KEFU_ROLE_ID])) { + if (isset($data["status"]) && $data["status"] != "") { + $where[] = [ + "status", "=", $data["status"] + ]; + } else { + $where[] = [ + "status", ">", -1 + ]; + } + } else { + $where[] = [ + "status", "=", 1 + ]; + } + + // 检索渠道 + if (isset($data["channel"]) && $data["channel"]) { + $where[] = [ + "log_type", "=", $data["channel"] + ]; + } + + // 检索国家 + if (isset($data["country"]) && $data["country"]) { + $where[] = [ + "log_country", "=", $data["country"] + ]; + } + + // 检索时间 + if (isset($data["ld_time"]) && $data["ld_time"]) { + $times = explode(' - ', $data["ld_time"]); + $specificDayStart = trim($times[0]); + $specificDayEnd = trim($times[1]); + $where[] = [ + "c_time", ">", strtotime($specificDayStart) + ]; + $where[] = [ + "c_time", "<=", strtotime($specificDayEnd) + ]; + } + + // 检查渠道(不取缓存中的值) + // $channel = $request->user->channel; + $uinfos = UsersModel::where('id', $request->user->uid)->first(); + $channel = $uinfos->channel; + if ($channel) { + $channel = explode(",", $channel); + $InquiryLogModelModel = InquiryLogModel::where($where)->whereIn("log_type", $channel); + } else { + $InquiryLogModelModel = InquiryLogModel::where($where); + } + + // 关键字 + if (isset($data["kwd"]) && $data["kwd"] != "") { + $InquiryLogModelModel->whereRaw("concat(`log_contact_type`,`log_contact_tel`,`log_contact_whatsapp`,`log_contacts`,`order_no`) like '%".$data['kwd']."%'"); + } + + $list = $InquiryLogModelModel + ->with([ + "eUserInfo"=>function($query) { + $query->where("id", ">", 0); + }, "rUserInfo"=>function($query) { + $query->where("id", ">", 0); + }, "countryInfo" + ]) + ->orderBy("id", "desc") + ->paginate($limit); + + $configsModel = new ConfigsModel(); + $logLevel = $configsModel->getConfig("log_level"); + $countdown = (int)($configsModel->getConfig("score_countdown")) * 60; + $inquiryChannelList = ChannelModel::all(['id', 'name']); + $inquiryChannel = []; + foreach ($inquiryChannelList as $key => $val) { + $inquiryChannel[$val->id] = $val->name; + } + + $arr = []; + foreach ($list->items() as $key => $val) { + $expiry_time = $val->r_time + $countdown; + $is_expiry = 0; + $expiry_ts = 0; + if ($expiry_time <= time()) { + $is_expiry = 1; + } else { + $diff = $expiry_time - (int)time(); + $expiry_ts = $diff * 1000; + } + $rUinfo = ""; + if ($val->rUserInfo) { + $rUinfo = $val->rUserInfo->name; + } + $eUinfo = ""; + if ($val->eUserInfo) { + $eUinfo = $val->eUserInfo->name; + } + + /** + * 显示联系方式条件: + * 1.已跟单并且当前用户是接单人 + * 2.主权限 + * 3.当前用户是录单人 + */ + if ( + ($val->status == 2 && $request->user->uid == $val->recipient) || + ($roleId == RoleRulesModel::ALL_ROLE_ID) || + ($request->user->uid == $val->enter_user_id) + ) { + $log_contact_type = $val->log_contact_type == "" ? " 无 " : $val->log_contact_type; + $log_contact_tel = $val->log_contact_tel == "" ? " 无 " : $val->log_contact_tel; + $log_contact_whatsapp = $val->log_contact_whatsapp == "" ? " 无 " : $val->log_contact_whatsapp; + } else { + $log_contact_type = "******"; + $log_contact_tel = "******"; + $log_contact_whatsapp = "******"; + } + $arr[] = [ + "id" => $val->id, + "order_no" => $val->order_no, + "enter_user_id" => $val->enter_user_id, + "recipient" => $val->recipient, + "log_quality" => $val->log_quality, + "log_quality_desc" => $logLevel[$val->log_quality], + "log_quality_color" => InquiryLogModel::LEVEL_BUTTON_COLOR[$val->log_quality], + "log_type" => $val->log_type, + "log_type_desc" => $inquiryChannel[$val->log_type], + "log_contact_type" => $log_contact_type, + "log_contact_tel" => $log_contact_tel, + "log_contact_whatsapp" => $log_contact_whatsapp, + "log_country" => $val->countryInfo->cn_addr."(".$val->countryInfo->two_letter.")", + "log_country_steering" => $val->countryInfo->steering, + "log_contacts" => $val->log_contacts, + "log_content" => $val->log_content, + "e_time" => $val->status==1?date("Y-m-d H:i:s", $expiry_time):"", + "status" => $val->status, + "status_color" => InquiryLogModel::STATUS_BUTTON_COLOR[$val->status], + "status_desc" => InquiryLogModel::STATUS[$val->status], + "is_expiry" => $is_expiry, + "expiry_ts" => $expiry_ts, + "rUinfo" => $rUinfo, + "eUinfo" => $eUinfo, + "r_time" => $val->r_time?date("Y-m-d H:i:s", $val->r_time):"", + "n_time" => date("Y-m-d H:i:s", time()), + "c_time" => date("Y-m-d H:i:s", strtotime($val->c_time)), + "u_time" => date("Y-m-d H:i:s", strtotime($val->u_time)) + ]; + } + $data = [ + 'lastPage' => $list->lastPage(), + 'currentPage' => $list->currentPage(), + 'hasMorePages' => $list->hasMorePages(), + 'list' => $arr, + 'counts' => $list->total() + ]; + return $this->successResponse($data); + } + return view('inquiry-log/list'); + } + + // 添加 + public function add(Request $request) + { + if ($request->method() == "POST") { + $enterUserId = $request->user->uid; + $data = $request->all(); + + if ($data["log_contact_type"]) { + $check1 = InquiryLogModel::where("log_contact_type", trim($data["log_contact_type"]))->first(); + if ($check1 != NULL) { + return $this->errorResponse('邮箱-联系方式已存在'); + } + } + + if ($data["log_contact_tel"]) { + $check2 = InquiryLogModel::where("log_contact_tel", trim($data["log_contact_tel"]))->first(); + if ($check2 != NULL) { + return $this->errorResponse('电话-联系方式已存在'); + } + } + + if ($data["log_contact_whatsapp"]) { + $check3 = InquiryLogModel::where("log_contact_whatsapp", trim($data["log_contact_whatsapp"]))->first(); + if ($check3 != NULL) { + return $this->errorResponse('whatsapp-联系方式已存在'); + } + } + + if (!$data["log_contact_type"] && !$data["log_contact_tel"] && !$data["log_contact_whatsapp"]) { + $check4 = InquiryLogModel::where("log_contacts", trim($data["log_contacts"]))->where("log_country", $data["country"])->first(); + if ($check4 != NULL) { + return $this->errorResponse('联系人已存在'); + } + } + + $model = new InquiryLogModel(); + $model->order_no = orderNum(); + $model->enter_user_id = $enterUserId; + $model->recipient = 0; + $model->log_type = $data["log_type"]; + $model->log_contact_type = trim($data["log_contact_type"]); + $model->log_contact_tel = trim($data["log_contact_tel"]); + $model->log_contact_whatsapp = trim($data["log_contact_whatsapp"]); + $model->log_country = $data["country"]; + $model->log_contacts = $data["log_contacts"]; + $model->log_content = $data["log_content"]; + $model->log_quality = $data["log_quality"]; + $model->status = 0; + if ($model->save()) { + return $this->successResponse([], '添加成功'); + } else { + return $this->errorResponse('添加失败,请重试'); + } + } + return view('inquiry-log/add'); + } + + // 编辑 + public function edit(Request $request) + { + if ($request->method() == "POST") { + $data = $request->all(); + $model = new InquiryLogModel(); + $info = $model->where("id", $data["id"])->first(); + + if ($data["log_contact_type"]) { + $check1 = InquiryLogModel::where("log_contact_type", trim($data["log_contact_type"]))->first(); + if ($check1 != NULL && $check1->id != $data["id"]) { + return $this->errorResponse('邮箱-联系方式已存在'); + } + } + + if ($data["log_contact_tel"]) { + $check2 = InquiryLogModel::where("log_contact_tel", trim($data["log_contact_tel"]))->first(); + if ($check2 != NULL && $check2->id != $data["id"]) { + return $this->errorResponse('电话-联系方式已存在'); + } + } + + if ($data["log_contact_whatsapp"]) { + $check3 = InquiryLogModel::where("log_contact_whatsapp", trim($data["log_contact_whatsapp"]))->first(); + if ($check3 != NULL && $check3->id != $data["id"]) { + return $this->errorResponse('whatsapp-联系方式已存在'); + } + } + + if (!$data["log_contact_type"] && !$data["log_contact_tel"] && !$data["log_contact_whatsapp"]) { + $check4 = InquiryLogModel::where("log_contacts", trim($data["log_contacts"]))->where("log_country", $data["country"])->first(); + if ($check4 != NULL && $check4->id != $data["id"]) { + return $this->errorResponse('联系人已存在'); + } + } + + $info->log_type = $data["log_type"]; + $info->log_contact_type = $data["log_contact_type"]; + $info->log_contact_tel = $data["log_contact_tel"]; + $info->log_contact_whatsapp = $data["log_contact_whatsapp"]; + $info->log_country = $data["country"]; + $info->log_contacts = $data["log_contacts"]; + $info->log_content = $data["log_content"]; + $info->log_quality = $data["log_quality"]; + if (!$info->save()) { + return $this->errorResponse('操作失败,请重试'); + } + return $this->successResponse([], '操作成功'); + } + return view('inquiry-log/edit'); + } + + // 删除 + public function del(Request $request) + { + if ($request->method() == "POST") { + $id = $request->post("id"); + $rst = InquiryLogModel::where("id", $id)->first(); + if (!$rst) { + return $this->errorResponse('询单未找到'); + } + if ($rst->enter_user_id != $request->user->uid) { + return $this->errorResponse('只能删除自己录入的询单'); + } + $res = InquiryLogModel::where("id", $id)->where("enter_user_id", $request->user->uid)->delete(); + if (!$res) { + return $this->errorResponse('操作失败,请重试'); + } + fileLog('ID:'.$request->user->uid.' - 删除:询单ID:'.$id); + return $this->successResponse([], '操作成功'); + } + return $this->errorResponse('请求方式错误'); + } + + // 单条信息 + public function info(Request $request) + { + if ($request->method() == "POST") { + $id = $request->all("id"); + $info = InquiryLogModel::where("id", $id)->first()->toArray(); + $list = CountryModel::all(["cn_addr", "two_letter", "id"]); + $arr = []; + foreach ($list as $vo) { + $arr[] = [ + "value" => $vo->id, + "name" => $vo->cn_addr."(".$vo->two_letter.")" + ]; + } + $info["guojia"] = $arr; + $info["guojia_id"] = [$info["log_country"]]; + + $configsModel = new ConfigsModel(); + // $data = $configsModel->getConfig("inquiry_channel"); + $inquiryChannelList = ChannelModel::all(['id', 'name']); + $data = []; + foreach ($inquiryChannelList as $key => $val) { + $data[$val->id] = $val->name; + } + + $arr = []; + foreach ($data as $ky => $vo) { + $arr[] = [ + "id" => $ky, + "name" => $vo + ]; + } + $info["qudao"] = $arr; + + $data1 = $configsModel->getConfig("log_level"); + $arr1 = []; + foreach ($data1 as $ky => $vo) { + $arr1[] = [ + "id" => $ky, + "name" => $vo + ]; + } + $info["dengji"] = $arr1; + if (!$info) { + return $this->errorResponse('查询失败,请重试'); + } + $info["log_content"] = strTh($info["log_content"]); + return $this->successResponse($info, 'success'); + } + return $this->errorResponse('请求方式错误'); + } + + // 立即发布 + public function release(Request $request) + { + try { + if ($request->method() == "POST") { + $id = $request->post("id"); + if (!in_array($request->user->role_id, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::KEFU_ROLE_ID])) { + return $this->errorResponse('无操作权限'); + } + $info = InquiryLogModel::where("id", $id)->first(); + if (!$info) { + return $this->errorResponse('询单信息未找到'); + } + if ($info->status != 0) { + return $this->errorResponse('状态异常,无法发布'); + } + $info->r_time = time(); + $info->status = 1; + if (!$info->save()) { + return $this->errorResponse('发布失败,请重试'); + } + + // 发送飞书群消息通知 + $data = ["order_id"=>$id]; + RedisQueueRedis::send("release", $data); + // 发送飞书群消息通知 + + return $this->successResponse([], '发布成功'); + } + return $this->errorResponse('电话-请求方式错误'); + } catch (\Throwable $th) { + fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); + return $this->errorResponse($th->getMessage()); + } + } + + // 立即跟单 + public function track(Request $request) + { + // Db::beginTransaction(); + try { + if ($request->method() == "POST") { + $id = $request->post("id"); + $uid = $request->user->uid; + if (!in_array($request->user->role_id, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::YEWU_ROLE_ID])) { + // Db::rollBack(); + return $this->errorResponse('无操作权限'); + } + $info = InquiryLogModel::where("id", $id)->first(); + if (!$info) { + return $this->errorResponse('询单信息未找到'); + } + if ($info->status != 1) { + // Db::rollBack(); + return $this->errorResponse('状态异常,无法跟单'); + } + // 更新询单 + $info->recipient = $uid; + $info->t_time = time(); + $info->status = 2; + // 记录跟单 + $model = new UserOrderModel(); + $model->uid = $request->user->uid; + $model->log_id = $id; + $model->score = (new InquiryLogModel())->score($info->log_quality, $info->r_time); + if (!$info->save() || !$model->save()) { + // Db::rollBack(); + return $this->errorResponse('操作失败,请重试'); + } + // Db::commit(); + + // // 发送飞书群消息通知 + $data1 = ["order_id"=>$id]; + RedisQueueRedis::send("track", $data1); + // // 发送飞书群消息通知 + + // // 同步询单到crm + $data2 = ["order_id"=>$id]; + RedisQueueRedis::send("inquiry-sync", $data2); + // // 同步询单到crm + + return $this->successResponse([], '跟单操作成功'); + } else { + // Db::rollBack(); + return $this->errorResponse('请求方式错误'); + } + } catch (\Throwable $th) { + // Db::rollBack(); + fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); + return $this->errorResponse($th->getMessage()); + } + } + + // 详情 + public function detail(Request $request) + { + if ($request->method() == "POST") { + $id = $request->all("id"); + $res = InquiryLogModel::where("id", $id)->first()->toArray(); + return $this->successResponse(["detail"=>$res["log_content"]], '操作成功'); + + } + return view('inquiry-log/detail'); + } + + // 客服分单给业务 + public function allocation(Request $request) + { + // Db::beginTransaction(); + try { + if ($request->method() == "POST") { + $data = $request->all(); + + $userInfo = UsersModel::where("id", $data["gd_user"])->first(); + if (!$userInfo) { + // Db::rollBack(); + return $this->errorResponse('用户未找到'); + } + + $model = new InquiryLogModel(); + $info = $model->where("id", $data["id"])->first(); + if (!$info) { + // Db::rollBack(); + return $this->errorResponse('询单未找到'); + } + + $uomodel = new UserOrderModel(); + $uoInfo = $uomodel->where("log_id", $data["id"])->first(); + if (!$uoInfo) { + // 记录跟单 + $HsUserOrderModel = new UserOrderModel(); + $HsUserOrderModel->uid = $data["gd_user"]; + $HsUserOrderModel->log_id = $data["id"]; + $HsUserOrderModel->score = (new InquiryLogModel())->score($info->log_quality, $info->r_time); + if (!$HsUserOrderModel->save()) { + // Db::rollBack(); + return $this->errorResponse('记录跟单操作失败,请重试'); + } + } else { + $uoInfo->uid = $data["gd_user"]; + if (!$uoInfo->save()) { + // Db::rollBack(); + return $this->errorResponse('操作失败,请重试'); + } + } + + if ($info->status != 2) { + $info->t_time = time(); + $info->status = 2; + } + $info->recipient = $data["gd_user"]; + if (!$info->save()) { + // Db::rollBack(); + return $this->errorResponse('操作失败,请重试'); + } + // Db::commit(); + + // 发送飞书群消息通知 + $data1 = ["order_id"=>$data["id"]]; + RedisQueueRedis::send("allocation", $data1); + // 发送飞书群消息通知 + + // // 同步询单到crm + $data2 = ["order_id"=>$data["id"]]; + RedisQueueRedis::send("inquiry-sync", $data2); + // // 同步询单到crm + + return $this->successResponse([], '操作成功'); + } + return view('inquiry-log/allocation'); + } catch (\Throwable $th) { + // Db::rollBack(); + fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); + return $this->errorResponse($th->getMessage()); + } + } + + // 导出 + public function export(Request $request) + { + if ($request->method() == "POST") { + $data = $request->all(); + + $where = []; + if (isset($data['log_lusers']) && $data['log_lusers']) { + $where[] = ['enter_user_id', '=', $data['log_lusers']]; + } + if (isset($data['log_gusers']) && $data['log_gusers']) { + $where[] = ['recipient', '=', $data['log_gusers']]; + } + if (isset($data['log_country']) && $data['log_country']) { + $where[] = ['log_country', '=', $data['log_country']]; + } + if (isset($data['log_channel']) && $data['log_channel']) { + $where[] = ['log_type', '=', $data['log_channel']]; + } + if (isset($data['log_times']) && $data['log_times']) { + $times = explode(' - ', $data["log_times"]); + $specificDayStart = trim($times[0]); + $specificDayEnd = trim($times[1]); + $where[] = [ + "c_time", ">", strtotime($specificDayStart) + ]; + $where[] = [ + "c_time", "<=", strtotime($specificDayEnd) + ]; + } + + // 查询 + $results = InquiryLogModel::with(['enterUser', 'recipientUser', 'country']) + ->where($where) + ->get() + ->map(function ($log) use($data) { + $arr = []; + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['订单号'] = $log->order_no; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['录入人'] = $log->enterUser->name ?? null; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['接单人'] = $log->recipientUser->name ?? null; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['等级'] = $log->log_quality; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['邮箱'] = $log->log_contact_type; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['电话'] = $log->log_contact_tel; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['WhatsApp'] = $log->log_contact_whatsapp; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['联系人'] = $log->log_contacts; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['国家(中文)'] = $log->country->cn_addr ?? null; + } + if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + $arr['国家(英文)'] = $log->country->en_full_name ?? null; + } + if (isset($data['hsl_r_time']) && $data['hsl_r_time']) { + $arr['发单时间'] = $log->r_time ? date('Y-m-d H:i:s', $log->r_time) : null; + } + if (isset($data['hsl_t_time']) && $data['hsl_t_time']) { + $arr['跟单时间'] = $log->t_time ? date('Y-m-d H:i:s', $log->t_time) : null; + } + return $arr; + }); + // 创建一个新的 Spreadsheet 对象 + $spreadsheet = new Spreadsheet(); + // 设置当前活动的工作表 + $sheet = $spreadsheet->getActiveSheet(); + // 设置工作表的标题 + $sheet->setCellValue('A1', '订单号'); + $sheet->setCellValue('B1', '录入人'); + $sheet->setCellValue('C1', '接单人'); + $sheet->setCellValue('D1', '等级'); + $sheet->setCellValue('E1', '邮箱'); + $sheet->setCellValue('F1', '电话'); + $sheet->setCellValue('G1', 'WhatsApp'); + $sheet->setCellValue('H1', '联系人'); + $sheet->setCellValue('I1', '国家(中文)'); + $sheet->setCellValue('J1', '国家(英文)'); + $sheet->setCellValue('K1', '发单时间'); + $sheet->setCellValue('L1', '跟单时间'); + $data = $results->toArray(); + + // 将数据填充到工作表中 + $row = 2; // 从第2行开始填充 + foreach ($data as $item) { + $sheet->setCellValue('A' . $row, $item['订单号']); + $sheet->setCellValue('B' . $row, $item['录入人']); + $sheet->setCellValue('C' . $row, $item['接单人']); + + $sheet->setCellValue('D' . $row, $item['等级']); + $sheet->setCellValue('E' . $row, $item['邮箱']); + $sheet->setCellValue('F' . $row, $item['电话']); + + $sheet->setCellValue('G' . $row, $item['WhatsApp']); + $sheet->setCellValue('H' . $row, $item['联系人']); + $sheet->setCellValue('I' . $row, $item['国家(中文)']); + + $sheet->setCellValue('J' . $row, $item['国家(英文)']); + $sheet->setCellValue('K' . $row, $item['发单时间']); + $sheet->setCellValue('L' . $row, $item['跟单时间']); + $row++; + } + + // 创建 Excel 文件的写入器 + $writer = new Xlsx($spreadsheet); + // 名称 + $fileName = $request->user->name.'_'.date('Ymd').'_'.uniqid().'.xlsx'; + // 路径 + $filePath = '/export/'.$fileName; + // 将文件保存到本地 + $writer->save(public_path().$filePath); + + return $this->successResponse(['url'=>$filePath, 'name'=>$fileName]); + } + return view('inquiry-log/export'); + } +} \ No newline at end of file diff --git a/app/inquiry/logic/CountryLogic.php b/app/inquiry/logic/CountryLogic.php new file mode 100644 index 0000000..34508b2 --- /dev/null +++ b/app/inquiry/logic/CountryLogic.php @@ -0,0 +1,17 @@ +validator; + } +} \ No newline at end of file diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php new file mode 100644 index 0000000..04ec37a --- /dev/null +++ b/app/inquiry/service/InquiryLogService.php @@ -0,0 +1,103 @@ +validator; + } + + + protected function buildQueryConfig(Request $request, string $scene = 'index'): array + { + $p = $request->all(); + + $config = []; + $config['model'] = $this->modelClass; + switch ($scene) { + case 'default': + $config['where']['status'] = 1; + $config['order']['sort'] = 'desc'; + $config['order']['id'] = 'desc'; + break; + case 'index': + $config['order']['id'] = 'desc'; + + // 渠道 + if (isset($p['channel']) && $p['channel']) { + $config['where']['channel'] = $p['channel']; + } + + // 国家 + if (isset($p['country']) && $p['country']) { + $config['where']['country'] = $p['country']; + } + + // 时间 + if (isset($p['time']) && $p['time']) { + $time = explode(' - ', $p['time']); + $config['where']['created_at'] = [ + ['>=', strtotime($time[0])], + ['<=', strtotime($time[1])], + ]; + } + + // 状态 + if (isset($p['status']) && $p['status']) { + $config['where']['status'] = $p['status']; + } + + // 关键字 + if (isset($p['keyword']) && $p['keyword']) { + // $config['where']['order_no|email|tel|whatsapp|contacts'] = ['like', '%' . $p['keyword'] . '%']; + } + + $config['with'][] = 'enter'; + $config['with'][] = 'recipient'; + + $config['withFields']['enter'][] = 'id'; + $config['withFields']['enter'][] = 'name'; + + $config['withFields']['recipient'][] = 'id'; + $config['withFields']['recipient'][] = 'name'; + break; + case 'show': + $config['where']['id'] = $p['id']; + $config['where']['status'] = 1; + break; + case 'store': + break; + case 'update': + $config['where']['id'] = $p['id']; + break; + case 'destroy': + $config['where']['id'] = $p['id']; + break; + } + + return $config; + } +} \ No newline at end of file diff --git a/config/route.php b/config/route.php index d0e9290..01953d0 100644 --- a/config/route.php +++ b/config/route.php @@ -73,6 +73,15 @@ Route::group('/admin', function () { Route::any('/update', [app\admin\controller\CorpController::class, 'update']); Route::any('/destroy', [app\admin\controller\CorpController::class, 'destroy']); }); + + // 询单 + Route::group('/inquiry', function () { + Route::any('/list', [app\inquiry\controller\InquiryLogController::class, 'index']); + // Route::any('/show', [app\admin\controller\CorpController::class, 'show']); + // Route::any('/store', [app\admin\controller\CorpController::class, 'store']); + // Route::any('/update', [app\admin\controller\CorpController::class, 'update']); + // Route::any('/destroy', [app\admin\controller\CorpController::class, 'destroy']); + }); }); From e20ffa05c0ada732d1273308e6484c11306871bf Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 15:29:54 +0800 Subject: [PATCH 079/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 74 ++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index 275912b..af9f848 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -54,14 +54,14 @@ class PermLogic /** - * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单 component 处理 + * 构建目录/菜单树 * * @param \Illuminate\Support\Collection $resources 全部资源集合 * @param int $parentId 当前父级资源 ID * @param string $parentPath 父级完整路径(用于递归) * @return array */ - public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array + public function buildTree_bak($resources, int $parentId = 0, string $parentPath = ''): array { $tree = []; @@ -137,16 +137,7 @@ class PermLogic return $tree; } - - /** - * 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单 component 处理 - * - * @param \Illuminate\Support\Collection $resources 全部资源集合 - * @param int $parentId 当前父级资源 ID - * @param string $parentPath 父级完整路径(用于递归) - * @return array - */ - public function buildTree_bak($resources, int $parentId = 0, string $parentPath = ''): array + public function buildTree_bak2($resources, int $parentId = 0, string $parentPath = ''): array { $tree = []; @@ -170,7 +161,7 @@ class PermLogic // —— 递归构建子节点 —— $children = []; - if (in_array($node->type, ['directory', 'menu'])) { + if ($node->type === 'directory') { $children = $this->buildTree($resources, $node->id, $currentPath); } @@ -218,7 +209,64 @@ class PermLogic return $tree; } + public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array + { + $tree = []; + foreach ($resources as $node) { + if ($node->parent_id !== $parentId) continue; + // 拼接完整路径 + $currentPath = $parentPath; + if ($currentPath && substr($currentPath, -1) === '/') { + $currentPath = rtrim($currentPath, '/'); + } + if (!empty($node->url)) { + if (!$currentPath) { + $currentPath = $node->url; + } else { + $url = ltrim($node->url, '/'); + $currentPath = $currentPath . '/' . $url; + } + } + + // 子节点递归 + $children = []; + if ($node->type === 'directory') { + $children = $this->buildTree($resources, $node->id, $currentPath); + } + + // 权限 + $authList = $resources + ->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id) + ->map(fn($r) => [ + 'id' => $r->id, + 'title' => $r->name, + 'auth_mark' => $r->code, + ])->values()->all(); + + // 判断是否有子级菜单(非权限) + $hasChildrenMenu = collect($resources)->where('parent_id', $node->id) + ->whereIn('type', ['directory', 'menu'])->isNotEmpty(); + + $componentValue = $hasChildrenMenu ? '/index/index' : $currentPath; + + $tree[] = [ + 'id' => $node->id, + 'name' => $node->name, + 'path' => $node->url, + 'component' => $componentValue, + 'meta' => [ + 'title' => $node->name, + 'icon' => $node->icon, + 'keepAlive' => true, + 'authList' => $authList, + 'openType' => $node->open_type, + ], + 'children' => $children, + ]; + } + return $tree; + } /** * 获取用户所有权限code From 283b21b1f8b4eb267d5b0059ae3cce331e388b68 Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 22 Jul 2025 19:30:27 +0800 Subject: [PATCH 080/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E5=8A=A9=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../traits/crud/CrudControllerTrait.php | 10 +- app/common/utils/CrudUtil.php | 131 +++++++++++++----- app/inquiry/service/InquiryLogService.php | 35 ++--- 3 files changed, 122 insertions(+), 54 deletions(-) diff --git a/app/common/traits/crud/CrudControllerTrait.php b/app/common/traits/crud/CrudControllerTrait.php index 41d9cd5..ca30ba3 100644 --- a/app/common/traits/crud/CrudControllerTrait.php +++ b/app/common/traits/crud/CrudControllerTrait.php @@ -24,7 +24,7 @@ trait CrudControllerTrait ? [$this, 'indexFormat'] : null); return $this->success($data); - } catch (\Exception $e) { + } catch (\Throwable $e) { return $this->error($e->getMessage()); } } @@ -44,7 +44,7 @@ trait CrudControllerTrait ? [$this, 'showFormat'] : null); return $this->success($data); - } catch (\Exception $e) { + } catch (\Throwable $e) { return $this->error($e->getMessage()); } } @@ -61,7 +61,7 @@ trait CrudControllerTrait $data = $this->validate($this->service->getValidator(), $request->post(), 'store'); $res = $this->service->serviceStore($request, $data); return $this->success($res, '创建成功'); - } catch (\Exception $e) { + } catch (\Throwable $e) { return $this->error($e->getMessage()); } } @@ -78,7 +78,7 @@ trait CrudControllerTrait $data = $this->validate($this->service->getValidator(), $request->post(), 'update'); $res = $this->service->serviceUpdate($request, $data); return $this->success([], '更新成功'); - } catch (\Exception $e) { + } catch (\Throwable $e) { return $this->error($e->getMessage()); } } @@ -94,7 +94,7 @@ trait CrudControllerTrait try { $count = $this->service->serviceDestroy($request); return $this->success([], "成功删除了 {$count} 条"); - } catch (\Exception $e) { + } catch (\Throwable $e) { return $this->error($e->getMessage()); } } diff --git a/app/common/utils/CrudUtil.php b/app/common/utils/CrudUtil.php index 22b5202..979181f 100644 --- a/app/common/utils/CrudUtil.php +++ b/app/common/utils/CrudUtil.php @@ -24,6 +24,7 @@ use support\exception\BusinessException; class CrudUtil { + /** * 构建查询,支持: * - 本表字段过滤 where @@ -56,48 +57,62 @@ class CrudUtil $model = new $config['model']; $q = $model->newQuery(); - // 1. eager load - if (!empty($config['with'])) { - $q->with($config['with']); - } - - // 2. 关联表字段选择 - if (!empty($config['withFields'])) { - foreach ($config['withFields'] as $rel => $fields) { - $q->with([$rel => function ($qr) use ($fields) { - $fk = $qr->getModel()->getForeignKey(); - $qr->select(array_unique(array_merge($fields, [$fk]))); - }]); + // eager-load:支持 ['rel','rel2'] 和 ['rel'=>fields[], ...] + if (! empty($config['with'])) { + foreach ($config['with'] as $rel => $fields) { + if (is_int($rel)) { + // 普通关联 + $q->with($fields); + } else { + // 带字段筛选的关联 + $q->with([$rel => function ($qr) use ($fields) { + $key = $qr->getModel()->getKeyName(); + if (! in_array($key, $fields, true)) { + $fields[] = $key; + } + $qr->select($fields); + }]); + } } } - // 3. 本表字段选择 + // 表字段 if (!empty($config['fields'])) { $q->select($config['fields']); } - // 4. where 条件(支持 dot-notation 关联过滤) + // where if (!empty($config['where'])) { foreach ($config['where'] as $col => $val) { if (strpos($col, '.') !== false) { - [$rel, $relCol] = explode('.', $col, 2); - $q->whereHas($rel, function ($qr) use ($relCol, $val) { - self::applyWhere($qr, $relCol, $val); - }); + // 多层支持 + $segments = explode('.', $col); + $leaf = array_pop($segments); + self::applyNestedWhere($q, $segments, $leaf, $val); } else { self::applyWhere($q, $col, $val); } } } - // 5. 排序 + // or where + if (!empty($config['orWhere'])) { + $q->where(function($q) use($config) { + foreach ($config['orWhere'] as $cond) { + [$col,$op,$val] = $cond; + $q->orWhere($col,$op,$val); + } + }); + } + + // 排序 if (!empty($config['order'])) { foreach ($config['order'] as $col => $dir) { $q->orderBy($col, strtolower($dir) === 'asc' ? 'asc' : 'desc'); } } - // 6. 数据权限过滤 + // 数据权限过滤 if (!empty($config['dataLimit'])) { self::applyDataLimit($q, $config['dataLimit']); } @@ -261,8 +276,29 @@ class CrudUtil //=================== Internal Methods ===================// + /** - * 应用单字段 where 过滤 + * 支持多层的 whereHas:$relations=['roles','permissions'],$leaf='name' + */ + protected static function applyNestedWhere(Builder $q, array $relations, string $leaf, $val): void + { + $rel = array_shift($relations); + $q->whereHas($rel, function (Builder $qr) use ($relations, $leaf, $val) { + if ($relations) { + // 还有更深层,继续递归 + self::applyNestedWhere($qr, $relations, $leaf, $val); + } else { + // 最后一层,使用普通 applyWhere + self::applyWhere($qr, $leaf, $val); + } + }); + } + + /** + * 单字段 where 支持: + * - 多条件数组 [ [op1,val1], [op2,val2], … ] + * - 单条件 ['op', val] + * - 直接值 * * @param Builder $q * @param string $col @@ -271,22 +307,53 @@ class CrudUtil */ protected static function applyWhere(Builder $q, string $col, $val): void { - if (is_array($val) && count($val) === 2) { - [$op, $v] = $val; - switch (strtolower($op)) { - case 'like': $q->where($col,'like',$v); break; - case 'in': $q->whereIn($col,(array)$v); break; - case 'not in': $q->whereNotIn($col,(array)$v); break; - case 'null': $q->whereNull($col); break; - case 'not null': $q->whereNotNull($col); break; - case 'between': $q->whereBetween($col,(array)$v); break; - default: $q->where($col,$op,$v); + // 多条件数组 + if (is_array($val) && isset($val[0]) && is_array($val[0])) { + foreach ($val as $pair) { + if (is_array($pair) && count($pair) === 2) { + self::applySingleWhere($q, $col, $pair[0], $pair[1]); + } } - } else { + } + // 单条件 + elseif (is_array($val) && count($val) === 2) { + self::applySingleWhere($q, $col, $val[0], $val[1]); + } + // 直接等值 + else { $q->where($col, $val); } } + /** + * 真正执行一个条件的 where + */ + protected static function applySingleWhere(Builder $q, string $col, string $op, $v): void + { + switch (strtolower($op)) { + case 'like': + $q->where($col, 'like', $v); + break; + case 'in': + $q->whereIn($col, (array)$v); + break; + case 'not in': + $q->whereNotIn($col, (array)$v); + break; + case 'null': + $q->whereNull($col); + break; + case 'not null': + $q->whereNotNull($col); + break; + case 'between': + $q->whereBetween($col, (array)$v); + break; + default: + $q->where($col, $op, $v); + } + } + /** * 应用数据权限过滤到查询(buildQuery 场景) */ diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 04ec37a..bc966f4 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -38,12 +38,8 @@ class InquiryLogService $config = []; $config['model'] = $this->modelClass; switch ($scene) { - case 'default': - $config['where']['status'] = 1; - $config['order']['sort'] = 'desc'; - $config['order']['id'] = 'desc'; - break; case 'index': + // 排序 $config['order']['id'] = 'desc'; // 渠道 @@ -58,10 +54,14 @@ class InquiryLogService // 时间 if (isset($p['time']) && $p['time']) { - $time = explode(' - ', $p['time']); + // $time = explode(' - ', $p['time']); + // $config['where']['created_at'] = [ + // ['>=', strtotime($time[0])], + // ['<=', strtotime($time[1])], + // ]; $config['where']['created_at'] = [ - ['>=', strtotime($time[0])], - ['<=', strtotime($time[1])], + strtotime(explode(' - ', $p['time'])[0]), + strtotime(explode(' - ', $p['time'])[1]), ]; } @@ -72,17 +72,18 @@ class InquiryLogService // 关键字 if (isset($p['keyword']) && $p['keyword']) { - // $config['where']['order_no|email|tel|whatsapp|contacts'] = ['like', '%' . $p['keyword'] . '%']; + $k = '%' . $p['keyword'] . '%'; + foreach (['order_no','email','tel','whatsapp','contacts'] as $col) { + $config['orWhere'][] = [$col, 'like', $k]; + } } - $config['with'][] = 'enter'; - $config['with'][] = 'recipient'; - - $config['withFields']['enter'][] = 'id'; - $config['withFields']['enter'][] = 'name'; - - $config['withFields']['recipient'][] = 'id'; - $config['withFields']['recipient'][] = 'name'; + // 关联 + $config['with'][] = 'country'; + $config['with']['enter'][] = 'id'; + $config['with']['enter'][] = 'name'; + $config['with']['recipient'][] = 'id'; + $config['with']['recipient'][] = 'name'; break; case 'show': $config['where']['id'] = $p['id']; From b22db8ac35416326fef3eed95bb6d5d2e47bc451 Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 23 Jul 2025 15:02:09 +0800 Subject: [PATCH 081/120] =?UTF-8?q?=E8=AF=A2=E5=8D=95=E6=8E=A5=E5=8F=A3&?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=84=9A=E6=89=8B=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/model/InquiryChannel.php | 49 +++++++++ app/common/model/InquiryLog.php | 13 +-- app/common/traits/crud/CrudLogicTrait.php | 24 ----- app/common/traits/crud/CrudServiceTrait.php | 82 ++++++--------- app/common/utils/CrudUtil.php | 2 +- app/common/utils/scaffold/Generate.php | 75 +++++++------- app/common/validate/InquiryLogValidate.php | 99 ++++++++++++++++++- app/controller/IndexController.php | 38 +++---- app/functions.php | 9 ++ .../controller/InquiryLogController.php | 20 ++-- app/inquiry/service/InquiryLogService.php | 33 ++++--- config/route.php | 8 +- 12 files changed, 285 insertions(+), 167 deletions(-) create mode 100644 app/common/model/InquiryChannel.php diff --git a/app/common/model/InquiryChannel.php b/app/common/model/InquiryChannel.php new file mode 100644 index 0000000..fb387ba --- /dev/null +++ b/app/common/model/InquiryChannel.php @@ -0,0 +1,49 @@ +hasOne(User::class, 'id', 'recipient'); } - // public function enter() - // { - // return $this->belongsTo(User::class, 'enter_user_id'); - // } - - // public function recipient() - // { - // return $this->belongsTo(User::class, 'recipient'); - // } + public function channel() + { + return $this->hasOne(InquiryChannel::class, 'id', 'channel'); + } public function country() { diff --git a/app/common/traits/crud/CrudLogicTrait.php b/app/common/traits/crud/CrudLogicTrait.php index 4e2b794..e648f2f 100644 --- a/app/common/traits/crud/CrudLogicTrait.php +++ b/app/common/traits/crud/CrudLogicTrait.php @@ -38,14 +38,6 @@ trait CrudLogicTrait return CrudUtil::findOneBy(CrudUtil::buildQuery($config)); } - /** - * 任意条件查询单条(可带 dataLimit) - */ - // public function logicShowBy(array $where, array $with = [], array $dataLimit = []) - // { - // return CrudUtil::findOneBy($this->modelClass, $where, $with, $dataLimit); - // } - /** * 创建(可带 dataLimit) */ @@ -54,14 +46,6 @@ trait CrudLogicTrait return CrudUtil::create($this->modelClass, $data, $dataLimit); } - /** - * 主键更新(可带 dataLimit) - */ - // public function logicUpdateByPk($id, array $data, array $dataLimit = []) - // { - // return CrudUtil::updateByPk($this->modelClass, $id, $data, $dataLimit); - // } - /** * 任意条件更新(可带 dataLimit) */ @@ -70,14 +54,6 @@ trait CrudLogicTrait return CrudUtil::updateBy(CrudUtil::buildQuery($config), $data); } - /** - * 主键删除(批量) - */ - // public function logicDeleteByPk(array $ids) - // { - // return CrudUtil::deleteByPk($this->modelClass, $ids); - // } - /** * 任意条件删除(可带 dataLimit) */ diff --git a/app/common/traits/crud/CrudServiceTrait.php b/app/common/traits/crud/CrudServiceTrait.php index c042b5f..bf17abc 100644 --- a/app/common/traits/crud/CrudServiceTrait.php +++ b/app/common/traits/crud/CrudServiceTrait.php @@ -32,8 +32,8 @@ trait CrudServiceTrait $result = $this->complexQuery($request->get()); } else { $config = $this->buildQueryConfig($request, 'index'); - $limit = (int)$request->get('limit', 15); - $page = (int)$request->get('page', 1); + $limit = (int)$request->get('size', 15); + $page = (int)$request->get('current', 1); $result = $this->logicList($config, $limit, $page); } return $this->postProcess('List', $result); @@ -62,7 +62,7 @@ trait CrudServiceTrait /** 创建 */ public function serviceStore(Request $request, array $data = []) { - // $data = $request->post(); + $data = $this->preProcess('Store', $data, $request); $dl = $this->getDataLimitFromRequest($request); $created = $this->logicCreate($data, $dl); return $this->postProcess('Store', $created); @@ -71,7 +71,7 @@ trait CrudServiceTrait /** 更新 */ public function serviceUpdate(Request $request, array $data = []) { - // $data= $request->post(); + $data = $this->preProcess('Update', $data, $request); $config = $this->buildQueryConfig($request, 'update'); $updated = $this->logicUpdateBy($config, $data); return $this->postProcess('Update', $updated); @@ -88,16 +88,7 @@ trait CrudServiceTrait /** ----------------- 公共辅助方法 ----------------- */ /** - * 构造 CrudUtil 的查询配置1 - * - * 前端可以通过以下参数控制: - * - filter_= → where - * - sort_by=&order= - * - fields=id,name,email → select - * - with=roles,posts → 关联 - * - withFields=roles:id,name;... → 关联字段 - * - dataLimitType=personal|auth - * - dataLimitField=admin_id + * 构造 CrudUtil 查询配置 */ protected function buildQueryConfig(Request $request, string $scene = 'default'): array { @@ -126,42 +117,11 @@ trait CrudServiceTrait $config['where']['id'] = $p['id']; break; case 'destroy': - $config['where']['id'] = $p['id']; + // $config['where']['id'] = $p['id']; break; } return $config; - - // // 1. where 从 filter_ 前缀自动映射 - // $where = []; - // foreach ($p as $key => $val) { - // if (strpos($key, 'filter_') === 0 && $val !== '') { - // $col = substr($key, 7); - // // 支持 %like% 查询 - // $where[$col] = strpos($val, '%') !== false - // ? ['like', $val] - // : $val; - // } - // } - - // // 2. 其余配置 - // return [ - // 'model' => $this->modelClass, - // 'where' => $where, - // 'order' => [ - // $p['sort_by'] ?? 'id' => $p['order'] ?? 'desc' - // ], - // 'fields' => !empty($p['fields']) - // ? explode(',', $p['fields']) - // : [], - // 'with' => !empty($p['with']) - // ? explode(',', $p['with']) - // : [], - // 'withFields' => $this->parseWithFields( - // $p['withFields'] ?? '' - // ), - // 'dataLimit' => $this->getDataLimitFromRequest($request), - // ]; } /** 解析 withFields 字符串 "rel:field,field;..." */ @@ -191,14 +151,20 @@ trait CrudServiceTrait ]; } - /** - * 处理复杂查询 - * 覆盖此方法,实现复杂查询逻辑 + /** + * $data 前置处理 + * + * @param array $data + * @param Request $request + * @return array */ - // protected function complexQuery(array $params): array - // { - // throw new \LogicException('请在子类实现 complexQuery'); - // } + protected function preProcess(string $op, mixed $data, Request $request): mixed + { + $hook = "beforeService{$op}"; + return method_exists($this, $hook) + ? $this->{$hook}($data, $request) + : $data; + } /** * 根据操作类型,调用对应的后置钩子 @@ -214,4 +180,14 @@ trait CrudServiceTrait ? $this->{$hook}($data) : $data; } + + + /** + * 处理复杂查询 + * 覆盖此方法,实现复杂查询逻辑 + */ + // protected function complexQuery(array $params): array + // { + // throw new \LogicException('请在子类实现 complexQuery'); + // } } \ No newline at end of file diff --git a/app/common/utils/CrudUtil.php b/app/common/utils/CrudUtil.php index 979181f..dec779b 100644 --- a/app/common/utils/CrudUtil.php +++ b/app/common/utils/CrudUtil.php @@ -326,7 +326,7 @@ class CrudUtil } /** - * 真正执行一个条件的 where + * 执行条件 where */ protected static function applySingleWhere(Builder $q, string $col, string $op, $v): void { diff --git a/app/common/utils/scaffold/Generate.php b/app/common/utils/scaffold/Generate.php index 6735997..753a9cf 100644 --- a/app/common/utils/scaffold/Generate.php +++ b/app/common/utils/scaffold/Generate.php @@ -24,44 +24,51 @@ class Generate { $fs = new Filesystem(); - // 模板路径 $templateDir = __DIR__ . '/templates/'; - $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".$this->rName($this->options['controller']['name'])."Controller.php"; - $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".$this->rName($this->options['service']['name'])."Service.php"; - $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".$this->rName($this->options['logic']['name'])."Logic.php"; - $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".$this->rName($this->options['model']['name']).".php"; - $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".$this->rName($this->options['validate']['name'])."Validate.php"; - // 创建目录(如果不存在) - $fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller"); - $fs->mkdir(__DIR__ . "/../../../{$this->options['service']['module']}/service"); - $fs->mkdir(__DIR__ . "/../../../{$this->options['logic']['module']}/logic"); - $fs->mkdir(__DIR__ . "/../../../{$this->options['model']['module']}/model"); - $fs->mkdir(__DIR__ . "/../../../{$this->options['validate']['module']}/validate"); + $resultStr = ''; - // // 删除已存在的文件并重新生成 - $this->deleteFileIfExists($controllerPath); - $this->deleteFileIfExists($servicePath); - $this->deleteFileIfExists($logicPath); - $this->deleteFileIfExists($modelPath); - $this->deleteFileIfExists($validatePath); + if (!empty($this->options['controller'])) { + $controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".$this->rName($this->options['controller']['name'])."Controller.php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller"); + $this->deleteFileIfExists($controllerPath); + $ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath, 'controller'); + $resultStr .= $ControllerStub; + } - // 生成控制器文件 - $ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath, 'controller'); - - // 生成服务类文件 - $ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath, 'service'); - - // 生成逻辑类文件 - $LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath, 'logic'); - - // 生成模型类文件 - $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath, 'model'); - - // 生成验证器类文件 - $ValidatorStub = $this->generateFile($templateDir, 'ValidateStub.stub', $validatePath, 'validate'); - - return $ControllerStub . $ServiceStub . $LogicStub . $ModelStub . $ValidatorStub . "文件生成成功!
"; + if (!empty($this->options['service'])) { + $servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".$this->rName($this->options['service']['name'])."Service.php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['service']['module']}/service"); + $this->deleteFileIfExists($servicePath); + $ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath, 'service'); + $resultStr .= $ServiceStub; + } + + if (!empty($this->options['logic'])) { + $logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".$this->rName($this->options['logic']['name'])."Logic.php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['logic']['module']}/logic"); + $this->deleteFileIfExists($logicPath); + $LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath, 'logic'); + $resultStr .= $LogicStub; + } + + if (!empty($this->options['model'])) { + $modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".$this->rName($this->options['model']['name']).".php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['model']['module']}/model"); + $this->deleteFileIfExists($modelPath); + $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath, 'model'); + $resultStr .= $ModelStub; + } + + if (!empty($this->options['validate'])) { + $validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".$this->rName($this->options['validate']['name'])."Validate.php"; + $fs->mkdir(__DIR__ . "/../../../{$this->options['validate']['module']}/validate"); + $this->deleteFileIfExists($validatePath); + $ValidatorStub = $this->generateFile($templateDir, 'ValidateStub.stub', $validatePath, 'validate'); + $resultStr .= $ValidatorStub; + } + + return $resultStr ? $resultStr . "文件生成成功!
" : '未生成任何文件'; } /** diff --git a/app/common/validate/InquiryLogValidate.php b/app/common/validate/InquiryLogValidate.php index ca266b1..58058cb 100644 --- a/app/common/validate/InquiryLogValidate.php +++ b/app/common/validate/InquiryLogValidate.php @@ -3,27 +3,118 @@ namespace app\common\validate; use think\Validate; +use app\common\model\InquiryLog; class InquiryLogValidate extends Validate { // 规则 protected $rule = [ - + 'id' => 'require|integer|gt:0', + 'order_no' => 'require|alphaDash|length:36', + 'enter_user_id'=> 'integer|egt:0', + 'recipient' => 'integer|egt:0', + 'quality' => 'require|in:1,2,3', + 'channel' => 'integer|between:0,99', + 'email' => 'email|max:255|checkEmailContactUnique', + 'tel' => 'max:255|checkTelContactUnique', + 'whatsapp' => 'max:255|checkWhatsappContactUnique', + 'country' => 'integer|egt:0|requireWith:contacts', + 'contacts' => 'require|max:255|requireWithout:email,tel,whatsapp|checkContactUnique', + 'content' => 'string', + 'status' => 'in:0,1,2', + 'r_time' => 'integer|egt:0', + 't_time' => 'integer|egt:0' ]; // 提示 protected $message = [ - + 'id.require' => 'ID不能为空', + 'id.integer' => 'ID必须是整数', + 'id.gt' => 'ID必须大于0', + 'order_no.require' => '订单号不能为空', + 'order_no.alphaDash'=> '订单号只能包含字母、数字和下划线', + 'order_no.length' => '订单号长度必须为36位', + 'enter_user_id.integer' => '录入人ID必须是整数', + 'enter_user_id.egt' => '录入人ID不能为负数', + 'recipient.integer' => '接单人ID必须是整数', + 'recipient.egt' => '接单人ID不能为负数', + 'quality.require' => '询单等级不能为空', + 'quality.in' => '询单等级只能是1、2或3', + 'channel.integer' => '渠道编号必须是整数', + 'channel.between' => '渠道编号范围0-99', + 'email.email' => '邮箱格式不正确', + 'email.max' => '邮箱长度不能超过255个字符', + 'tel.max' => '电话长度不能超过255个字符', + 'whatsapp.max' => 'WhatsApp长度不能超过255个字符', + 'country.integer' => '国家代码必须是整数', + 'country.egt' => '国家代码不能为负数', + 'contacts.require' => '联系人不能为空', + 'contacts.max' => '联系人长度不能超过255个字符', + 'content.string' => '询单内容必须是字符串', + 'status.in' => '状态值只能是0、1或2', + 'r_time.integer' => '发布时间必须是整数', + 'r_time.egt' => '发布时间不能为负数', + 't_time.integer' => '跟踪时间必须是整数', + 't_time.egt' => '跟踪时间不能为负数', + 'email.checkEmailContactUnique' => '邮箱联系方式已存在', + 'tel.checkTelContactUnique' => '电话联系方式已存在', + 'whatsapp.checkWhatsappContactUnique' => 'WhatsApp联系方式已存在', + 'contacts.requireWithout' => '至少需要提供一种联系方式', + 'contacts.checkContactUnique' => '联系人已存在', + 'country.requireWith' => '国家必须与联系人一起提供' ]; // 场景 protected $scene = [ 'store' => [ - + 'enter_user_id', 'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content' ], 'update' => [ - + 'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content' ] ]; + + /** + * 验证联系方式唯一性 + */ + protected function checkContactUnique($value, $rule, $data) + { + // 至少需要提供一种联系方式 + if (empty($data['email']) && empty($data['tel']) && empty($data['whatsapp'])) { + $exists = InquiryLog::where('contacts', trim($data['contacts'])) + ->where('country', $data['country']) + ->exists(); + return !$exists; + } + + return true; + } + + /** + * 验证邮箱联系方式唯一性 + */ + protected function checkEmailContactUnique($value) + { + if (empty($value)) return true; + return !InquiryLog::where('email', trim($value))->exists(); + } + + /** + * 验证电话联系方式唯一性 + */ + protected function checkTelContactUnique($value) + { + if (empty($value)) return true; + return !InquiryLog::where('tel', trim($value))->exists(); + } + + /** + * 验证WhatsApp联系方式唯一性 + */ + protected function checkWhatsappContactUnique($value) + { + if (empty($value)) return true; + return !InquiryLog::where('whatsapp', trim($value))->exists(); + } } \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 50a3280..48977c2 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -70,27 +70,27 @@ class IndexController public function createResource() { $options = [ - 'controller' => [ - 'module' => 'inquiry', - 'name' => 'country' - ], + // 'controller' => [ + // 'module' => 'inquiry', + // 'name' => 'country' + // ], 'model' => [ - 'module' => 'inquiry', - 'name' => 'country' + 'module' => 'common', + 'name' => 'inquiry_channel' ], - 'service' => [ - 'module' => 'inquiry', - 'name' => 'country' - ], - 'logic' => [ - 'module' => 'inquiry', - 'name' => 'country' - ], - 'validate' => [ - 'module' => 'inquiry', - 'name' => 'country' - ], - 'table' => 'country' + // 'service' => [ + // 'module' => 'inquiry', + // 'name' => 'country' + // ], + // 'logic' => [ + // 'module' => 'inquiry', + // 'name' => 'country' + // ], + // 'validate' => [ + // 'module' => 'inquiry', + // 'name' => 'country' + // ], + 'table' => 'inquiry_channel' ]; $result = (new Generate($options))->doGenerate(); diff --git a/app/functions.php b/app/functions.php index 2a05e3f..3fed6a2 100644 --- a/app/functions.php +++ b/app/functions.php @@ -66,6 +66,15 @@ if (!function_exists('getConfig')) { } } +/* + * 询单号生成 + */ +if (!function_exists('orderNum')) { + function orderNum() { + return "SN".date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); + } +} + /* * 调试用 */ diff --git a/app/inquiry/controller/InquiryLogController.php b/app/inquiry/controller/InquiryLogController.php index 9b48395..8fbd985 100644 --- a/app/inquiry/controller/InquiryLogController.php +++ b/app/inquiry/controller/InquiryLogController.php @@ -22,14 +22,18 @@ class InquiryLogController extends BaseController * @param array $data * @return array */ - // protected function indexFormat($data) - // { - // if (isset($data['created_at'])) - // $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); - // if (isset($data['updated_at'])) - // $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); - // return $data; - // } + protected function indexFormat($data) + { + if (isset($data['created_at'])) + $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); + if (isset($data['updated_at'])) + $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); + if (isset($data['r_time'])) + $data['r_time'] = $data['r_time'] > 0 ? date('Y-m-d H:i:s', $data['r_time']) : 0; + if (isset($data['t_time'])) + $data['t_time'] = $data['t_time'] > 0 ? date('Y-m-d H:i:s', $data['t_time']) : 0; + return $data; + } // 列表 diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index bc966f4..754280d 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -54,14 +54,9 @@ class InquiryLogService // 时间 if (isset($p['time']) && $p['time']) { - // $time = explode(' - ', $p['time']); - // $config['where']['created_at'] = [ - // ['>=', strtotime($time[0])], - // ['<=', strtotime($time[1])], - // ]; + $time = explode(' - ', $p['time']); $config['where']['created_at'] = [ - strtotime(explode(' - ', $p['time'])[0]), - strtotime(explode(' - ', $p['time'])[1]), + 'between', [strtotime($time[0]), strtotime($time[1])] ]; } @@ -79,15 +74,13 @@ class InquiryLogService } // 关联 - $config['with'][] = 'country'; - $config['with']['enter'][] = 'id'; + $config['with']['country'][] = 'cn_addr'; $config['with']['enter'][] = 'name'; - $config['with']['recipient'][] = 'id'; $config['with']['recipient'][] = 'name'; + $config['with']['channel'][] = 'name'; break; case 'show': $config['where']['id'] = $p['id']; - $config['where']['status'] = 1; break; case 'store': break; @@ -101,4 +94,22 @@ class InquiryLogService return $config; } + + public function beforeServiceStore($data, Request $request) + { + $data['order_no'] = orderNum(); + $data['enter_user_id'] = $request->user->uid; + $data['recipient'] = 0; + $data['email'] = trim($data["email"]); + $data['tel'] = trim($data['tel']); + $data['whatsapp'] = trim($data['whatsapp']); + return $data; + } + + // public function afterServiceList($data) + // { + // var_dump($data); + + // return $data; + // } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index 01953d0..6238ffd 100644 --- a/config/route.php +++ b/config/route.php @@ -77,10 +77,10 @@ Route::group('/admin', function () { // 询单 Route::group('/inquiry', function () { Route::any('/list', [app\inquiry\controller\InquiryLogController::class, 'index']); - // Route::any('/show', [app\admin\controller\CorpController::class, 'show']); - // Route::any('/store', [app\admin\controller\CorpController::class, 'store']); - // Route::any('/update', [app\admin\controller\CorpController::class, 'update']); - // Route::any('/destroy', [app\admin\controller\CorpController::class, 'destroy']); + Route::any('/show', [app\inquiry\controller\InquiryLogController::class, 'show']); + Route::any('/store', [app\inquiry\controller\InquiryLogController::class, 'store']); + Route::any('/update', [app\inquiry\controller\InquiryLogController::class, 'update']); + Route::any('/destroy', [app\inquiry\controller\InquiryLogController::class, 'destroy']); }); }); From 36288fa64d2a8db5fc90bef2bfa1e57d777c42c9 Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 23 Jul 2025 17:06:48 +0800 Subject: [PATCH 082/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AF=A2=E5=8D=95?= =?UTF-8?q?=E5=8F=91=E5=B8=83&=E5=88=86=E9=85=8D&=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=85=A8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/PresetController.php | 12 + app/common/logic/UserLogic.php | 15 + app/common/service/UserService.php | 9 + app/common/traits/crud/CrudLogicTrait.php | 8 + app/common/traits/crud/CrudServiceTrait.php | 2 +- app/common/validate/InquiryLogValidate.php | 7 +- .../controller/InquiryLogController.php | 549 +----------------- app/inquiry/service/InquiryLogService.php | 84 ++- config/route.php | 2 + 9 files changed, 149 insertions(+), 539 deletions(-) diff --git a/app/common/controller/PresetController.php b/app/common/controller/PresetController.php index a4b2a81..27f0c7a 100644 --- a/app/common/controller/PresetController.php +++ b/app/common/controller/PresetController.php @@ -6,6 +6,7 @@ use support\Request; use app\common\service\CorpService; use app\common\service\CorpFeishuAppService; use app\common\service\SystemsService; +use app\common\service\UserService; class PresetController extends BaseController { @@ -48,4 +49,15 @@ class PresetController extends BaseController return $this->error($e->getMessage()); } } + + + public function users() + { + try { + $result = (new UserService())->getList(); + return $this->success($result, '获取用户列表'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/common/logic/UserLogic.php b/app/common/logic/UserLogic.php index 4b58247..9f5ae44 100644 --- a/app/common/logic/UserLogic.php +++ b/app/common/logic/UserLogic.php @@ -53,4 +53,19 @@ class UserLogic return $res; } + /** + * 获取用户列表 + */ + public function getList() + { + $list = User::where('status', 1)->orderBy('id', 'desc')->get(); + $arr = []; + foreach ($list->toArray() as $item) { + $arr[] = [ + 'name' => $item['name'], + 'id' => $item['id'], + ]; + } + return $arr; + } } \ No newline at end of file diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 85684d3..25de588 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -5,6 +5,7 @@ namespace app\common\service; use app\common\logic\UserLogic; use app\common\model\Corp; use app\common\model\User; +use app\common\utils\CrudUtil; use app\crm\model\Country; use support\exception\BusinessException; @@ -99,4 +100,12 @@ class UserService $user = $this->logic->savePassword($uid,$data); return $user; } + + /** + * 获取用户列表 + */ + public function getList() + { + return $this->logic->getList(); + } } \ No newline at end of file diff --git a/app/common/traits/crud/CrudLogicTrait.php b/app/common/traits/crud/CrudLogicTrait.php index e648f2f..9e629f9 100644 --- a/app/common/traits/crud/CrudLogicTrait.php +++ b/app/common/traits/crud/CrudLogicTrait.php @@ -69,4 +69,12 @@ trait CrudLogicTrait { return CrudUtil::executeComplex($this->modelClass, $callback); } + + /** + * 根据主键查询 + */ + public function findByPk(int $id, array $with = []) + { + return CrudUtil::findByPk($this->modelClass, $id, $with); + } } diff --git a/app/common/traits/crud/CrudServiceTrait.php b/app/common/traits/crud/CrudServiceTrait.php index bf17abc..5ad6dbc 100644 --- a/app/common/traits/crud/CrudServiceTrait.php +++ b/app/common/traits/crud/CrudServiceTrait.php @@ -117,7 +117,7 @@ trait CrudServiceTrait $config['where']['id'] = $p['id']; break; case 'destroy': - // $config['where']['id'] = $p['id']; + $config['where']['id'] = $p['id']; break; } diff --git a/app/common/validate/InquiryLogValidate.php b/app/common/validate/InquiryLogValidate.php index 58058cb..aac6495 100644 --- a/app/common/validate/InquiryLogValidate.php +++ b/app/common/validate/InquiryLogValidate.php @@ -69,9 +69,14 @@ class InquiryLogValidate extends Validate 'store' => [ 'enter_user_id', 'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content' ], - 'update' => [ 'quality', 'channel', 'email', 'tel', 'whatsapp', 'country', 'contacts', 'content' + ], + 'release' => [ + 'status', 'r_time' + ], + 'allocation' => [ + 'status', 't_time' ] ]; diff --git a/app/inquiry/controller/InquiryLogController.php b/app/inquiry/controller/InquiryLogController.php index 8fbd985..8bc6761 100644 --- a/app/inquiry/controller/InquiryLogController.php +++ b/app/inquiry/controller/InquiryLogController.php @@ -4,6 +4,7 @@ namespace app\inquiry\controller; use app\common\controller\BaseController; use app\common\traits\crud\CrudControllerTrait; use app\inquiry\service\InquiryLogService; +use support\Request; class InquiryLogController extends BaseController { @@ -24,6 +25,11 @@ class InquiryLogController extends BaseController */ protected function indexFormat($data) { + $data['countdown'] = time()-1; + if (isset($data['r_time'])) { + $countdown = 60 * 60; + $data['countdown'] = (time() - $data['r_time']) < $countdown ? $data['r_time'] + (60*60) : time()-1; + } if (isset($data['created_at'])) $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); if (isset($data['updated_at'])) @@ -32,538 +38,41 @@ class InquiryLogController extends BaseController $data['r_time'] = $data['r_time'] > 0 ? date('Y-m-d H:i:s', $data['r_time']) : 0; if (isset($data['t_time'])) $data['t_time'] = $data['t_time'] > 0 ? date('Y-m-d H:i:s', $data['t_time']) : 0; + return $data; } - - // 列表 - public function list(Request $request) - { - if ($request->method() == 'POST') { - $data = $request->all(); - $limit = isset($data['limit']) && $data['limit'] ? $data['limit'] : 10; - - // 状态 - $roleId = $request->user->role_id; - $where = []; - if (in_array($roleId, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::KEFU_ROLE_ID])) { - if (isset($data["status"]) && $data["status"] != "") { - $where[] = [ - "status", "=", $data["status"] - ]; - } else { - $where[] = [ - "status", ">", -1 - ]; - } - } else { - $where[] = [ - "status", "=", 1 - ]; - } - - // 检索渠道 - if (isset($data["channel"]) && $data["channel"]) { - $where[] = [ - "log_type", "=", $data["channel"] - ]; - } - - // 检索国家 - if (isset($data["country"]) && $data["country"]) { - $where[] = [ - "log_country", "=", $data["country"] - ]; - } - - // 检索时间 - if (isset($data["ld_time"]) && $data["ld_time"]) { - $times = explode(' - ', $data["ld_time"]); - $specificDayStart = trim($times[0]); - $specificDayEnd = trim($times[1]); - $where[] = [ - "c_time", ">", strtotime($specificDayStart) - ]; - $where[] = [ - "c_time", "<=", strtotime($specificDayEnd) - ]; - } - - // 检查渠道(不取缓存中的值) - // $channel = $request->user->channel; - $uinfos = UsersModel::where('id', $request->user->uid)->first(); - $channel = $uinfos->channel; - if ($channel) { - $channel = explode(",", $channel); - $InquiryLogModelModel = InquiryLogModel::where($where)->whereIn("log_type", $channel); - } else { - $InquiryLogModelModel = InquiryLogModel::where($where); - } - - // 关键字 - if (isset($data["kwd"]) && $data["kwd"] != "") { - $InquiryLogModelModel->whereRaw("concat(`log_contact_type`,`log_contact_tel`,`log_contact_whatsapp`,`log_contacts`,`order_no`) like '%".$data['kwd']."%'"); - } - - $list = $InquiryLogModelModel - ->with([ - "eUserInfo"=>function($query) { - $query->where("id", ">", 0); - }, "rUserInfo"=>function($query) { - $query->where("id", ">", 0); - }, "countryInfo" - ]) - ->orderBy("id", "desc") - ->paginate($limit); - - $configsModel = new ConfigsModel(); - $logLevel = $configsModel->getConfig("log_level"); - $countdown = (int)($configsModel->getConfig("score_countdown")) * 60; - $inquiryChannelList = ChannelModel::all(['id', 'name']); - $inquiryChannel = []; - foreach ($inquiryChannelList as $key => $val) { - $inquiryChannel[$val->id] = $val->name; - } - - $arr = []; - foreach ($list->items() as $key => $val) { - $expiry_time = $val->r_time + $countdown; - $is_expiry = 0; - $expiry_ts = 0; - if ($expiry_time <= time()) { - $is_expiry = 1; - } else { - $diff = $expiry_time - (int)time(); - $expiry_ts = $diff * 1000; - } - $rUinfo = ""; - if ($val->rUserInfo) { - $rUinfo = $val->rUserInfo->name; - } - $eUinfo = ""; - if ($val->eUserInfo) { - $eUinfo = $val->eUserInfo->name; - } - - /** - * 显示联系方式条件: - * 1.已跟单并且当前用户是接单人 - * 2.主权限 - * 3.当前用户是录单人 - */ - if ( - ($val->status == 2 && $request->user->uid == $val->recipient) || - ($roleId == RoleRulesModel::ALL_ROLE_ID) || - ($request->user->uid == $val->enter_user_id) - ) { - $log_contact_type = $val->log_contact_type == "" ? " 无 " : $val->log_contact_type; - $log_contact_tel = $val->log_contact_tel == "" ? " 无 " : $val->log_contact_tel; - $log_contact_whatsapp = $val->log_contact_whatsapp == "" ? " 无 " : $val->log_contact_whatsapp; - } else { - $log_contact_type = "******"; - $log_contact_tel = "******"; - $log_contact_whatsapp = "******"; - } - $arr[] = [ - "id" => $val->id, - "order_no" => $val->order_no, - "enter_user_id" => $val->enter_user_id, - "recipient" => $val->recipient, - "log_quality" => $val->log_quality, - "log_quality_desc" => $logLevel[$val->log_quality], - "log_quality_color" => InquiryLogModel::LEVEL_BUTTON_COLOR[$val->log_quality], - "log_type" => $val->log_type, - "log_type_desc" => $inquiryChannel[$val->log_type], - "log_contact_type" => $log_contact_type, - "log_contact_tel" => $log_contact_tel, - "log_contact_whatsapp" => $log_contact_whatsapp, - "log_country" => $val->countryInfo->cn_addr."(".$val->countryInfo->two_letter.")", - "log_country_steering" => $val->countryInfo->steering, - "log_contacts" => $val->log_contacts, - "log_content" => $val->log_content, - "e_time" => $val->status==1?date("Y-m-d H:i:s", $expiry_time):"", - "status" => $val->status, - "status_color" => InquiryLogModel::STATUS_BUTTON_COLOR[$val->status], - "status_desc" => InquiryLogModel::STATUS[$val->status], - "is_expiry" => $is_expiry, - "expiry_ts" => $expiry_ts, - "rUinfo" => $rUinfo, - "eUinfo" => $eUinfo, - "r_time" => $val->r_time?date("Y-m-d H:i:s", $val->r_time):"", - "n_time" => date("Y-m-d H:i:s", time()), - "c_time" => date("Y-m-d H:i:s", strtotime($val->c_time)), - "u_time" => date("Y-m-d H:i:s", strtotime($val->u_time)) - ]; - } - $data = [ - 'lastPage' => $list->lastPage(), - 'currentPage' => $list->currentPage(), - 'hasMorePages' => $list->hasMorePages(), - 'list' => $arr, - 'counts' => $list->total() - ]; - return $this->successResponse($data); - } - return view('inquiry-log/list'); - } - - // 添加 - public function add(Request $request) - { - if ($request->method() == "POST") { - $enterUserId = $request->user->uid; - $data = $request->all(); - - if ($data["log_contact_type"]) { - $check1 = InquiryLogModel::where("log_contact_type", trim($data["log_contact_type"]))->first(); - if ($check1 != NULL) { - return $this->errorResponse('邮箱-联系方式已存在'); - } - } - - if ($data["log_contact_tel"]) { - $check2 = InquiryLogModel::where("log_contact_tel", trim($data["log_contact_tel"]))->first(); - if ($check2 != NULL) { - return $this->errorResponse('电话-联系方式已存在'); - } - } - - if ($data["log_contact_whatsapp"]) { - $check3 = InquiryLogModel::where("log_contact_whatsapp", trim($data["log_contact_whatsapp"]))->first(); - if ($check3 != NULL) { - return $this->errorResponse('whatsapp-联系方式已存在'); - } - } - - if (!$data["log_contact_type"] && !$data["log_contact_tel"] && !$data["log_contact_whatsapp"]) { - $check4 = InquiryLogModel::where("log_contacts", trim($data["log_contacts"]))->where("log_country", $data["country"])->first(); - if ($check4 != NULL) { - return $this->errorResponse('联系人已存在'); - } - } - - $model = new InquiryLogModel(); - $model->order_no = orderNum(); - $model->enter_user_id = $enterUserId; - $model->recipient = 0; - $model->log_type = $data["log_type"]; - $model->log_contact_type = trim($data["log_contact_type"]); - $model->log_contact_tel = trim($data["log_contact_tel"]); - $model->log_contact_whatsapp = trim($data["log_contact_whatsapp"]); - $model->log_country = $data["country"]; - $model->log_contacts = $data["log_contacts"]; - $model->log_content = $data["log_content"]; - $model->log_quality = $data["log_quality"]; - $model->status = 0; - if ($model->save()) { - return $this->successResponse([], '添加成功'); - } else { - return $this->errorResponse('添加失败,请重试'); - } - } - return view('inquiry-log/add'); - } - - // 编辑 - public function edit(Request $request) - { - if ($request->method() == "POST") { - $data = $request->all(); - $model = new InquiryLogModel(); - $info = $model->where("id", $data["id"])->first(); - - if ($data["log_contact_type"]) { - $check1 = InquiryLogModel::where("log_contact_type", trim($data["log_contact_type"]))->first(); - if ($check1 != NULL && $check1->id != $data["id"]) { - return $this->errorResponse('邮箱-联系方式已存在'); - } - } - - if ($data["log_contact_tel"]) { - $check2 = InquiryLogModel::where("log_contact_tel", trim($data["log_contact_tel"]))->first(); - if ($check2 != NULL && $check2->id != $data["id"]) { - return $this->errorResponse('电话-联系方式已存在'); - } - } - - if ($data["log_contact_whatsapp"]) { - $check3 = InquiryLogModel::where("log_contact_whatsapp", trim($data["log_contact_whatsapp"]))->first(); - if ($check3 != NULL && $check3->id != $data["id"]) { - return $this->errorResponse('whatsapp-联系方式已存在'); - } - } - - if (!$data["log_contact_type"] && !$data["log_contact_tel"] && !$data["log_contact_whatsapp"]) { - $check4 = InquiryLogModel::where("log_contacts", trim($data["log_contacts"]))->where("log_country", $data["country"])->first(); - if ($check4 != NULL && $check4->id != $data["id"]) { - return $this->errorResponse('联系人已存在'); - } - } - - $info->log_type = $data["log_type"]; - $info->log_contact_type = $data["log_contact_type"]; - $info->log_contact_tel = $data["log_contact_tel"]; - $info->log_contact_whatsapp = $data["log_contact_whatsapp"]; - $info->log_country = $data["country"]; - $info->log_contacts = $data["log_contacts"]; - $info->log_content = $data["log_content"]; - $info->log_quality = $data["log_quality"]; - if (!$info->save()) { - return $this->errorResponse('操作失败,请重试'); - } - return $this->successResponse([], '操作成功'); - } - return view('inquiry-log/edit'); - } - - // 删除 - public function del(Request $request) - { - if ($request->method() == "POST") { - $id = $request->post("id"); - $rst = InquiryLogModel::where("id", $id)->first(); - if (!$rst) { - return $this->errorResponse('询单未找到'); - } - if ($rst->enter_user_id != $request->user->uid) { - return $this->errorResponse('只能删除自己录入的询单'); - } - $res = InquiryLogModel::where("id", $id)->where("enter_user_id", $request->user->uid)->delete(); - if (!$res) { - return $this->errorResponse('操作失败,请重试'); - } - fileLog('ID:'.$request->user->uid.' - 删除:询单ID:'.$id); - return $this->successResponse([], '操作成功'); - } - return $this->errorResponse('请求方式错误'); - } - - // 单条信息 - public function info(Request $request) - { - if ($request->method() == "POST") { - $id = $request->all("id"); - $info = InquiryLogModel::where("id", $id)->first()->toArray(); - $list = CountryModel::all(["cn_addr", "two_letter", "id"]); - $arr = []; - foreach ($list as $vo) { - $arr[] = [ - "value" => $vo->id, - "name" => $vo->cn_addr."(".$vo->two_letter.")" - ]; - } - $info["guojia"] = $arr; - $info["guojia_id"] = [$info["log_country"]]; - - $configsModel = new ConfigsModel(); - // $data = $configsModel->getConfig("inquiry_channel"); - $inquiryChannelList = ChannelModel::all(['id', 'name']); - $data = []; - foreach ($inquiryChannelList as $key => $val) { - $data[$val->id] = $val->name; - } - - $arr = []; - foreach ($data as $ky => $vo) { - $arr[] = [ - "id" => $ky, - "name" => $vo - ]; - } - $info["qudao"] = $arr; - - $data1 = $configsModel->getConfig("log_level"); - $arr1 = []; - foreach ($data1 as $ky => $vo) { - $arr1[] = [ - "id" => $ky, - "name" => $vo - ]; - } - $info["dengji"] = $arr1; - if (!$info) { - return $this->errorResponse('查询失败,请重试'); - } - $info["log_content"] = strTh($info["log_content"]); - return $this->successResponse($info, 'success'); - } - return $this->errorResponse('请求方式错误'); - } - - // 立即发布 + /** + * 发布 + * + * @param Request $request + * @return mixed + */ public function release(Request $request) { try { - if ($request->method() == "POST") { - $id = $request->post("id"); - if (!in_array($request->user->role_id, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::KEFU_ROLE_ID])) { - return $this->errorResponse('无操作权限'); - } - $info = InquiryLogModel::where("id", $id)->first(); - if (!$info) { - return $this->errorResponse('询单信息未找到'); - } - if ($info->status != 0) { - return $this->errorResponse('状态异常,无法发布'); - } - $info->r_time = time(); - $info->status = 1; - if (!$info->save()) { - return $this->errorResponse('发布失败,请重试'); - } - - // 发送飞书群消息通知 - $data = ["order_id"=>$id]; - RedisQueueRedis::send("release", $data); - // 发送飞书群消息通知 - - return $this->successResponse([], '发布成功'); - } - return $this->errorResponse('电话-请求方式错误'); - } catch (\Throwable $th) { - fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); - return $this->errorResponse($th->getMessage()); + $data = $this->validate($this->service->getValidator(), $request->post(), 'release'); + $res = $this->service->serviceRelease($request, $data); + return $this->success([], '发布成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); } } - // 立即跟单 - public function track(Request $request) - { - // Db::beginTransaction(); - try { - if ($request->method() == "POST") { - $id = $request->post("id"); - $uid = $request->user->uid; - if (!in_array($request->user->role_id, [RoleRulesModel::ALL_ROLE_ID, RoleRulesModel::YEWU_ROLE_ID])) { - // Db::rollBack(); - return $this->errorResponse('无操作权限'); - } - $info = InquiryLogModel::where("id", $id)->first(); - if (!$info) { - return $this->errorResponse('询单信息未找到'); - } - if ($info->status != 1) { - // Db::rollBack(); - return $this->errorResponse('状态异常,无法跟单'); - } - // 更新询单 - $info->recipient = $uid; - $info->t_time = time(); - $info->status = 2; - // 记录跟单 - $model = new UserOrderModel(); - $model->uid = $request->user->uid; - $model->log_id = $id; - $model->score = (new InquiryLogModel())->score($info->log_quality, $info->r_time); - if (!$info->save() || !$model->save()) { - // Db::rollBack(); - return $this->errorResponse('操作失败,请重试'); - } - // Db::commit(); - - // // 发送飞书群消息通知 - $data1 = ["order_id"=>$id]; - RedisQueueRedis::send("track", $data1); - // // 发送飞书群消息通知 - - // // 同步询单到crm - $data2 = ["order_id"=>$id]; - RedisQueueRedis::send("inquiry-sync", $data2); - // // 同步询单到crm - - return $this->successResponse([], '跟单操作成功'); - } else { - // Db::rollBack(); - return $this->errorResponse('请求方式错误'); - } - } catch (\Throwable $th) { - // Db::rollBack(); - fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); - return $this->errorResponse($th->getMessage()); - } - } - - // 详情 - public function detail(Request $request) - { - if ($request->method() == "POST") { - $id = $request->all("id"); - $res = InquiryLogModel::where("id", $id)->first()->toArray(); - return $this->successResponse(["detail"=>$res["log_content"]], '操作成功'); - - } - return view('inquiry-log/detail'); - } - - // 客服分单给业务 + /** + * 分配 + * + * @param Request $request + * @return mixed + */ public function allocation(Request $request) { - // Db::beginTransaction(); try { - if ($request->method() == "POST") { - $data = $request->all(); - - $userInfo = UsersModel::where("id", $data["gd_user"])->first(); - if (!$userInfo) { - // Db::rollBack(); - return $this->errorResponse('用户未找到'); - } - - $model = new InquiryLogModel(); - $info = $model->where("id", $data["id"])->first(); - if (!$info) { - // Db::rollBack(); - return $this->errorResponse('询单未找到'); - } - - $uomodel = new UserOrderModel(); - $uoInfo = $uomodel->where("log_id", $data["id"])->first(); - if (!$uoInfo) { - // 记录跟单 - $HsUserOrderModel = new UserOrderModel(); - $HsUserOrderModel->uid = $data["gd_user"]; - $HsUserOrderModel->log_id = $data["id"]; - $HsUserOrderModel->score = (new InquiryLogModel())->score($info->log_quality, $info->r_time); - if (!$HsUserOrderModel->save()) { - // Db::rollBack(); - return $this->errorResponse('记录跟单操作失败,请重试'); - } - } else { - $uoInfo->uid = $data["gd_user"]; - if (!$uoInfo->save()) { - // Db::rollBack(); - return $this->errorResponse('操作失败,请重试'); - } - } - - if ($info->status != 2) { - $info->t_time = time(); - $info->status = 2; - } - $info->recipient = $data["gd_user"]; - if (!$info->save()) { - // Db::rollBack(); - return $this->errorResponse('操作失败,请重试'); - } - // Db::commit(); - - // 发送飞书群消息通知 - $data1 = ["order_id"=>$data["id"]]; - RedisQueueRedis::send("allocation", $data1); - // 发送飞书群消息通知 - - // // 同步询单到crm - $data2 = ["order_id"=>$data["id"]]; - RedisQueueRedis::send("inquiry-sync", $data2); - // // 同步询单到crm - - return $this->successResponse([], '操作成功'); - } - return view('inquiry-log/allocation'); - } catch (\Throwable $th) { - // Db::rollBack(); - fileLog($th->getFile()."|".$th->getLine()."|".$th->getMessage()); - return $this->errorResponse($th->getMessage()); + $data = $this->validate($this->service->getValidator(), $request->post(), 'allocation'); + $res = $this->service->serviceAllocation($request, $data); + return $this->success([], '分配成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); } } diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 754280d..05b371d 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -5,6 +5,7 @@ namespace app\inquiry\service; use app\common\validate\InquiryLogValidate; use app\common\traits\crud\CrudServiceTrait; use app\common\model\InquiryLog; +use support\exception\BusinessException; use support\Request; class InquiryLogService @@ -30,7 +31,31 @@ class InquiryLogService return new $this->validator; } + /** + * 发布 + */ + public function serviceRelease(Request $request, array $data = []) + { + $data = $this->preProcess('Release', $data, $request); + $config = $this->buildQueryConfig($request, 'release'); + $updated = $this->logicUpdateBy($config, $data); + return $this->postProcess('Release', $updated); + } + /** + * 分配 + */ + public function serviceAllocation(Request $request, array $data = []) + { + $data = $this->preProcess('Allocation', $data, $request); + $config = $this->buildQueryConfig($request, 'allocation'); + $updated = $this->logicUpdateBy($config, $data); + return $this->postProcess('Allocation', $updated); + } + + /** + * 构建查询条件 + */ protected function buildQueryConfig(Request $request, string $scene = 'index'): array { $p = $request->all(); @@ -39,19 +64,18 @@ class InquiryLogService $config['model'] = $this->modelClass; switch ($scene) { case 'index': - // 排序 + // ====== ====== 排序 ====== ====== $config['order']['id'] = 'desc'; + // ====== ====== 筛选 ====== ====== // 渠道 if (isset($p['channel']) && $p['channel']) { $config['where']['channel'] = $p['channel']; } - // 国家 if (isset($p['country']) && $p['country']) { $config['where']['country'] = $p['country']; } - // 时间 if (isset($p['time']) && $p['time']) { $time = explode(' - ', $p['time']); @@ -59,12 +83,10 @@ class InquiryLogService 'between', [strtotime($time[0]), strtotime($time[1])] ]; } - // 状态 if (isset($p['status']) && $p['status']) { $config['where']['status'] = $p['status']; } - // 关键字 if (isset($p['keyword']) && $p['keyword']) { $k = '%' . $p['keyword'] . '%'; @@ -73,28 +95,29 @@ class InquiryLogService } } - // 关联 + // ====== ====== 关联 ====== ====== $config['with']['country'][] = 'cn_addr'; + $config['with']['country'][] = 'steering'; $config['with']['enter'][] = 'name'; $config['with']['recipient'][] = 'name'; $config['with']['channel'][] = 'name'; break; case 'show': - $config['where']['id'] = $p['id']; - break; - case 'store': - break; case 'update': - $config['where']['id'] = $p['id']; - break; case 'destroy': + case 'release': + case 'allocation': $config['where']['id'] = $p['id']; + case 'store': break; } return $config; } + /** + * 添加前置处理 + */ public function beforeServiceStore($data, Request $request) { $data['order_no'] = orderNum(); @@ -106,10 +129,37 @@ class InquiryLogService return $data; } - // public function afterServiceList($data) - // { - // var_dump($data); + /** + * 发布前置处理 + */ + public function beforeServiceRelease($data, Request $request) + { + $info = $this->findByPk($request->post('id')); + if (!$info) { + throw new BusinessException('数据不存在'); + } + if ($info->status != 0) { + throw new BusinessException('询单已发布'); + } + $data['r_time'] = time(); + $data['status'] = 1; + return $data; + } - // return $data; - // } + /** + * 分配前置处理 + */ + public function beforeServiceAllocation($data, Request $request) + { + $info = $this->findByPk($request->post('id')); + if (!$info) { + throw new BusinessException('数据不存在'); + } + if ($info->status == 2) { + throw new BusinessException('询单已被分配'); + } + $data['t_time'] = time(); + $data['status'] = 2; + return $data; + } } \ No newline at end of file diff --git a/config/route.php b/config/route.php index 6238ffd..ddd93d8 100644 --- a/config/route.php +++ b/config/route.php @@ -81,6 +81,8 @@ Route::group('/admin', function () { Route::any('/store', [app\inquiry\controller\InquiryLogController::class, 'store']); Route::any('/update', [app\inquiry\controller\InquiryLogController::class, 'update']); Route::any('/destroy', [app\inquiry\controller\InquiryLogController::class, 'destroy']); + Route::any('/release', [app\inquiry\controller\InquiryLogController::class, 'release']); + Route::any('/allocation', [app\inquiry\controller\InquiryLogController::class, 'allocation']); }); }); From d55b6bd4d9c8c69347ab7df2763a2412e6462551 Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 23 Jul 2025 18:41:47 +0800 Subject: [PATCH 083/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=8C=E6=AD=A5crm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/model/CrmCustomer.php | 49 ++++++++++++ app/common/model/User.php | 2 +- app/controller/IndexController.php | 16 +++- app/inquiry/service/InquiryLogService.php | 28 ++++++- app/queue/redis/InquiryToCrm.php | 83 ++++++++++++++++++++ config/plugin/webman/redis-queue/process.php | 2 +- 6 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 app/common/model/CrmCustomer.php create mode 100644 app/queue/redis/InquiryToCrm.php diff --git a/app/common/model/CrmCustomer.php b/app/common/model/CrmCustomer.php new file mode 100644 index 0000000..e5a40b7 --- /dev/null +++ b/app/common/model/CrmCustomer.php @@ -0,0 +1,49 @@ +orWhere('phone', $account) ->orWhere('email', $account); } -} +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index 48977c2..c2a23df 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -13,9 +13,9 @@ use support\Cache; class IndexController { - protected $noNeedLogin = ['index', 'view', 'perm', 'createResource']; + protected $noNeedLogin = ['index', 'view', 'perm', 'createResource', 'test']; - protected $noNeedHeaderData = ['index', 'view', 'perm', 'createResource']; + protected $noNeedHeaderData = ['index', 'view', 'perm', 'createResource', 'test']; public function index(Request $request) { @@ -76,7 +76,7 @@ class IndexController // ], 'model' => [ 'module' => 'common', - 'name' => 'inquiry_channel' + 'name' => 'crm_customer' ], // 'service' => [ // 'module' => 'inquiry', @@ -90,10 +90,18 @@ class IndexController // 'module' => 'inquiry', // 'name' => 'country' // ], - 'table' => 'inquiry_channel' + 'table' => 'crm_customer' ]; $result = (new Generate($options))->doGenerate(); return $result; } + + + public function test() + { + $res = ['id'=>25714]; + \Webman\RedisQueue\Redis::send('inquiry-to-crm', $res); + return 'success'; + } } \ No newline at end of file diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 05b371d..d8be346 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -7,6 +7,7 @@ use app\common\traits\crud\CrudServiceTrait; use app\common\model\InquiryLog; use support\exception\BusinessException; use support\Request; +use Webman\RedisQueue\Redis as RedisQueueRedis; class InquiryLogService { @@ -39,7 +40,7 @@ class InquiryLogService $data = $this->preProcess('Release', $data, $request); $config = $this->buildQueryConfig($request, 'release'); $updated = $this->logicUpdateBy($config, $data); - return $this->postProcess('Release', $updated); + return $this->postProcess('Release', $data); } /** @@ -50,7 +51,7 @@ class InquiryLogService $data = $this->preProcess('Allocation', $data, $request); $config = $this->buildQueryConfig($request, 'allocation'); $updated = $this->logicUpdateBy($config, $data); - return $this->postProcess('Allocation', $updated); + return $this->postProcess('Allocation', $data); } /** @@ -139,7 +140,7 @@ class InquiryLogService throw new BusinessException('数据不存在'); } if ($info->status != 0) { - throw new BusinessException('询单已发布'); + throw new BusinessException('询单已被发布'); } $data['r_time'] = time(); $data['status'] = 1; @@ -158,8 +159,29 @@ class InquiryLogService if ($info->status == 2) { throw new BusinessException('询单已被分配'); } + if ($info->status == 0) { + $data['r_time'] = time(); + } $data['t_time'] = time(); $data['status'] = 2; return $data; } + + /** + * 发布后置处理 + */ + protected function afterServiceRelease($res) + { + RedisQueueRedis::send('inquiry-to-crm', $res); + return $res; + } + + /** + * 分配后置处理 + */ + protected function afterServiceAllocation($res) + { + RedisQueueRedis::send('inquiry-to-crm', $res); + return $res; + } } \ No newline at end of file diff --git a/app/queue/redis/InquiryToCrm.php b/app/queue/redis/InquiryToCrm.php new file mode 100644 index 0000000..36e70dc --- /dev/null +++ b/app/queue/redis/InquiryToCrm.php @@ -0,0 +1,83 @@ +where('sync_crm', 0)->first(); + if ($info) { + // 获取用户信息 + $recipient = 0; + $corpId = 0; + if ($info->recipient) { + $recipient = $info->recipient; + $uinfo = User::where('id', $info->recipient)->first(); + if ($uinfo) { + $corpId = $uinfo->corp_id; + } + } + // 同步客户数据 + $crmCustomer = new CrmCustomer(); + $crmCustomer->sn = $info->order_no; + $crmCustomer->corp_abbr = $corpId; + $crmCustomer->user_id = $recipient; + $crmCustomer->type = 1; + $crmCustomer->channel_id = $info->channel; + $crmCustomer->country_id = $info->country; + $crmCustomer->magnitude = $info->quality - 1; + $crmCustomer->email = $info->email; + $crmCustomer->phone = $info->tel; + $crmCustomer->WhatsApp = $info->whatsapp; + $crmCustomer->contacts = $info->contacts; + $crmCustomer->desc_collect = $info->content; + $crmCustomer->enter_user_id = $info->enter_user_id; + // 修改同步CRM状态 + $info->sync_crm = 1; + // 保存 + if (!$crmCustomer->save() || !$info->save()) { + throw new BusinessException('同步失败~'); + } + } + } catch (\Throwable $th) { + throw $th; + } + } + + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/process.php b/config/plugin/webman/redis-queue/process.php index 9fac733..8474aee 100644 --- a/config/plugin/webman/redis-queue/process.php +++ b/config/plugin/webman/redis-queue/process.php @@ -5,7 +5,7 @@ return [ 'count' => getenv('REDIS_QUEUE_PROCESS_COUNT'), // 可以设置多进程同时消费 'constructor' => [ // 消费者类目录 - // 'consumer_dir' => app_path() . '/queue/redis' + 'consumer_dir' => app_path() . '/queue/redis', 'consumer_dir' => app_path() . getenv('REDIS_QUEUE_CONSUMER_DIR') ] ] From 9fdf2dabdeaf3b9d25e5a736c416e3148a8dbbb3 Mon Sep 17 00:00:00 2001 From: mzl Date: Wed, 23 Jul 2025 19:00:27 +0800 Subject: [PATCH 084/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=A2=E5=8D=95?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E5=99=A8&=E7=8A=B6=E6=80=81=E8=8C=83?= =?UTF-8?q?=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/validate/InquiryLogValidate.php | 75 ++++++++++++++++++---- app/inquiry/service/InquiryLogService.php | 2 +- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/app/common/validate/InquiryLogValidate.php b/app/common/validate/InquiryLogValidate.php index aac6495..2a869bb 100644 --- a/app/common/validate/InquiryLogValidate.php +++ b/app/common/validate/InquiryLogValidate.php @@ -80,17 +80,29 @@ class InquiryLogValidate extends Validate ] ]; + /** * 验证联系方式唯一性 */ - protected function checkContactUnique($value, $rule, $data) + protected function checkContactUnique($value, $rule, $data, $field, $title) { - // 至少需要提供一种联系方式 + // 如果是更新操作且联系人未修改,跳过检查 + if ($this->currentScene == 'update') { + $original = InquiryLog::find($data['id']); + if ($original && $original->contacts == $value && $original->country == ($data['country'] ?? 0)) { + return true; + } + } + if (empty($data['email']) && empty($data['tel']) && empty($data['whatsapp'])) { - $exists = InquiryLog::where('contacts', trim($data['contacts'])) - ->where('country', $data['country']) - ->exists(); - return !$exists; + $query = InquiryLog::where('contacts', trim($value)) + ->where('country', $data['country']); + + if (!empty($data['id'])) { + $query->where('id', '<>', $data['id']); + } + + return !$query->exists(); } return true; @@ -99,27 +111,66 @@ class InquiryLogValidate extends Validate /** * 验证邮箱联系方式唯一性 */ - protected function checkEmailContactUnique($value) + protected function checkEmailContactUnique($value, $rule, $data, $field, $title) { if (empty($value)) return true; - return !InquiryLog::where('email', trim($value))->exists(); + + // 如果是更新操作且邮箱未修改,跳过检查 + if ($this->currentScene == 'update' && !empty($data['id'])) { + $original = InquiryLog::find($data['id']); + if ($original && $original->email == $value) { + return true; + } + } + + $query = InquiryLog::where('email', trim($value)); + if (!empty($data['id'])) { + $query->where('id', '<>', $data['id']); + } + return !$query->exists(); } /** * 验证电话联系方式唯一性 */ - protected function checkTelContactUnique($value) + protected function checkTelContactUnique($value, $rule, $data, $field, $title) { if (empty($value)) return true; - return !InquiryLog::where('tel', trim($value))->exists(); + + // 如果是更新操作且电话未修改,跳过检查 + if ($this->currentScene == 'update' && !empty($data['id'])) { + $original = InquiryLog::find($data['id']); + if ($original && $original->tel == $value) { + return true; + } + } + + $query = InquiryLog::where('tel', trim($value)); + if (!empty($data['id'])) { + $query->where('id', '<>', $data['id']); + } + return !$query->exists(); } /** * 验证WhatsApp联系方式唯一性 */ - protected function checkWhatsappContactUnique($value) + protected function checkWhatsappContactUnique($value, $rule, $data, $field, $title) { if (empty($value)) return true; - return !InquiryLog::where('whatsapp', trim($value))->exists(); + + // 如果是更新操作且WhatsApp未修改,跳过检查 + if ($this->currentScene == 'update' && !empty($data['id'])) { + $original = InquiryLog::find($data['id']); + if ($original && $original->whatsapp == $value) { + return true; + } + } + + $query = InquiryLog::where('whatsapp', trim($value)); + if (!empty($data['id'])) { + $query->where('id', '<>', $data['id']); + } + return !$query->exists(); } } \ No newline at end of file diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index d8be346..759c8c4 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -85,7 +85,7 @@ class InquiryLogService ]; } // 状态 - if (isset($p['status']) && $p['status']) { + if (isset($p['status']) && in_array($p['status'], [0,1,2])) { $config['where']['status'] = $p['status']; } // 关键字 From ff44c642ffa43487c2493ad11d7db1ea5ece88e5 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Thu, 24 Jul 2025 08:59:26 +0800 Subject: [PATCH 085/120] =?UTF-8?q?=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 8eee3ac..a9796f2 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -124,8 +124,8 @@ class CustomerController extends BaseController { $userId = $request->user->uid; $filter = Util::getParam($request, 'filter'); - $page = $request->input('page', 1); - $perPage = $request->input('per_page', 10); + $page = $request->input('current', 1); + $perPage = $request->input('size', 10); $where = [ ['user_id', '=', $userId], ['corp_abbr', '=', $request->basic_data['corp_abbr']], From be87fce09b637f11183289489c17252db17d09a4 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 09:47:27 +0800 Subject: [PATCH 086/120] =?UTF-8?q?=E5=85=AC=E5=8F=B8id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 7 +++++-- app/crm/controller/FollowController.php | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index a9796f2..81936eb 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -2,6 +2,7 @@ namespace app\crm\controller; +use app\common\model\Corp; use app\common\model\User; use app\crm\model\Channel; use app\crm\model\Country; @@ -249,13 +250,15 @@ class CustomerController extends BaseController return $this->error(trans('contacts_not_null')); } + $corpInfo = Corp::where('abbr', $request->basic_data['corp_abbr'])->first(); + $corpId = $corpInfo->id; $data = [ 'user_id' => $request->user->uid, - 'corp_abbr' => $request->basic_data['corp_abbr'], + 'corp_abbr' => $corpId, 'grade' => Util::getParam($request, 'grade', 0), 'channel_id' => $channel_id, 'country_id' => $country_id, - 'car_type_id' => $car_type_id, +// 'car_type_id' => $car_type_id, 'magnitude' => Util::getParam($request, 'magnitude', 0), 'email' => $email, 'phone' => $phone, diff --git a/app/crm/controller/FollowController.php b/app/crm/controller/FollowController.php index a23749c..caf785f 100644 --- a/app/crm/controller/FollowController.php +++ b/app/crm/controller/FollowController.php @@ -2,6 +2,7 @@ namespace app\crm\controller; +use app\common\model\Corp; use app\crm\model\Customer; use app\crm\model\Follow; use app\crm\service\CustomerService; @@ -96,9 +97,11 @@ class FollowController extends BaseController return $this->error(trans('picture_not_null')); } + $corpInfo = Corp::where('abbr', $request->basic_data['corp_abbr'])->first(); + $corpId = $corpInfo->id; $data = [ 'user_id' => $request->user->uid, - 'corp_abbr' => $request->basic_data['corp_abbr'], + 'corp_abbr' => $corpId, 'customer_id' => $customer_id, 'type' => $type, 'picture' => $picture, From 2b052ec2b43a8b6f3e86472d6d092e9b36b4d6a4 Mon Sep 17 00:00:00 2001 From: mzl Date: Fri, 25 Jul 2025 13:39:23 +0800 Subject: [PATCH 087/120] =?UTF-8?q?=E9=A3=9E=E4=B9=A6=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/NotifyController.php | 136 +++++ app/common/model/CrmCustomer.php | 2 +- app/common/model/InquiryLog.php | 4 +- app/common/utils/FeishuMessage.php | 555 +++++++++++++++++++++ app/controller/IndexController.php | 20 +- app/inquiry/service/InquiryLogService.php | 45 +- app/queue/redis/FeishuMessage.php | 127 +++++ app/queue/redis/InquiryToCrm.php | 20 +- config/route.php | 4 + config/whitelist.php | 13 - 10 files changed, 904 insertions(+), 22 deletions(-) create mode 100644 app/common/controller/NotifyController.php create mode 100644 app/common/utils/FeishuMessage.php create mode 100644 app/queue/redis/FeishuMessage.php delete mode 100644 config/whitelist.php diff --git a/app/common/controller/NotifyController.php b/app/common/controller/NotifyController.php new file mode 100644 index 0000000..9e4fffb --- /dev/null +++ b/app/common/controller/NotifyController.php @@ -0,0 +1,136 @@ +post(); + + // 解析数据 + // $param = json_decode($param['event'], true); + $orderSn = $param['event']['action']['value']; + if (strpos($orderSn, '"') !== false) { + $orderSn = str_replace('"', '', $orderSn); + } + + // 处理询单&客户线索信息 + $openId = $param['event']['operator']['open_id']; + + // 获取询单 + $order = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('order_no', $orderSn)->first(); + if (!$order) { + return $this->error('未找到询单记录'); + } + + // 获取客户xians + $customer = CrmCustomer::where('sn', $orderSn)->first(); + if (!$customer) { + return $this->error('未找到客户线索'); + } + + // 获取用户 + $uinfo = User::where('open_id', $openId)->first(); + + // 更新客户线索 + $customer->user_id = $uinfo->id; + + // 更新询单数据 + $order->recipient = $uinfo->id; + $order->status = 2; + $order->t_time = time(); + + // 保存 + if (!$order->save() || !$customer->save()) { + return $this->error('跟踪失败'); + } + + // 获取飞书消息模板 + $feishuMessage = new FeishuMessage(); + $content = $feishuMessage->groupUpdateTemplates( + (new InquiryLogService())->qualityName($order->quality), + $order->order_no, + $order->channelInfo ? $order->channelInfo->name : '', + $order->countryInfo ? $order->countryInfo->cn_addr : '', + $order->enter ? $order->enter->name : '', + $order->r_time ? date('Y-m-d H:i:s', $order->r_time) : '', + $order->t_time ? date('Y-m-d H:i:s', $order->t_time) : '' + ); + + // 组装飞书消息模板 + $data = '{ + "toast": { + "type": "success", + "content": "操作成功" + }, + "card": { + "type": "raw", + "data": '.$content.' + } + }'; + + // 响应消息 + return new Response(200, ['Content-Type' => 'application/json'], $data); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + public function error($msg = '') + { + $data = '{ + "toast": { + "type": "error", + "content": "'.$msg.'" + } + }'; + return new Response(200, ['Content-Type' => 'application/json'], $data); + } + + /** + * 飞书事件 + * @param Request $request + * @return Response + */ + public function feishuEvent(Request $request) + { + try { + $data = $request->post(); + + var_dump($data); + + return json($data); + } catch (\Throwable $e) { + return json($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/common/model/CrmCustomer.php b/app/common/model/CrmCustomer.php index e5a40b7..49446e6 100644 --- a/app/common/model/CrmCustomer.php +++ b/app/common/model/CrmCustomer.php @@ -40,7 +40,7 @@ class CrmCustomer extends Model /** * 允许自动填充模型字段 */ - protected $fillable = ['id', 'sn', 'corp_abbr', 'user_id', 'user_sn', 'type', 'grade', 'sex', 'channel_id', 'country_id', 'car_type_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'desc_collect', 'address', 'lat', 'lng', 'follow_last', 'follow_next', 'warn', 'follow_remark', 'created_at', 'updated_at', 'deleted_at']; + protected $fillable = ['id', 'sn', 'corp_abbr', 'user_id', 'user_sn', 'type', 'grade', 'sex', 'channel_id', 'country_id', 'car_type_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'desc_collect', 'address', 'lat', 'lng', 'follow_last', 'follow_next', 'warn', 'follow_remark', 'enter_user_id', 'created_at', 'updated_at', 'deleted_at']; /** * 不允许自动填充模型字段 diff --git a/app/common/model/InquiryLog.php b/app/common/model/InquiryLog.php index b8820b1..ce01b4a 100644 --- a/app/common/model/InquiryLog.php +++ b/app/common/model/InquiryLog.php @@ -65,12 +65,12 @@ class InquiryLog extends Model return $this->hasOne(User::class, 'id', 'recipient'); } - public function channel() + public function channelInfo() { return $this->hasOne(InquiryChannel::class, 'id', 'channel'); } - public function country() + public function countryInfo() { return $this->hasOne(Country::class, 'id', 'country'); } diff --git a/app/common/utils/FeishuMessage.php b/app/common/utils/FeishuMessage.php new file mode 100644 index 0000000..311ffd9 --- /dev/null +++ b/app/common/utils/FeishuMessage.php @@ -0,0 +1,555 @@ +class = new Feishu($this->app_id, $this->app_secret); + } + + /** + * 飞书消息模板 + * @param string $scene + * @param array $options + */ + public function messageTemplates(string $scene = 'group-send', array $options = []) + { + try { + $template = match ($scene) { + 'inquiry-group-send' => $this->groupNewTemplates( + $options['quality'], + $options['order_no'], + $options['channel'], + $options['country'], + $options['enter_user'], + $options['r_time'] + ), + 'inquiry-group-update' => $this->groupUpdateTemplates( + $options['quality'], + $options['order_no'], + $options['channel'], + $options['country'], + $options['enter_user'], + $options['r_time'], + $options['t_time'] + ), + 'inquiry-person-allocation' => $this->personAllocationTemplates( + $options['quality'], + $options['order_no'], + $options['channel'], + $options['country'], + $options['enter_user'], + $options['r_time'], + $options['t_time'] + ), + 'crm-person-follow-up' => $this->personFollowUpTemplates( + $options['contacts'], + $options['follow_next'], + $options['follow_last'] + ) + }; + } catch (\UnhandledMatchError $e) { + throw new BusinessException($e->getMessage()); + } + return $template; + } + + /** + * 发送飞书消息 + * @param array $options + */ + public function messageSend(string $scene = 'group-send', array $options = []) + { + $template = $this->messageTemplates($scene, $options); + $result = $this->class->sendMessages($options['receive_id'], $template, $options['msg_type'], $options['receive_id_type']); + if (!isset($result['code']) || $result['code'] !== 0) { + throw new BusinessException('发送飞书消息失败'); + } + return $result; + } + + /** + * 线索消息发送 + */ + public function groupNewTemplates($quality = '💛 银牌商机', $order_no = 'SN2025072365640', $channel = '谷歌', $country = '白俄罗斯', $enter_user = '木子李', $r_time = '2025-07-24 09:41:23') + { + $content = '{ + "schema": "2.0", + "config": { + "update_multi": true, + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "16px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 20px 20px 20px", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": " **'.$quality.'**\n- **编号:** '.$order_no.'\n- **渠道:** '.$channel.'\n- **国家:** '.$country.'\n- **发布人:** '.$enter_user.'\n- **发布时间:** '.$r_time.'", + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ] + }, + { + "tag": "form", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "flex_mode": "stretch", + "horizontal_spacing": "8px", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "auto", + "elements": [ + { + "tag": "button", + "text": { + "tag": "plain_text", + "content": "立即跟踪" + }, + "type": "primary_filled", + "width": "fill", + "size": "medium", + "icon": { + "tag": "standard_icon", + "token": "list-check-bold_outlined" + }, + "confirm": { + "title": { + "tag": "plain_text", + "content": "确认跟踪?" + }, + "text": { + "tag": "plain_text", + "content": "客户线索:'.$order_no.'" + } + }, + "behaviors": [ + { + "type": "callback", + "value": "'.$order_no.'" + } + ], + "form_action_type": "submit", + "name": "Button_m7d0zqa2", + "element_id": "track" + } + ], + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top" + }, + { + "tag": "column", + "width": "auto", + "elements": [], + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top" + }, + { + "tag": "column", + "width": "auto", + "elements": [], + "padding": "0px 0px 0px 0px", + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "margin": "0px 0px 0px 0px" + } + ], + "margin": "4px 0px 0px 0px" + } + ], + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "12px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 0px 0px 0px", + "margin": "0px 0px 0px 0px", + "name": "Form_m7d0zqa1" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "新的【公海】客户线索" + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "template": "default", + "icon": { + "tag": "standard_icon", + "token": "ai-common_colorful" + }, + "padding": "12px 12px 12px 20px" + } + }'; + return $content; + } + + /** + * 线索消息更新 + */ + public function groupUpdateTemplates($quality = '💛 银牌商机', $order_no = 'SN2025072365640', $channel = '谷歌', $country = '白俄罗斯', $enter_user = '木子李', $r_time = '2025-07-24 09:41:23', $t_time = '2025-07-24 14:41:23') + { + $content = '{ + "schema": "2.0", + "config": { + "update_multi": true, + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "16px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 20px 20px 20px", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": " **'.$quality.'**\n- **编号:** '.$order_no.'\n- **渠道:** '.$channel.'\n- **国家:** '.$country.'\n- **发布人:** '.$enter_user.'\n- **发布时间:** '.$r_time.'", + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ] + }, + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "div", + "text": { + "tag": "plain_text", + "content": "跟踪时间:'.$t_time.'", + "text_size": "notation", + "text_align": "left", + "text_color": "green" + }, + "icon": { + "tag": "standard_icon", + "token": "chat-done_outlined", + "color": "green" + }, + "element_id": "track_time" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "【公海】客户线索" + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "text_tag_list": [ + { + "tag": "text_tag", + "text": { + "tag": "plain_text", + "content": "已跟踪" + }, + "color": "orange" + } + ], + "template": "green", + "icon": { + "tag": "standard_icon", + "token": "ai-common_colorful" + }, + "padding": "12px 12px 12px 20px" + } + }'; + return $content; + } + + /** + * 线索分配消息 + */ + public function personAllocationTemplates($quality = '💛 银牌商机', $order_no = 'SN2025072365640', $channel = '谷歌', $country = '白俄罗斯', $enter_user = '木子李', $r_time = '2025-07-24 09:41:23', $t_time = '2025-07-24 14:41:23') + { + $content = '{ + "schema": "2.0", + "config": { + "update_multi": true, + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "16px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 20px 20px 20px", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": " **'.$quality.'**\n- **编号:** '.$order_no.'\n- **渠道:** '.$channel.'\n- **国家:** '.$country.'\n- **录单人:** '.$enter_user.'\n- **录入时间:** '.$r_time.'", + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ] + }, + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "div", + "text": { + "tag": "plain_text", + "content": "分配时间:'.$t_time.'", + "text_size": "notation", + "text_align": "left", + "text_color": "green" + }, + "icon": { + "tag": "standard_icon", + "token": "chat-done_outlined", + "color": "green" + }, + "element_id": "allocation_time" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "新的客户线索" + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "text_tag_list": [ + { + "tag": "text_tag", + "text": { + "tag": "plain_text", + "content": "指定" + }, + "color": "yellow" + } + ], + "template": "blue", + "icon": { + "tag": "standard_icon", + "token": "todo_colorful" + }, + "padding": "12px 12px 12px 20px" + } + }'; + return $content; + } + + /** + * 跟进提醒消息 + */ + public function personFollowUpTemplates($contacts = '木子李', $follow_next = '2025-07-24 14:41:23', $follow_last = '2025-07-24 14:41:23') + { + $content = '{ + "schema": "2.0", + "config": { + "update_multi": true, + "style": { + "text_size": { + "normal_v2": { + "default": "normal", + "pc": "normal", + "mobile": "heading" + } + } + } + }, + "body": { + "direction": "vertical", + "horizontal_spacing": "8px", + "vertical_spacing": "16px", + "horizontal_align": "left", + "vertical_align": "top", + "padding": "0px 20px 20px 20px", + "elements": [ + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "column_set", + "horizontal_align": "left", + "columns": [ + { + "tag": "column", + "width": "weighted", + "elements": [ + { + "tag": "markdown", + "content": "- **客户名称:** '.$contacts.'\n- **跟进时间:** '.$follow_next.'", + "text_align": "left", + "text_size": "normal_v2", + "margin": "0px 0px 0px 0px" + } + ], + "vertical_spacing": "8px", + "horizontal_align": "left", + "vertical_align": "top", + "weight": 1 + } + ] + }, + { + "tag": "hr", + "margin": "0px 0px 0px 0px" + }, + { + "tag": "div", + "text": { + "tag": "plain_text", + "content": "最近跟进:'.$follow_last.'", + "text_size": "notation", + "text_align": "left", + "text_color": "grey" + }, + "icon": { + "tag": "standard_icon", + "token": "chat_outlined", + "color": "grey" + }, + "element_id": "track_time" + } + ] + }, + "header": { + "title": { + "tag": "plain_text", + "content": "跟进提醒" + }, + "subtitle": { + "tag": "plain_text", + "content": "" + }, + "template": "green", + "icon": { + "tag": "standard_icon", + "token": "calendar_colorful" + }, + "padding": "12px 12px 12px 20px" + } + }'; + return $content; + } +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index c2a23df..b942887 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -9,6 +9,7 @@ use app\common\utils\scaffold\Generate; use support\Request; use support\Db; use support\Cache; +use app\inquiry\service\InquiryLogService; class IndexController { @@ -100,8 +101,23 @@ class IndexController public function test() { - $res = ['id'=>25714]; - \Webman\RedisQueue\Redis::send('inquiry-to-crm', $res); + // $res = ['id'=>25714]; + // \Webman\RedisQueue\Redis::send('inquiry-to-crm', $res); + + // $res = [ + // 'id'=>25717, + // 'category'=>'inquiry', + // 'scene'=>'inquiry-person-allocation', + // 'receive_type'=>'person' + // ]; + + // $res = [ + // 'id'=>1, + // 'category'=>'crm', + // 'scene'=>'crm-person-follow-up', + // 'receive_type'=>'person' + // ]; + // \Webman\RedisQueue\Redis::send('feishu-message', $res); return 'success'; } } \ No newline at end of file diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 759c8c4..8f40a0e 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -97,11 +97,11 @@ class InquiryLogService } // ====== ====== 关联 ====== ====== - $config['with']['country'][] = 'cn_addr'; - $config['with']['country'][] = 'steering'; + $config['with']['countryInfo'][] = 'cn_addr'; + $config['with']['countryInfo'][] = 'steering'; $config['with']['enter'][] = 'name'; $config['with']['recipient'][] = 'name'; - $config['with']['channel'][] = 'name'; + $config['with']['channelInfo'][] = 'name'; break; case 'show': case 'update': @@ -184,4 +184,43 @@ class InquiryLogService RedisQueueRedis::send('inquiry-to-crm', $res); return $res; } + + + /** + * 获取询单ById + * @param int|string $id + * @param string $orderSn + * @return Model|null + */ + public function getInquiryLogBy(int|string $id) + { + $config = []; + $config['model'] = $this->modelClass; + $config['with']['countryInfo'][] = 'cn_addr'; + $config['with']['countryInfo'][] = 'steering'; + $config['with']['enter'][] = 'name'; + $config['with']['recipient'][] = 'name'; + $config['with']['channelInfo'][] = 'name'; + $config['orWhere'][] = ['id', '=', $id]; + $config['orWhere'][] = ['order_no', '=', $id]; + return $this->logicShow($config); + } + + /** + * 获取等级名称 + * @param $id + * @return string + */ + public function qualityName($id) + { + if (!$id) { + return '🤍 银牌商机'; + } + $quality = [ + 1 => '💛 金牌商机', + 2 => '🤍 银牌商机', + 3 => '🤍 银牌商机' + ]; + return $quality[$id]; + } } \ No newline at end of file diff --git a/app/queue/redis/FeishuMessage.php b/app/queue/redis/FeishuMessage.php new file mode 100644 index 0000000..dacb4fc --- /dev/null +++ b/app/queue/redis/FeishuMessage.php @@ -0,0 +1,127 @@ +where('id', $data['id'])->first(); + + // 跟单人 + $reInfo = User::where('id', $info->user_id)->first(); + + // 飞书应用 + $feishuApp = new CorpFeishuApp(); + $feishuAppInfo = $feishuApp->where('corp_id', $reInfo->corp_id)->first(); + + // 推送数据 + $options = [ + 'contacts' => $info->contacts, + 'follow_last' => $info->follow_last ? date('Y-m-d H:i', $info->follow_last) : '', + 'follow_next' => $info->follow_next ? date('Y-m-d H:i', $info->follow_next) : '', + + 'app_id' => $feishuAppInfo->app_id, + 'app_secret' => $feishuAppInfo->app_secret, + 'msg_type' => 'interactive', + 'receive_id_type' => 'open_id', + 'receive_id' => $reInfo->open_id + ]; + } + + if ($data['category'] === 'inquiry') { + // 询单信息 + $inquiryLogService = new InquiryLogService(); + $info = $inquiryLogService->getInquiryLogBy($data['id']); + + // 接受者处理 + if ($data['receive_type'] == 'group') { + $appId = 'cli_a72f13d6c022500d'; + $appSecret = 'Kxtbq70OQWrEhpltrufRZc76z7kjDqPT'; + $receiveId = 'oc_4f574e6f8bc5b8f3e5175e67ebc5725c'; + $receiveIdType = 'chat_id'; + } else { + // 跟单人 + $reInfo = User::where('id', $info->recipient)->first(); + // 飞书应用 + $feishuApp = new CorpFeishuApp(); + $feishuAppInfo = $feishuApp->where('corp_id', $reInfo->corp_id)->first(); + // 推送信息 + $appId = $feishuAppInfo->app_id; + $appSecret = $feishuAppInfo->app_secret; + $receiveId = $reInfo->open_id; + $receiveIdType = 'open_id'; + } + + // 推送数据 + $options = [ + 'order_no' => $info->order_no, + 'quality' => $inquiryLogService->qualityName($info->quality), + 'channel' => $info->channelInfo ? $info->channelInfo->name : '', + 'country' => $info->countryInfo ? $info->countryInfo->cn_addr : '', + 'enter_user' => $info->enter ? $info->enter->name : '', + 'r_time' => $info->r_time ? date('Y-m-d H:i:s', $info->r_time) : '', + 't_time' => $info->t_time ? date('Y-m-d H:i:s', $info->t_time) : '', + + 'app_id' => $appId, + 'app_secret' => $appSecret, + 'msg_type' => 'interactive', + 'receive_id_type' => $receiveIdType, + 'receive_id' => $receiveId + ]; + } + + $feishuMsg = new FeishuMsg(); + $feishuMsg->messageSend($data['scene'], $options); + } catch (\Throwable $th) { + throw $th; + } + } + + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file diff --git a/app/queue/redis/InquiryToCrm.php b/app/queue/redis/InquiryToCrm.php index 36e70dc..ecac73e 100644 --- a/app/queue/redis/InquiryToCrm.php +++ b/app/queue/redis/InquiryToCrm.php @@ -2,10 +2,12 @@ namespace app\queue\redis; +use app\inquiry\service\InquiryLogService; use app\common\model\CrmCustomer; use app\common\model\InquiryLog; use Webman\RedisQueue\Consumer; use app\common\model\User; +use app\common\utils\FeishuMessage; use support\exception\BusinessException; class InquiryToCrm implements Consumer @@ -21,7 +23,7 @@ class InquiryToCrm implements Consumer { try { // 获取询盘信息 - $info = InquiryLog::where('id', $data['id'])->where('sync_crm', 0)->first(); + $info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', $data['id'])->where('sync_crm', 0)->first(); if ($info) { // 获取用户信息 $recipient = 0; @@ -32,6 +34,13 @@ class InquiryToCrm implements Consumer if ($uinfo) { $corpId = $uinfo->corp_id; } + // 消息推送参数 分配 + $scene = 'inquiry-person-allocation'; + $receiveType = 'person'; + } else { + // 消息推送参数 发布 + $scene = 'inquiry-group-send'; + $receiveType = 'group'; } // 同步客户数据 $crmCustomer = new CrmCustomer(); @@ -54,6 +63,15 @@ class InquiryToCrm implements Consumer if (!$crmCustomer->save() || !$info->save()) { throw new BusinessException('同步失败~'); } + + // 发送飞书消息 + $options = [ + 'id' => $info->id, + 'category' => 'inquiry', + 'scene' => $scene, + 'receive_type' => $receiveType + ]; + \Webman\RedisQueue\Redis::send('feishu-message', $options); } } catch (\Throwable $th) { throw $th; diff --git a/config/route.php b/config/route.php index ddd93d8..8d12177 100644 --- a/config/route.php +++ b/config/route.php @@ -29,6 +29,10 @@ use Webman\Route; Route::any('/c', [app\controller\IndexController::class, 'createResource']); Route::any('/perm', [app\controller\IndexController::class, 'perm']); +// 飞书 +Route::any('/feishu/event', [app\common\controller\NotifyController::class, 'feishuEvent']); +// 飞书回调 +Route::any('/feishu/callback', [app\common\controller\NotifyController::class, 'feishuCallback']); // 公共 Route::group('/common', function () { diff --git a/config/whitelist.php b/config/whitelist.php deleted file mode 100644 index 1736876..0000000 --- a/config/whitelist.php +++ /dev/null @@ -1,13 +0,0 @@ - [ - '/', // 首页 - '/index/index', // 首页 - '/index/view', // 飞书调试 - '/perm', // 调试-添加权限 - - '/common/preset/corps', // 公司列表 - '/common/preset/feishu/app', // 飞书应用信息 - ] -]; \ No newline at end of file From de93d8bd9ec12877734bfe19f190a30491562c85 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 13:55:30 +0800 Subject: [PATCH 088/120] =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/FollowController.php | 8 ++-- app/process/Task.php | 39 +++++++++++++++++++ composer.json | 3 +- composer.lock | 52 ++++++++++++++++++++++++- config/process.php | 5 ++- resource/translations/ar/messages.php | 1 + resource/translations/en/messages.php | 1 + resource/translations/ru/messages.php | 1 + resource/translations/zh/messages.php | 1 + 9 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 app/process/Task.php diff --git a/app/crm/controller/FollowController.php b/app/crm/controller/FollowController.php index caf785f..42d6a01 100644 --- a/app/crm/controller/FollowController.php +++ b/app/crm/controller/FollowController.php @@ -92,9 +92,11 @@ class FollowController extends BaseController if (Util::isEmpty($type)) { return $this->error('type' . trans('not_null')); } + $audio_text = Util::getParam($request, 'audio_text', ''); $picture = Util::getParam($request, 'picture'); - if (Util::isEmpty($picture)) { - return $this->error(trans('picture_not_null')); + $remark = Util::getParam($request,'remark'); + if(Util::isEmpty($picture) && Util::isEmpty($audio_text) && Util::isEmpty($remark)){ + return $this->error(trans('follow_not_null')); } $corpInfo = Corp::where('abbr', $request->basic_data['corp_abbr'])->first(); @@ -107,7 +109,7 @@ class FollowController extends BaseController 'picture' => $picture, 'remark' => Util::getParam($request, 'remark', ''), 'audio' => Util::getParam($request, 'audio', ''), - 'audio_text' => Util::getParam($request, 'audio_text', ''), + 'audio_text' => $audio_text, 'lat' => Util::getParam($request, 'lat', 0), 'lng' => Util::getParam($request, 'lng', 0), 'address' => Util::getParam($request, 'address'), diff --git a/app/process/Task.php b/app/process/Task.php new file mode 100644 index 0000000..540eb8f --- /dev/null +++ b/app/process/Task.php @@ -0,0 +1,39 @@ +', time()], + ['warn', '<', 1] + ]; + $customer = Customer::where($where)->get(); + foreach ($customer as $item) { + $res =[ + 'id'=>$item->id, + 'category'=>'crm', + 'scene'=>'crm-person-follow-up', + 'receive type'=>'person' + ]; + \Webman\RedisQueue\Redis::send("feishu-message",$res); + } + + Log::info('send follow start',[time()]); + }); + } + +} \ No newline at end of file diff --git a/composer.json b/composer.json index 9847f65..64b3e58 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,8 @@ "shopwwi/filesystem-oss": "^1.0", "webman/redis-queue": "^2.1", "symfony/translation": "^7.3", - "symfony/filesystem": "^7.3" + "symfony/filesystem": "^7.3", + "workerman/crontab": "^1.0" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index 3736333..7f84b75 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6c03076d4c7c86514f6b2d0073545bea", + "content-hash": "8ef9d9a07b2be477345002a7623ea951", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -6292,6 +6292,56 @@ }, "time": "2025-02-17T03:34:21+00:00" }, + { + "name": "workerman/crontab", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/walkor/crontab.git", + "reference": "74f51ca8204e8eb628e57bc0e640561d570da2cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/crontab/zipball/74f51ca8204e8eb628e57bc0e640561d570da2cb", + "reference": "74f51ca8204e8eb628e57bc0e640561d570da2cb", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "workerman/workerman": ">=4.0.20" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\Crontab\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "A crontab written in PHP based on workerman", + "homepage": "http://www.workerman.net", + "keywords": [ + "crontab" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/crontab", + "wiki": "http://doc.workerman.net/" + }, + "time": "2025-01-15T07:20:50+00:00" + }, { "name": "workerman/redis", "version": "v2.0.4", diff --git a/config/process.php b/config/process.php index 224a111..05b250f 100644 --- a/config/process.php +++ b/config/process.php @@ -59,5 +59,8 @@ return [ 'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/', ] ] - ] + ], + 'task' => [ + 'handler' => app\process\Task::class + ], ]; diff --git a/resource/translations/ar/messages.php b/resource/translations/ar/messages.php index 7430697..ed99240 100644 --- a/resource/translations/ar/messages.php +++ b/resource/translations/ar/messages.php @@ -23,6 +23,7 @@ return [ 'param_error' => ' خطأ في المعلمات', 'required_not_null' => ' الحقول المطلوبة لا يمكن أن تكون فارغة', 'picture_not_null' => ' لا يمكن أن تكون الصورة فارغة', + 'follow_not_null' => ' الصورة و متابعة المحتوى لا يمكن أن تكون فارغة في نفس الوقت', 'contacts_not_null' => ' لا يمكن أن يكون جهة الاتصال أو البريد الإلكتروني أو الهاتف فارغين جميعًا في نفس الوقت', 'car_type_not_null' => ' لا يمكن أن تكون التفضيلات فارغة', 'channel_not_null' => ' لا يمكن أن تكون القناة فارغة', diff --git a/resource/translations/en/messages.php b/resource/translations/en/messages.php index 342ffad..4c73bcd 100644 --- a/resource/translations/en/messages.php +++ b/resource/translations/en/messages.php @@ -23,6 +23,7 @@ return [ 'param_error' => ' param error', 'required_not_null' => ' required not_ ull', 'picture_not_null' => ' picture not null', + 'follow_not_null' => ' The image and follow-up content cannot be empty at the same time', 'contacts_not_null' => ' Contact person, email, and phone cannot be empty at the same time', 'car_type_not_null' => ' preference_not_null', 'channel_not_null' => ' channel not null', diff --git a/resource/translations/ru/messages.php b/resource/translations/ru/messages.php index 3a1839f..b275ac6 100644 --- a/resource/translations/ru/messages.php +++ b/resource/translations/ru/messages.php @@ -23,6 +23,7 @@ return [ 'param_error' => ' Ошибка параметра', 'required_not_null' => ' Обязательное заполнение не может быть пустым.', 'picture_not_null' => ' Фотографии не могут быть пустыми', + 'follow_not_null' => ' Изображение и последующий контент не могут быть пустыми одновременно.X', 'contacts_not_null' => ' Контакты, почтовый ящик, телефон не может быть одновременно пустым', 'car_type_not_null' => ' Предпочтения не могут быть пустыми', 'channel_not_null' => ' Каналы не могут быть пустыми', diff --git a/resource/translations/zh/messages.php b/resource/translations/zh/messages.php index 96c3cb7..b8ca32d 100644 --- a/resource/translations/zh/messages.php +++ b/resource/translations/zh/messages.php @@ -23,6 +23,7 @@ return [ 'param_error' => ' 参数错误', 'required_not_null' => ' 必填项不能为空', 'picture_not_null' => ' 图片不能为空', + 'follow_not_null' => ' 图片与跟进内容不能同时为空', 'contacts_not_null' => ' 联系人,邮箱,电话不能同时为空', 'car_type_not_null' => ' 偏好不能为空', 'channel_not_null' => ' 渠道不能为空', From cf3d6a6dcfc377b5b15089d8c82254a6f9611a69 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 14:24:20 +0800 Subject: [PATCH 089/120] =?UTF-8?q?=E5=85=AC=E6=B5=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index e411984..e781f68 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -88,7 +88,7 @@ class CustomerService foreach ($customer as &$item) { $item = $this->getCustomer($item); if($seas == 'seas'){ - $item['seas_time'] = time()-strtotime($item['deleted_at']); + $item['seas_time'] = time()-(empty($item['deleted_at'])?0:strtotime($item['deleted_at'])); } } unset($item); From d777c1924d404c87ba62b7cfbeda10b8751f9e7a Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 14:39:40 +0800 Subject: [PATCH 090/120] =?UTF-8?q?=E5=85=AC=E6=B5=B7=E5=AE=A2=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index e781f68..4155de2 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -80,15 +80,18 @@ class CustomerService $query->whereIn('grade', $grade); } - if ($recycle == 'recycle' || $seas == 'seas') { + if ($recycle == 'recycle') { $query->onlyTrashed(); } + if ($seas == 'seas') { + $query->withTrashed()(); + } - $customer = Customer::where($where)->paginate($perPage, ['*'], 'page', $page); + $customer = $query->paginate($perPage, ['*'], 'page', $page); foreach ($customer as &$item) { $item = $this->getCustomer($item); if($seas == 'seas'){ - $item['seas_time'] = time()-(empty($item['deleted_at'])?0:strtotime($item['deleted_at'])); + $item['seas_time'] = time()-(empty($item['deleted_at'])?$item['updated_at']:strtotime($item['deleted_at'])); } } unset($item); From a668ce38bca1727758aff0856fb7e3854a90277f Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 14:41:26 +0800 Subject: [PATCH 091/120] =?UTF-8?q?=E5=85=AC=E6=B5=B7=E5=AE=A2=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 4155de2..9238475 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -84,7 +84,7 @@ class CustomerService $query->onlyTrashed(); } if ($seas == 'seas') { - $query->withTrashed()(); + $query->withTrashed(); } $customer = $query->paginate($perPage, ['*'], 'page', $page); From b189b8c1af039e7e1826f9c2e1caaac7526035c4 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 14:43:11 +0800 Subject: [PATCH 092/120] =?UTF-8?q?=E5=85=AC=E6=B5=B7=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=88=B3=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 9238475..f4b498f 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -91,7 +91,7 @@ class CustomerService foreach ($customer as &$item) { $item = $this->getCustomer($item); if($seas == 'seas'){ - $item['seas_time'] = time()-(empty($item['deleted_at'])?$item['updated_at']:strtotime($item['deleted_at'])); + $item['seas_time'] = time()-(empty($item['deleted_at'])?strtotime($item['updated_at']):strtotime($item['deleted_at'])); } } unset($item); From 98e3dc033cf993a8d9738504e5d4370b3a17a78c Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 15:19:33 +0800 Subject: [PATCH 093/120] =?UTF-8?q?=E5=85=AC=E6=B5=B7=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E8=84=B1=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 2 ++ app/crm/utils/Util.php | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index f4b498f..4c66414 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -91,6 +91,8 @@ class CustomerService foreach ($customer as &$item) { $item = $this->getCustomer($item); if($seas == 'seas'){ + $item['phone'] = Util::mask($item['phone']); + $item['email'] = Util::mask($item['email']); $item['seas_time'] = time()-(empty($item['deleted_at'])?strtotime($item['updated_at']):strtotime($item['deleted_at'])); } } diff --git a/app/crm/utils/Util.php b/app/crm/utils/Util.php index db6743b..afc05b9 100644 --- a/app/crm/utils/Util.php +++ b/app/crm/utils/Util.php @@ -118,4 +118,16 @@ class Util return $en; } + public static function mask($str) + { + if(empty($str)){ + return ''; + } + // 只对 11 位手机号进行脱敏,中间 4 位用 * 替换 + if (preg_match('/^1[3-9]\d{9}$/', $str)) { + return substr($str, 0, 3) . '****' . substr($str, -4); + } + return $str; + } + } \ No newline at end of file From 04f118bcde4ba24dfef1fbae18d90180871e6ac0 Mon Sep 17 00:00:00 2001 From: mzl92 Date: Fri, 25 Jul 2025 16:10:59 +0800 Subject: [PATCH 094/120] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=A3=9E=E4=B9=A6?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E4=BF=A1=E6=81=AF=E5=A2=9E=E5=8A=A0=E7=B3=BB?= =?UTF-8?q?=E7=BB=9Fid&&=E8=B0=83=E6=95=B4=E8=8F=9C=E5=8D=95=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/PresetController.php | 3 ++- app/common/logic/CorpFeishuAppLogic.php | 4 ++-- app/common/logic/PermLogic.php | 2 ++ app/common/service/CorpFeishuAppService.php | 4 ++-- app/queue/redis/FeishuMessage.php | 4 ++-- app/view/index/qrcode.html | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/common/controller/PresetController.php b/app/common/controller/PresetController.php index 27f0c7a..ad88a8f 100644 --- a/app/common/controller/PresetController.php +++ b/app/common/controller/PresetController.php @@ -31,7 +31,8 @@ class PresetController extends BaseController { try { $corpId = $request->get('corp_id'); - $result = (new CorpFeishuAppService())->getFeishuApp($corpId); + $systemId = $request->get('system_id'); + $result = (new CorpFeishuAppService())->getFeishuApp($corpId, $systemId); return $this->success($result, '获取飞书应用信息'); } catch (\Throwable $e) { return $this->error($e->getMessage()); diff --git a/app/common/logic/CorpFeishuAppLogic.php b/app/common/logic/CorpFeishuAppLogic.php index 2073aac..e246316 100644 --- a/app/common/logic/CorpFeishuAppLogic.php +++ b/app/common/logic/CorpFeishuAppLogic.php @@ -8,9 +8,9 @@ class CorpFeishuAppLogic { - public function getFeishuApp($corpId) + public function getFeishuApp($corpId, $systemId) { - return CorpFeishuApp::where('corp_id', $corpId)->first(['id', 'corp_id', 'name', 'app_id', 'r_url', 'state']); + return CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first(['id', 'corp_id', 'name', 'app_id', 'r_url', 'state']); } } \ No newline at end of file diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index 24f8e65..0977a4f 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -257,6 +257,8 @@ class PermLogic 'keepAlive' => true, 'authList' => $authList, 'openType' => $node->open_type, + 'isHide' => $node->is_hide == 0 ? false : true, + 'isHideTab' => $node->is_hide_tab == 0 ? false : true ], 'children' => $children, ]; diff --git a/app/common/service/CorpFeishuAppService.php b/app/common/service/CorpFeishuAppService.php index 0c10be8..ad84582 100644 --- a/app/common/service/CorpFeishuAppService.php +++ b/app/common/service/CorpFeishuAppService.php @@ -14,8 +14,8 @@ class CorpFeishuAppService $this->logic = (new CorpFeishuAppLogic()); } - public function getFeishuApp(int $corpId) + public function getFeishuApp(int $corpId, int $systemId) { - return $this->logic->getFeishuApp($corpId); + return $this->logic->getFeishuApp($corpId, $systemId); } } \ No newline at end of file diff --git a/app/queue/redis/FeishuMessage.php b/app/queue/redis/FeishuMessage.php index dacb4fc..c2663c6 100644 --- a/app/queue/redis/FeishuMessage.php +++ b/app/queue/redis/FeishuMessage.php @@ -39,7 +39,7 @@ class FeishuMessage implements Consumer // 飞书应用 $feishuApp = new CorpFeishuApp(); - $feishuAppInfo = $feishuApp->where('corp_id', $reInfo->corp_id)->first(); + $feishuAppInfo = $feishuApp->where('corp_id', $reInfo->corp_id)->where('system_id', 1)->first(); // 推送数据 $options = [ @@ -71,7 +71,7 @@ class FeishuMessage implements Consumer $reInfo = User::where('id', $info->recipient)->first(); // 飞书应用 $feishuApp = new CorpFeishuApp(); - $feishuAppInfo = $feishuApp->where('corp_id', $reInfo->corp_id)->first(); + $feishuAppInfo = $feishuApp->where('corp_id', $reInfo->corp_id)->where('system_id', 1)->first(); // 推送信息 $appId = $feishuAppInfo->app_id; $appSecret = $feishuAppInfo->app_secret; diff --git a/app/view/index/qrcode.html b/app/view/index/qrcode.html index fb6f11c..97c2a7c 100644 --- a/app/view/index/qrcode.html +++ b/app/view/index/qrcode.html @@ -16,7 +16,7 @@ // const REDIRECT_URI = encodeURIComponent('https://example.com/api/oauth/callback/feishu'); // const STATE = '随机串_防CSRF'; // // 用于跳转获取 code 的 URL - const GOTO = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=cli_a72f13d6c022500d&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&response_type=code&state=MZL`; + const GOTO = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=cli_a72f13d6c022500d&redirect_uri=https://web-fdesk.hnxinyouhe.com/auth/feishu-callback&response_type=code&state=MZL`; // const GOTO = `https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a73e1e6406fa501c&redirect_uri=https://mzl.dev.hnxinyouhe.com/common/auth/feishu/auth&scope=contact:user.base:readonly contact:contact.base:readonly contact:user.employee_id:readonly im:message:send_as_bot&state=xinyouhe`; // 2. 创建二维码实例 const QRLoginObj = QRLogin({ From 2141d22dda2971d015fa7b4e2ed95bf2ebd669a7 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 16:14:09 +0800 Subject: [PATCH 095/120] =?UTF-8?q?=E8=B7=9F=E8=BF=9B=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 2 +- app/crm/service/FollowService.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 4c66414..022a3dc 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -66,7 +66,7 @@ class CustomerService //查询有$tagId 里所有标签的客户 foreach ($tagId as $id) { $query->whereHas('tags', function($query) use ($id){ - $query->where('tag.id', $id); + $query->where('tag_id', $id); }); } } diff --git a/app/crm/service/FollowService.php b/app/crm/service/FollowService.php index 0d3fd8e..e8ce0f7 100644 --- a/app/crm/service/FollowService.php +++ b/app/crm/service/FollowService.php @@ -23,7 +23,7 @@ class FollowService $where = []; $where['customer_id'] = $customer_id; - $where['corp_abbr'] = $corp_abbr; +// $where['corp_abbr'] = $corp_abbr; //用户角色 From 60bf257cf7da78f450dd51bba655b29afbe3886e Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 17:00:03 +0800 Subject: [PATCH 096/120] =?UTF-8?q?=E8=84=B1=E6=95=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 1 + app/crm/utils/Util.php | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 022a3dc..a1931ad 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -93,6 +93,7 @@ class CustomerService if($seas == 'seas'){ $item['phone'] = Util::mask($item['phone']); $item['email'] = Util::mask($item['email']); + $item['WhatsApp'] = Util::mask($item['WhatsApp']); $item['seas_time'] = time()-(empty($item['deleted_at'])?strtotime($item['updated_at']):strtotime($item['deleted_at'])); } } diff --git a/app/crm/utils/Util.php b/app/crm/utils/Util.php index afc05b9..e6ac4d0 100644 --- a/app/crm/utils/Util.php +++ b/app/crm/utils/Util.php @@ -123,11 +123,7 @@ class Util if(empty($str)){ return ''; } - // 只对 11 位手机号进行脱敏,中间 4 位用 * 替换 - if (preg_match('/^1[3-9]\d{9}$/', $str)) { - return substr($str, 0, 3) . '****' . substr($str, -4); - } - return $str; + return substr($str, 0, 3) . '****' . substr($str, -4); } } \ No newline at end of file From 0e8eab7dfb43d1ccd9c8009fc88fe90fb2762af9 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 17:22:22 +0800 Subject: [PATCH 097/120] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index a1931ad..0e5ca8f 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -26,15 +26,13 @@ class CustomerService $tagId = $params['tag_id'] ?? []; $where = []; $user_id =$params['user_id']; - if(empty($user_id) || $user_id == 0){ - $where[] = ['user_id', '=', $user_id]; - } $seas = $params['seas'] ?? ''; if($seas == 'seas'){ $where[] = ['user_id', 0]; - } - if($recycle == 'recycle'){ +// }elseif($recycle == 'recycle'){ +// $where[] = ['user_id', $user_id]; + }else{ $where[] = ['user_id', $user_id]; } From e5f02d1441445b609ba7cffcb0f9c3b0f508f629 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 17:58:42 +0800 Subject: [PATCH 098/120] =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 81936eb..b6f7141 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -373,7 +373,10 @@ class CustomerController extends BaseController $where[] = ['created_at', '>=', $start]; $where[] = ['created_at', '<', $end]; $customer = Customer::where($where)->count(); - + $builder = Customer::where($where)->count(); + $sql = str_replace(['?'], ['\'%s\''], $builder->toSql()); + $fullSql = vsprintf($sql, $builder->getBindings()); + var_dump($fullSql); //初步接触 $whereGrade = $where; $whereGrade[] = ['grade', '<=', 2]; From 714f8dc41eac0ee20eb7b4ec39c8e14a216af354 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Fri, 25 Jul 2025 18:04:32 +0800 Subject: [PATCH 099/120] =?UTF-8?q?=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index b6f7141..998c721 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -365,7 +365,7 @@ class CustomerController extends BaseController $created_at = strtotime($created_at); $day = ceil((time() - $created_at + 1) / 60 / 60 / 24); $where[] = ['user_id', '=', $userId]; - $where[] = ['corp_abbr', '=', $request->basic_data['corp_abbr']]; + //$where[] = ['corp_abbr', '=', $request->basic_data['corp_abbr']]; $start = strtotime($created . '-01'); $year = date('Y', $start); $month = date('n', $start); //获取月份 @@ -373,10 +373,7 @@ class CustomerController extends BaseController $where[] = ['created_at', '>=', $start]; $where[] = ['created_at', '<', $end]; $customer = Customer::where($where)->count(); - $builder = Customer::where($where)->count(); - $sql = str_replace(['?'], ['\'%s\''], $builder->toSql()); - $fullSql = vsprintf($sql, $builder->getBindings()); - var_dump($fullSql); + //初步接触 $whereGrade = $where; $whereGrade[] = ['grade', '<=', 2]; From cf6fc7033a1076aff068c353e75eb7487196ae6e Mon Sep 17 00:00:00 2001 From: mzl92 Date: Fri, 25 Jul 2025 18:10:11 +0800 Subject: [PATCH 100/120] =?UTF-8?q?=E8=8F=9C=E5=8D=95=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UserController.php | 3 +- app/common/logic/AuthLogic.php | 43 +++++++++++++++++++++++- app/common/logic/PermLogic.php | 14 +++++--- app/common/service/AuthService.php | 2 +- app/common/service/PermService.php | 12 +++---- app/common/service/UserService.php | 6 ++-- 6 files changed, 63 insertions(+), 17 deletions(-) diff --git a/app/common/controller/UserController.php b/app/common/controller/UserController.php index 2a22145..d725166 100644 --- a/app/common/controller/UserController.php +++ b/app/common/controller/UserController.php @@ -15,7 +15,8 @@ class UserController extends BaseController { try { $uid = $request->user->uid; - $result = (new UserService())->getUserInfo($uid); + $systemCode = $request->system_code; + $result = (new UserService())->getUserInfo($uid, $systemCode); return $this->success($result, '获取用户信息'); } catch (\Throwable $e) { return $this->error($e->getMessage()); diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index ccdbf16..e6022f3 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -4,6 +4,7 @@ namespace app\common\logic; use app\common\model\Corp; use app\common\model\CorpFeishuApp; +use app\common\model\Resource; use app\common\model\UserTokenLog; use app\common\model\Systems; use app\common\model\User; @@ -140,6 +141,7 @@ class AuthLogic $payload = $this->buildJwtPayload($user, $system); $token = $this->generateAccessToken($payload); $refreshToken = $this->generateRefreshToken($user->id, $data['system_code']); + $homeUrl = $this->getHome($system->id); // 记录 token 到 Redis 和 MySQL $this->recordUserToken($user->id, $token, $data['system_code']); @@ -150,7 +152,8 @@ class AuthLogic 'expires_in' => config('jwt.jwt_expiration'), 'system_code' => $data['system_code'], 'corp_abbr' => $data['corp_abbr'], - 'login_type' => $data['login_type'] + 'login_type' => $data['login_type'], + 'home_url' => $homeUrl, // 'payload' => $payload, ]; } @@ -500,4 +503,42 @@ class AuthLogic Redis::del("user_token_meta:{$token}"); UserTokenLog::where('token', $token)->update(['is_valid'=>0]); } + + + public function getHome($systemId) + { + $info = Resource::where('system_id', $systemId) + ->where('is_home', 1) + ->first(); + + if (!$info) { + return '/dashboard/console'; + } + + // 递归拼接 URL + $url = $this->buildParentUrl($info); + + return $url ?: '/dashboard/console'; + } + + /** + * 递归构建父级 URL + * @param Resource $resource + * @return string + */ + protected function buildParentUrl(Resource $resource) + { + $url = $resource->url ?? ''; + + // 如果存在父级,则递归拼接 + if ($resource->parent_id != 0) { + $parent = Resource::find($resource->parent_id); + if ($parent) { + $parentUrl = $this->buildParentUrl($parent); + $url = $parentUrl . ($url ? '/' . ltrim($url, '/') : ''); + } + } + + return $url; + } } \ No newline at end of file diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index 0977a4f..fbd5218 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -4,14 +4,17 @@ namespace app\common\logic; use app\common\model\User; use app\common\model\Resource; +use app\common\model\Systems; class PermLogic { /** * 获取用户所有资源(合并角色+直接分配),并加入公共菜单,排序按sort字段 */ - public function getUserResources(int $userId) + public function getUserResources(int $userId, string $systemCode) { + $system = Systems::where('code', $systemCode)->first(); + // 获取用户及其关联角色资源和直接分配的资源 $user = User::with(['roles.resources', 'resources'])->find($userId); @@ -24,9 +27,10 @@ class PermLogic // 合并角色资源,排除不需要权限认证的资源 foreach ($user->roles as $role) { $all = $all->merge( - $role->resources->filter(function ($resource) { + $role->resources->filter(function ($resource) use($system) { + // var_dump($resource->toArray()); // 只保留需要权限认证的资源(或公共资源) - return !$resource->no_auth; + return !$resource->no_auth && $system->id == $resource->system_id; }) ); } @@ -269,9 +273,9 @@ class PermLogic /** * 获取用户所有权限code */ - public function getPermissionCodes(int $userId): array + public function getPermissionCodes(int $userId, string $systemCode): array { - $resources = $this->getUserResources($userId); + $resources = $this->getUserResources($userId, $systemCode); return $resources ->filter(fn($res) => $res->type === 'permission') ->pluck('code') diff --git a/app/common/service/AuthService.php b/app/common/service/AuthService.php index aa8f4eb..149e966 100644 --- a/app/common/service/AuthService.php +++ b/app/common/service/AuthService.php @@ -31,7 +31,7 @@ class AuthService return match ($loginType) { 'feishu' => $logic->feishuLogin($data), - 'qrcode' => $logic->qrCodeLogin($data), + // 'qrcode' => $logic->qrCodeLogin($data), default => $logic->uPwdLogin($data), }; } diff --git a/app/common/service/PermService.php b/app/common/service/PermService.php index 8f8770c..06c7535 100644 --- a/app/common/service/PermService.php +++ b/app/common/service/PermService.php @@ -23,7 +23,7 @@ class PermService /** * 获取用户所有资源(合并角色+直接分配),结果缓存 */ - public function getUserResources(int $userId) + public function getUserResources(int $userId, string $systemCode) { $cacheKey = self::cacheKey($userId); $cached = Cache::get($cacheKey); @@ -31,7 +31,7 @@ class PermService return $cached; } // 调用 Logic 层获取资源 - $resources = $this->logic->getUserResources($userId); + $resources = $this->logic->getUserResources($userId, $systemCode); // 缓存结果 Cache::set($cacheKey, $resources, 3600); return $resources; @@ -40,19 +40,19 @@ class PermService /** * 构建目录/菜单树,节点包含权限与打开方式 */ - public function getMenuTree(int $userId): array + public function getMenuTree(int $userId, string $systemCode): array { // 调用 Logic 层处理菜单构建 - $resources = $this->getUserResources($userId); + $resources = $this->getUserResources($userId, $systemCode); return $this->logic->buildTree($resources); } /** * 获取用户所有权限code */ - public function getPermissionCodes(int $userId): array + public function getPermissionCodes(int $userId, string $systemCode): array { - return $this->logic->getPermissionCodes($userId); + return $this->logic->getPermissionCodes($userId, $systemCode); } /** diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 25de588..69fb2f4 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -22,12 +22,12 @@ class UserService /** * 获取用户信息(包括菜单和权限) */ - public function getUserInfo(int $uid) + public function getUserInfo(int $uid, string $systemCode) { $user = $this->logic->userInfo($uid); $permService = new PermService(); - $menuTree = $permService->getMenuTree($uid); - $permissionCodes = $permService->getPermissionCodes($uid); + $menuTree = $permService->getMenuTree($uid, $systemCode); + $permissionCodes = $permService->getPermissionCodes($uid, $systemCode); return [ 'user' => [ 'id' => $user->id, From 323a5091365c20988cd29b85c3558b24a42a3b30 Mon Sep 17 00:00:00 2001 From: mzl92 Date: Fri, 25 Jul 2025 18:14:39 +0800 Subject: [PATCH 101/120] =?UTF-8?q?=E8=8F=9C=E5=8D=95=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8C=BA=E5=88=86=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/service/PermService.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/common/service/PermService.php b/app/common/service/PermService.php index 06c7535..16030b1 100644 --- a/app/common/service/PermService.php +++ b/app/common/service/PermService.php @@ -15,9 +15,9 @@ class PermService } // 用户资源缓存key - protected static function cacheKey(int $userId): string + protected static function cacheKey(int $userId, string $systemCode): string { - return "user_resources_{$userId}"; + return "user_resources_{$userId}_{$systemCode}"; } /** @@ -25,7 +25,7 @@ class PermService */ public function getUserResources(int $userId, string $systemCode) { - $cacheKey = self::cacheKey($userId); + $cacheKey = self::cacheKey($userId, $systemCode); $cached = Cache::get($cacheKey); if ($cached !== null) { return $cached; @@ -58,8 +58,8 @@ class PermService /** * 清除资源相关缓存 */ - public function clearCache(int $userId): void + public function clearCache(int $userId, string $systemCode): void { - Cache::delete(self::cacheKey($userId)); + Cache::delete(self::cacheKey($userId, $systemCode)); } } \ No newline at end of file From 6dd22ad03cb1d9641100c900cd0f5c67e7027ed1 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Mon, 28 Jul 2025 09:36:16 +0800 Subject: [PATCH 102/120] =?UTF-8?q?=E8=B7=9F=E8=BF=9B=E6=8F=90=E9=86=92?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E7=8A=B6=E6=80=81=E6=A0=87=E8=AE=B0=EF=BC=8C?= =?UTF-8?q?=E8=B7=9F=E8=BF=9B=E6=95=B0=E7=BB=9F=E8=AE=A1=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 4 ++-- app/process/Task.php | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 998c721..42003c0 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -390,12 +390,12 @@ class CustomerController extends BaseController //拜访数 $whereVisit = $where; $whereVisit[] = ['type', '=', 0]; - $visit = Follow::where($whereVisit)->distinct('customer_id')->count(); + $visit = Follow::where($whereVisit)->count(); //跟进数 $whereVisit = $where; $whereVisit[] = ['type', '=', 1]; - $follow = Follow::where($whereVisit)->distinct('customer_id')->count(); + $follow = Follow::where($whereVisit)->count(); $res = [ 'customer' => $customer, //客户数 diff --git a/app/process/Task.php b/app/process/Task.php index 540eb8f..910b5ad 100644 --- a/app/process/Task.php +++ b/app/process/Task.php @@ -30,6 +30,8 @@ class Task 'receive type'=>'person' ]; \Webman\RedisQueue\Redis::send("feishu-message",$res); + $item->warn = 1; + $item->save(); } Log::info('send follow start',[time()]); From 1a54a55653c4c19f669f91cc9f1c77290edcbc91 Mon Sep 17 00:00:00 2001 From: mzl92 Date: Mon, 28 Jul 2025 14:03:15 +0800 Subject: [PATCH 103/120] =?UTF-8?q?=E9=A3=9E=E4=B9=A6=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E4=BC=81=E4=B8=9A=E9=82=AE=E7=AE=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/AuthLogic.php | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/common/logic/AuthLogic.php b/app/common/logic/AuthLogic.php index e6022f3..754504b 100644 --- a/app/common/logic/AuthLogic.php +++ b/app/common/logic/AuthLogic.php @@ -86,16 +86,23 @@ class AuthLogic { try { $phone = $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''); - $user = User::where('phone', $phone)->where('corp_id', $feishuUserInfo['corp_id'])->first(); + $email = $feishuUserInfo['enterprise_email']; + $user = User::where('corp_id', $feishuUserInfo['corp_id']) + ->where(function($query) use ($phone, $email) { + $query->where('phone', $phone) + ->orWhere('email', $email); + }) + ->first(); if (!$user) { // 获取编号 $userSn = $this->getAvailableUserSn(); // 创建用户 $user = User::create([ - 'name' => $feishuUserInfo['en_name'], + 'name' => $feishuUserInfo['name'], 'avatar' => $feishuUserInfo['avatar_middle'], 'phone' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''), + 'email' => $feishuUserInfo['enterprise_email'], 'union_id' => $feishuUserInfo['union_id'], 'user_id' => $feishuUserInfo['user_id'], 'open_id' => $feishuUserInfo['open_id'], @@ -112,12 +119,20 @@ class AuthLogic // 是否登陆 $user->isLogin = false; } else { - $user->update([ + $updated = [ 'union_id' => $feishuUserInfo['union_id'], 'user_id' => $feishuUserInfo['user_id'], 'open_id' => $feishuUserInfo['open_id'], 'tenant_key' => $feishuUserInfo['tenant_key'] - ]); + ]; + if ($phone && $phone == $user->phone) { + $updated['phone'] = $phone; + } + if ($phone && $phone == $user->phone) { + $updated['email'] = $email; + } + // 更新飞书信息 + $user->update($updated); } return $user; } catch (\Throwable $e) { From 22bee76835f340054dc28259962cdc793f87788f Mon Sep 17 00:00:00 2001 From: mzl92 Date: Mon, 28 Jul 2025 18:49:33 +0800 Subject: [PATCH 104/120] =?UTF-8?q?=E8=AF=A2=E5=8D=95url=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/command/TbCommand.php | 49 ++++ .../controller/InquiryLogController.php | 240 +++++++++--------- app/inquiry/service/InquiryLogService.php | 18 +- config/route.php | 2 + 4 files changed, 188 insertions(+), 121 deletions(-) create mode 100644 app/command/TbCommand.php diff --git a/app/command/TbCommand.php b/app/command/TbCommand.php new file mode 100644 index 0000000..3618b40 --- /dev/null +++ b/app/command/TbCommand.php @@ -0,0 +1,49 @@ +all(); + + foreach ($list as $item) { + // UserSn::where('user_sn', $item->sn)->update(['user_id' => $item->id]); + } + return self::SUCCESS; + } +} + + +function replaceRichTextUrls($input) { + // 匹配旧域名及其引号包裹情况(包含http/https协议) + $pattern = '#(https?://server\.xyh\.hnzhwlkj\.club|["\']https?://server\.xyh\.hnzhwlkj\.club)(/[^\s"\'<>]*)?#iu'; + + $replacement = function ($matches) { + $originalQuote = ''; + $path = $matches[2] ?? ''; + + // 检测是否以引号开头 + if (isset($matches[0][0]) && in_array($matches[0][0], ['"', "'"])) { + $originalQuote = $matches[0][0]; + } + + // 构建新URL(强制使用http协议) + return $originalQuote . 'http://219.157.255.213:25326' . $path; + }; + + return preg_replace_callback($pattern, $replacement, $input); +} \ No newline at end of file diff --git a/app/inquiry/controller/InquiryLogController.php b/app/inquiry/controller/InquiryLogController.php index 8bc6761..82f5667 100644 --- a/app/inquiry/controller/InquiryLogController.php +++ b/app/inquiry/controller/InquiryLogController.php @@ -38,7 +38,7 @@ class InquiryLogController extends BaseController $data['r_time'] = $data['r_time'] > 0 ? date('Y-m-d H:i:s', $data['r_time']) : 0; if (isset($data['t_time'])) $data['t_time'] = $data['t_time'] > 0 ? date('Y-m-d H:i:s', $data['t_time']) : 0; - + // var_dump($data); return $data; } @@ -77,131 +77,131 @@ class InquiryLogController extends BaseController } // 导出 - public function export(Request $request) - { - if ($request->method() == "POST") { - $data = $request->all(); + // public function export(Request $request) + // { + // if ($request->method() == "POST") { + // $data = $request->all(); - $where = []; - if (isset($data['log_lusers']) && $data['log_lusers']) { - $where[] = ['enter_user_id', '=', $data['log_lusers']]; - } - if (isset($data['log_gusers']) && $data['log_gusers']) { - $where[] = ['recipient', '=', $data['log_gusers']]; - } - if (isset($data['log_country']) && $data['log_country']) { - $where[] = ['log_country', '=', $data['log_country']]; - } - if (isset($data['log_channel']) && $data['log_channel']) { - $where[] = ['log_type', '=', $data['log_channel']]; - } - if (isset($data['log_times']) && $data['log_times']) { - $times = explode(' - ', $data["log_times"]); - $specificDayStart = trim($times[0]); - $specificDayEnd = trim($times[1]); - $where[] = [ - "c_time", ">", strtotime($specificDayStart) - ]; - $where[] = [ - "c_time", "<=", strtotime($specificDayEnd) - ]; - } + // $where = []; + // if (isset($data['log_lusers']) && $data['log_lusers']) { + // $where[] = ['enter_user_id', '=', $data['log_lusers']]; + // } + // if (isset($data['log_gusers']) && $data['log_gusers']) { + // $where[] = ['recipient', '=', $data['log_gusers']]; + // } + // if (isset($data['log_country']) && $data['log_country']) { + // $where[] = ['log_country', '=', $data['log_country']]; + // } + // if (isset($data['log_channel']) && $data['log_channel']) { + // $where[] = ['log_type', '=', $data['log_channel']]; + // } + // if (isset($data['log_times']) && $data['log_times']) { + // $times = explode(' - ', $data["log_times"]); + // $specificDayStart = trim($times[0]); + // $specificDayEnd = trim($times[1]); + // $where[] = [ + // "c_time", ">", strtotime($specificDayStart) + // ]; + // $where[] = [ + // "c_time", "<=", strtotime($specificDayEnd) + // ]; + // } - // 查询 - $results = InquiryLogModel::with(['enterUser', 'recipientUser', 'country']) - ->where($where) - ->get() - ->map(function ($log) use($data) { - $arr = []; - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['订单号'] = $log->order_no; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['录入人'] = $log->enterUser->name ?? null; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['接单人'] = $log->recipientUser->name ?? null; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['等级'] = $log->log_quality; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['邮箱'] = $log->log_contact_type; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['电话'] = $log->log_contact_tel; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['WhatsApp'] = $log->log_contact_whatsapp; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['联系人'] = $log->log_contacts; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['国家(中文)'] = $log->country->cn_addr ?? null; - } - if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { - $arr['国家(英文)'] = $log->country->en_full_name ?? null; - } - if (isset($data['hsl_r_time']) && $data['hsl_r_time']) { - $arr['发单时间'] = $log->r_time ? date('Y-m-d H:i:s', $log->r_time) : null; - } - if (isset($data['hsl_t_time']) && $data['hsl_t_time']) { - $arr['跟单时间'] = $log->t_time ? date('Y-m-d H:i:s', $log->t_time) : null; - } - return $arr; - }); - // 创建一个新的 Spreadsheet 对象 - $spreadsheet = new Spreadsheet(); - // 设置当前活动的工作表 - $sheet = $spreadsheet->getActiveSheet(); - // 设置工作表的标题 - $sheet->setCellValue('A1', '订单号'); - $sheet->setCellValue('B1', '录入人'); - $sheet->setCellValue('C1', '接单人'); - $sheet->setCellValue('D1', '等级'); - $sheet->setCellValue('E1', '邮箱'); - $sheet->setCellValue('F1', '电话'); - $sheet->setCellValue('G1', 'WhatsApp'); - $sheet->setCellValue('H1', '联系人'); - $sheet->setCellValue('I1', '国家(中文)'); - $sheet->setCellValue('J1', '国家(英文)'); - $sheet->setCellValue('K1', '发单时间'); - $sheet->setCellValue('L1', '跟单时间'); - $data = $results->toArray(); + // // 查询 + // $results = InquiryLogModel::with(['enterUser', 'recipientUser', 'country']) + // ->where($where) + // ->get() + // ->map(function ($log) use($data) { + // $arr = []; + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['订单号'] = $log->order_no; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['录入人'] = $log->enterUser->name ?? null; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['接单人'] = $log->recipientUser->name ?? null; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['等级'] = $log->log_quality; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['邮箱'] = $log->log_contact_type; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['电话'] = $log->log_contact_tel; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['WhatsApp'] = $log->log_contact_whatsapp; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['联系人'] = $log->log_contacts; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['国家(中文)'] = $log->country->cn_addr ?? null; + // } + // if (isset($data['hsl_order_no']) && $data['hsl_order_no']) { + // $arr['国家(英文)'] = $log->country->en_full_name ?? null; + // } + // if (isset($data['hsl_r_time']) && $data['hsl_r_time']) { + // $arr['发单时间'] = $log->r_time ? date('Y-m-d H:i:s', $log->r_time) : null; + // } + // if (isset($data['hsl_t_time']) && $data['hsl_t_time']) { + // $arr['跟单时间'] = $log->t_time ? date('Y-m-d H:i:s', $log->t_time) : null; + // } + // return $arr; + // }); + // // 创建一个新的 Spreadsheet 对象 + // $spreadsheet = new Spreadsheet(); + // // 设置当前活动的工作表 + // $sheet = $spreadsheet->getActiveSheet(); + // // 设置工作表的标题 + // $sheet->setCellValue('A1', '订单号'); + // $sheet->setCellValue('B1', '录入人'); + // $sheet->setCellValue('C1', '接单人'); + // $sheet->setCellValue('D1', '等级'); + // $sheet->setCellValue('E1', '邮箱'); + // $sheet->setCellValue('F1', '电话'); + // $sheet->setCellValue('G1', 'WhatsApp'); + // $sheet->setCellValue('H1', '联系人'); + // $sheet->setCellValue('I1', '国家(中文)'); + // $sheet->setCellValue('J1', '国家(英文)'); + // $sheet->setCellValue('K1', '发单时间'); + // $sheet->setCellValue('L1', '跟单时间'); + // $data = $results->toArray(); - // 将数据填充到工作表中 - $row = 2; // 从第2行开始填充 - foreach ($data as $item) { - $sheet->setCellValue('A' . $row, $item['订单号']); - $sheet->setCellValue('B' . $row, $item['录入人']); - $sheet->setCellValue('C' . $row, $item['接单人']); + // // 将数据填充到工作表中 + // $row = 2; // 从第2行开始填充 + // foreach ($data as $item) { + // $sheet->setCellValue('A' . $row, $item['订单号']); + // $sheet->setCellValue('B' . $row, $item['录入人']); + // $sheet->setCellValue('C' . $row, $item['接单人']); - $sheet->setCellValue('D' . $row, $item['等级']); - $sheet->setCellValue('E' . $row, $item['邮箱']); - $sheet->setCellValue('F' . $row, $item['电话']); + // $sheet->setCellValue('D' . $row, $item['等级']); + // $sheet->setCellValue('E' . $row, $item['邮箱']); + // $sheet->setCellValue('F' . $row, $item['电话']); - $sheet->setCellValue('G' . $row, $item['WhatsApp']); - $sheet->setCellValue('H' . $row, $item['联系人']); - $sheet->setCellValue('I' . $row, $item['国家(中文)']); + // $sheet->setCellValue('G' . $row, $item['WhatsApp']); + // $sheet->setCellValue('H' . $row, $item['联系人']); + // $sheet->setCellValue('I' . $row, $item['国家(中文)']); - $sheet->setCellValue('J' . $row, $item['国家(英文)']); - $sheet->setCellValue('K' . $row, $item['发单时间']); - $sheet->setCellValue('L' . $row, $item['跟单时间']); - $row++; - } + // $sheet->setCellValue('J' . $row, $item['国家(英文)']); + // $sheet->setCellValue('K' . $row, $item['发单时间']); + // $sheet->setCellValue('L' . $row, $item['跟单时间']); + // $row++; + // } - // 创建 Excel 文件的写入器 - $writer = new Xlsx($spreadsheet); - // 名称 - $fileName = $request->user->name.'_'.date('Ymd').'_'.uniqid().'.xlsx'; - // 路径 - $filePath = '/export/'.$fileName; - // 将文件保存到本地 - $writer->save(public_path().$filePath); + // // 创建 Excel 文件的写入器 + // $writer = new Xlsx($spreadsheet); + // // 名称 + // $fileName = $request->user->name.'_'.date('Ymd').'_'.uniqid().'.xlsx'; + // // 路径 + // $filePath = '/export/'.$fileName; + // // 将文件保存到本地 + // $writer->save(public_path().$filePath); - return $this->successResponse(['url'=>$filePath, 'name'=>$fileName]); - } - return view('inquiry-log/export'); - } + // return $this->successResponse(['url'=>$filePath, 'name'=>$fileName]); + // } + // return view('inquiry-log/export'); + // } } \ No newline at end of file diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 8f40a0e..7a935b4 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -127,6 +127,22 @@ class InquiryLogService $data['email'] = trim($data["email"]); $data['tel'] = trim($data['tel']); $data['whatsapp'] = trim($data['whatsapp']); + + // 将OSS直连地址替换为代理地址 + $ossDomain = 'https://vehicle-album.oss-cn-beijing.aliyuncs.com/'; + $proxyPath = getenv('MAIN_DOMAIN').'/proxy/ossurl?file='; + + $data['content'] = str_replace( + '?Expires=', + '&Expires=', + $data['content'] + ); + $data['content'] = str_replace( + $ossDomain, + $proxyPath, + $data['content'] + ); + return $data; } @@ -219,7 +235,7 @@ class InquiryLogService $quality = [ 1 => '💛 金牌商机', 2 => '🤍 银牌商机', - 3 => '🤍 银牌商机' + 3 => '🧡 银牌商机' ]; return $quality[$id]; } diff --git a/config/route.php b/config/route.php index 8d12177..303c4aa 100644 --- a/config/route.php +++ b/config/route.php @@ -33,6 +33,8 @@ Route::any('/perm', [app\controller\IndexController::class, 'perm']); Route::any('/feishu/event', [app\common\controller\NotifyController::class, 'feishuEvent']); // 飞书回调 Route::any('/feishu/callback', [app\common\controller\NotifyController::class, 'feishuCallback']); +// 代理oss文件地址 +Route::any('/proxy/ossurl', [app\common\controller\UploadController::class, 'proxy']); // 公共 Route::group('/common', function () { From 6eb6cd61be895f7f4f32befa8b372e6a35ae6a8a Mon Sep 17 00:00:00 2001 From: mzl92 Date: Mon, 28 Jul 2025 18:51:24 +0800 Subject: [PATCH 105/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 516299c..496f0a0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ .env /tests/tmp /tests/.phpunit.result.cache +/public/uploads \ No newline at end of file From 816ed1a29716d5d935efd4d25904687ded7ddabe Mon Sep 17 00:00:00 2001 From: mzl92 Date: Mon, 28 Jul 2025 19:36:21 +0800 Subject: [PATCH 106/120] =?UTF-8?q?=E8=84=9A=E6=9C=AC=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/command/DomainCommand.php | 58 +++++++++++++++++++++++++++++++++++ app/command/TbCommand.php | 21 ------------- 2 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 app/command/DomainCommand.php diff --git a/app/command/DomainCommand.php b/app/command/DomainCommand.php new file mode 100644 index 0000000..61e0777 --- /dev/null +++ b/app/command/DomainCommand.php @@ -0,0 +1,58 @@ +orderBy('id', 'desc')->get(); + + foreach ($list as $item) { + if (!$item->content) { + continue; + } + $item->content = processRichTextImagesEnhanced($item->content); + $item->save(); + } + return self::SUCCESS; + } +} + +/** + * 增强版 - 为富文本内容中的图片URL添加域名前缀 + * + * @param string $content 富文本HTML内容 + * @param string $domain 域名(如: https://fdesk.hnxinyouhe.com) + * @return string 处理后的富文本内容 + */ +function processRichTextImagesEnhanced($content, $domain = 'https://fdesk.hnxinyouhe.com') { + // 确保域名不以斜杠结尾 + $domain = rtrim($domain, '/'); + + // 要处理的属性列表 + $attributes = ['src', 'data-href', 'href', 'data-src', 'data-original']; + + foreach ($attributes as $attr) { + $content = preg_replace( + '/('.$attr.'=["\'])(\/uploads\/)/i', + '$1' . $domain . '$2', + $content + ); + } + + return $content; +} \ No newline at end of file diff --git a/app/command/TbCommand.php b/app/command/TbCommand.php index 3618b40..d60482f 100644 --- a/app/command/TbCommand.php +++ b/app/command/TbCommand.php @@ -25,25 +25,4 @@ class TbCommand extends Command } return self::SUCCESS; } -} - - -function replaceRichTextUrls($input) { - // 匹配旧域名及其引号包裹情况(包含http/https协议) - $pattern = '#(https?://server\.xyh\.hnzhwlkj\.club|["\']https?://server\.xyh\.hnzhwlkj\.club)(/[^\s"\'<>]*)?#iu'; - - $replacement = function ($matches) { - $originalQuote = ''; - $path = $matches[2] ?? ''; - - // 检测是否以引号开头 - if (isset($matches[0][0]) && in_array($matches[0][0], ['"', "'"])) { - $originalQuote = $matches[0][0]; - } - - // 构建新URL(强制使用http协议) - return $originalQuote . 'http://219.157.255.213:25326' . $path; - }; - - return preg_replace_callback($pattern, $replacement, $input); } \ No newline at end of file From ad21c932ca0031f83d58b1bb5168412a00011ebb Mon Sep 17 00:00:00 2001 From: mzl92 Date: Mon, 28 Jul 2025 19:44:57 +0800 Subject: [PATCH 107/120] =?UTF-8?q?=E8=84=9A=E6=9C=AC=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/command/DomainCommand.php | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/app/command/DomainCommand.php b/app/command/DomainCommand.php index 61e0777..d3c7496 100644 --- a/app/command/DomainCommand.php +++ b/app/command/DomainCommand.php @@ -27,29 +27,47 @@ class DomainCommand extends Command } $item->content = processRichTextImagesEnhanced($item->content); $item->save(); + $output->writeln("处理了{$item->id} \n"); } return self::SUCCESS; } } /** - * 增强版 - 为富文本内容中的图片URL添加域名前缀 + * 增强版 - 处理富文本内容中的图片URL * * @param string $content 富文本HTML内容 - * @param string $domain 域名(如: https://fdesk.hnxinyouhe.com) + * @param string $newDomain 新域名(如: https://fdesk.hnxinyouhe.com) + * @param string $oldDomain 旧域名(如: http://219.157.255.213:25326) * @return string 处理后的富文本内容 */ -function processRichTextImagesEnhanced($content, $domain = 'https://fdesk.hnxinyouhe.com') { - // 确保域名不以斜杠结尾 - $domain = rtrim($domain, '/'); +function processRichTextImagesEnhanced($content, $newDomain = 'https://fdesk.hnxinyouhe.com', $oldDomain = 'http://219.157.255.213:25326') { + // 标准化域名格式(去除末尾斜杠) + $newDomain = rtrim($newDomain, '/'); + $oldDomain = rtrim($oldDomain, '/'); // 要处理的属性列表 $attributes = ['src', 'data-href', 'href', 'data-src', 'data-original']; foreach ($attributes as $attr) { + // 情况1:替换旧域名为新域名 + $content = preg_replace( + '/('.$attr.'=["\'])'.preg_quote($oldDomain, '/').'(\/uploads\/)/i', + '$1' . $newDomain . '$2', + $content + ); + + // 情况2:给无域名的路径添加新域名 $content = preg_replace( '/('.$attr.'=["\'])(\/uploads\/)/i', - '$1' . $domain . '$2', + '$1' . $newDomain . '$2', + $content + ); + + // 情况3:处理可能存在的其他域名(可选) + $content = preg_replace( + '/('.$attr.'=["\'])(https?:\/\/[^\/]+)(\/uploads\/)/i', + '$1' . $newDomain . '$3', $content ); } From 17c34e09854a6501f25e5d9a7051a79d509fae79 Mon Sep 17 00:00:00 2001 From: mzl92 Date: Mon, 28 Jul 2025 19:51:04 +0800 Subject: [PATCH 108/120] =?UTF-8?q?=E5=A4=84=E7=90=86=E5=AF=8C=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E5=9B=BE=E7=89=87=E6=98=BE=E7=A4=BA=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=AF=8C=E6=96=87=E6=9C=AC=E5=9B=BE=E7=89=87=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/UploadController.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/common/controller/UploadController.php b/app/common/controller/UploadController.php index 9d8959d..e08fa9b 100644 --- a/app/common/controller/UploadController.php +++ b/app/common/controller/UploadController.php @@ -12,6 +12,10 @@ class UploadController use ResponseTrait; + protected $noNeedLogin = ['proxy']; + + protected $noNeedHeaderData = ['proxy']; + public function upload(Request $request) { try { From 39bd343893413e3322d66e3305e480215edae15a Mon Sep 17 00:00:00 2001 From: mzl92 Date: Tue, 29 Jul 2025 08:58:51 +0800 Subject: [PATCH 109/120] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=AF=A2=E5=8D=95?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/controller/NotifyController.php | 2 +- .../controller/InquiryLogController.php | 21 +++++++++++++++---- app/inquiry/service/InquiryLogService.php | 6 +++--- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/common/controller/NotifyController.php b/app/common/controller/NotifyController.php index 9e4fffb..f6e728e 100644 --- a/app/common/controller/NotifyController.php +++ b/app/common/controller/NotifyController.php @@ -126,7 +126,7 @@ class NotifyController try { $data = $request->post(); - var_dump($data); + var_dump('飞书事件回调:'.$data); return json($data); } catch (\Throwable $e) { diff --git a/app/inquiry/controller/InquiryLogController.php b/app/inquiry/controller/InquiryLogController.php index 82f5667..dec1ca4 100644 --- a/app/inquiry/controller/InquiryLogController.php +++ b/app/inquiry/controller/InquiryLogController.php @@ -30,14 +30,27 @@ class InquiryLogController extends BaseController $countdown = 60 * 60; $data['countdown'] = (time() - $data['r_time']) < $countdown ? $data['r_time'] + (60*60) : time()-1; } - if (isset($data['created_at'])) + + if (isset($data['created_at'])) { $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']); - if (isset($data['updated_at'])) + } + + if (isset($data['updated_at'])) { $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']); - if (isset($data['r_time'])) + } + + if (isset($data['r_time'])) { $data['r_time'] = $data['r_time'] > 0 ? date('Y-m-d H:i:s', $data['r_time']) : 0; - if (isset($data['t_time'])) + } + + if (isset($data['t_time'])) { $data['t_time'] = $data['t_time'] > 0 ? date('Y-m-d H:i:s', $data['t_time']) : 0; + } + + if (isset($data['quality'])) { + $inquiryLogService = new InquiryLogService(); + $data['quality'] = $inquiryLogService->qualityName($data['quality']); + } // var_dump($data); return $data; } diff --git a/app/inquiry/service/InquiryLogService.php b/app/inquiry/service/InquiryLogService.php index 7a935b4..c736ceb 100644 --- a/app/inquiry/service/InquiryLogService.php +++ b/app/inquiry/service/InquiryLogService.php @@ -233,9 +233,9 @@ class InquiryLogService return '🤍 银牌商机'; } $quality = [ - 1 => '💛 金牌商机', - 2 => '🤍 银牌商机', - 3 => '🧡 银牌商机' + 1 => '💛金牌商机', + 2 => '🤍银牌商机', + 3 => '🧡铜牌商机' ]; return $quality[$id]; } From 01163a4c8da40c48aecbef017974b08c703f647e Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Tue, 29 Jul 2025 09:32:23 +0800 Subject: [PATCH 110/120] =?UTF-8?q?=E5=95=86=E6=9C=BA=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/service/UserService.php | 1 + app/crm/service/CustomerService.php | 1 + config/constants/Constants.php | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 25de588..d0d9c23 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -54,6 +54,7 @@ class UserService $ossservice = new OssService(); return [ 'id' => $user->id, + 'sn' => $user->sn, 'name' => $user->name, 'position' => $user->position, 'socialize' => $user->socialize, diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 0e5ca8f..b54eeab 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -110,6 +110,7 @@ class CustomerService public static function getCustomer($customer, $language = 'zh_CN', $tag = false) { $customer['grade_txt'] = Util::language($language, Constants::GRADE_EN[$customer['grade']], Constants::GRADE_ZH[$customer['grade']], Constants::GRADE_RU[$customer['grade']]); + $customer['magnitude'] = ($customer['magnitude']==0)?1:$customer['magnitude']; $customer['magnitude_txt'] = Util::language($language, Constants::MAGNITUDE_EN[$customer['magnitude']], Constants::MAGNITUDE[$customer['magnitude']], Constants::MAGNITUDE_RU[$customer['magnitude']]); $customer['sex_txt'] = Util::language($language, Constants::SEX_EN[$customer['sex']], Constants::SEX[$customer['sex']], Constants::SEX_RU[$customer['sex']]); diff --git a/config/constants/Constants.php b/config/constants/Constants.php index 6797078..7b86a1d 100644 --- a/config/constants/Constants.php +++ b/config/constants/Constants.php @@ -71,9 +71,9 @@ class Constants ]; //客户重要性 - public const CUSTOMER_MAGNITUDE_INITIAL = 0; // 金牌商机 - public const CUSTOMER_MAGNITUDE_COMMUNICATE = 1; // 银牌商机 - public const CUSTOMER_MAGNITUDE_PLAN = 2; // 铜牌商机 + public const CUSTOMER_MAGNITUDE_INITIAL = 1; // 金牌商机 + public const CUSTOMER_MAGNITUDE_COMMUNICATE = 2; // 银牌商机 + public const CUSTOMER_MAGNITUDE_PLAN = 3; // 铜牌商机 public const MAGNITUDE = [ self::CUSTOMER_MAGNITUDE_INITIAL => '金牌商机', From 00f920df1865340e78ab1beb4aaecb06f5be887f Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Tue, 29 Jul 2025 10:06:06 +0800 Subject: [PATCH 111/120] =?UTF-8?q?=E5=A4=B4=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/service/UserService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index d0d9c23..0fa6458 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -63,7 +63,7 @@ class UserService 'country_id' => $user->country_id, 'country_txt' => Country::where('id',$user->country_id)->value('cn_addr'), 'avatar' => $user->avatar, - 'avatar_full' => (!empty($user->avatar))?$ossservice->generatePrivateUrl($user->avatar):'', + 'avatar_full' => $user->avatar, 'phone' => $user->phone, 'corp_name' => $corp_name, 'email' => $user->email, From 6722f24fcfadd2c2deedab06310738cb2fc63aa1 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Tue, 29 Jul 2025 10:13:50 +0800 Subject: [PATCH 112/120] =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/service/UserService.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/common/service/UserService.php b/app/common/service/UserService.php index 0fa6458..7859553 100644 --- a/app/common/service/UserService.php +++ b/app/common/service/UserService.php @@ -67,6 +67,7 @@ class UserService 'phone' => $user->phone, 'corp_name' => $corp_name, 'email' => $user->email, + 'sex' => $user->sex, 'status' => $user->status, 'roles' => $user->roles->pluck('name') ]; From 2dae0f42c42f1fa64e7687c5915761c07e31058c Mon Sep 17 00:00:00 2001 From: mzl92 Date: Tue, 29 Jul 2025 10:19:15 +0800 Subject: [PATCH 113/120] ts --- app/common/controller/NotifyController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/controller/NotifyController.php b/app/common/controller/NotifyController.php index f6e728e..9e4fffb 100644 --- a/app/common/controller/NotifyController.php +++ b/app/common/controller/NotifyController.php @@ -126,7 +126,7 @@ class NotifyController try { $data = $request->post(); - var_dump('飞书事件回调:'.$data); + var_dump($data); return json($data); } catch (\Throwable $e) { From f688fa342a95af2f509df83c1a5a1cccc383718f Mon Sep 17 00:00:00 2001 From: mzl92 Date: Tue, 29 Jul 2025 10:22:17 +0800 Subject: [PATCH 114/120] =?UTF-8?q?=E5=95=86=E6=9C=BA=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/constants/Constants.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/constants/Constants.php b/config/constants/Constants.php index 7b86a1d..fa0dd81 100644 --- a/config/constants/Constants.php +++ b/config/constants/Constants.php @@ -76,9 +76,9 @@ class Constants public const CUSTOMER_MAGNITUDE_PLAN = 3; // 铜牌商机 public const MAGNITUDE = [ - self::CUSTOMER_MAGNITUDE_INITIAL => '金牌商机', - self::CUSTOMER_MAGNITUDE_COMMUNICATE => '银牌商机', - self::CUSTOMER_MAGNITUDE_PLAN => '铜牌商机', + self::CUSTOMER_MAGNITUDE_INITIAL => '💛金牌商机', + self::CUSTOMER_MAGNITUDE_COMMUNICATE => '🤍银牌商机', + self::CUSTOMER_MAGNITUDE_PLAN => '🧡铜牌商机', ]; public const MAGNITUDE_EN = [ self::CUSTOMER_MAGNITUDE_INITIAL => 'Gold Medal', From 78cfb92a9e5e71f1b6eae64e26cbe380e8d6b47d Mon Sep 17 00:00:00 2001 From: mzl92 Date: Tue, 29 Jul 2025 11:12:47 +0800 Subject: [PATCH 115/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/logic/PermLogic.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/logic/PermLogic.php b/app/common/logic/PermLogic.php index fbd5218..9144d74 100644 --- a/app/common/logic/PermLogic.php +++ b/app/common/logic/PermLogic.php @@ -30,7 +30,7 @@ class PermLogic $role->resources->filter(function ($resource) use($system) { // var_dump($resource->toArray()); // 只保留需要权限认证的资源(或公共资源) - return !$resource->no_auth && $system->id == $resource->system_id; + return !$resource->no_auth && ($system->id == $resource->system_id || $resource->system_id == 0); }) ); } From 69e79c0e7472c35c64310b9e0d810b7c3bba6773 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Tue, 29 Jul 2025 11:51:00 +0800 Subject: [PATCH 116/120] =?UTF-8?q?=E5=B7=A5=E5=8D=95=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ticket/controller/TicketController.php | 1 + app/ticket/service/TicketService.php | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/app/ticket/controller/TicketController.php b/app/ticket/controller/TicketController.php index 3eb3222..cb2290e 100644 --- a/app/ticket/controller/TicketController.php +++ b/app/ticket/controller/TicketController.php @@ -75,6 +75,7 @@ class TicketController extends BaseController public function getStatus(Request $request) { try { + $request->setGet('user_id', $request->user->uid); $result = (new TicketService())->getStatus($request->all()); return $result ? $this->success($result) diff --git a/app/ticket/service/TicketService.php b/app/ticket/service/TicketService.php index 6250b5f..cdacddf 100644 --- a/app/ticket/service/TicketService.php +++ b/app/ticket/service/TicketService.php @@ -129,6 +129,19 @@ class TicketService foreach ($ticket_status as $key => $value) { $status[] = ['value' => $key, 'text' => $value]; } + + if (empty($data['id'])) { + return $status; + } + + $res = (new TicketLogic())->find($data['id']); + if(!empty($res) && $res->user_id == $data['user_id']){ + $status = [ + ['value' => Constants::TICKET_STATUS['PENDING'], 'text' => $ticket_status[Constants::TICKET_STATUS['PENDING']]], + ['value' => Constants::TICKET_STATUS['REJECTED'], 'text' => $ticket_status[Constants::TICKET_STATUS['REJECTED']]], + ]; + } + return $status; } } From e191788e0a4b6445a1df231007bb3dc443955a95 Mon Sep 17 00:00:00 2001 From: mzl92 Date: Tue, 29 Jul 2025 12:33:48 +0800 Subject: [PATCH 117/120] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=95=86=E6=9C=BA?= =?UTF-8?q?=E7=AD=89=E7=BA=A7=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/inquiry/controller/InquiryLogController.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/inquiry/controller/InquiryLogController.php b/app/inquiry/controller/InquiryLogController.php index dec1ca4..89af6b0 100644 --- a/app/inquiry/controller/InquiryLogController.php +++ b/app/inquiry/controller/InquiryLogController.php @@ -46,11 +46,6 @@ class InquiryLogController extends BaseController if (isset($data['t_time'])) { $data['t_time'] = $data['t_time'] > 0 ? date('Y-m-d H:i:s', $data['t_time']) : 0; } - - if (isset($data['quality'])) { - $inquiryLogService = new InquiryLogService(); - $data['quality'] = $inquiryLogService->qualityName($data['quality']); - } // var_dump($data); return $data; } From caa04809a933fae1c73f411efb2482663518267b Mon Sep 17 00:00:00 2001 From: mzl Date: Tue, 29 Jul 2025 19:39:17 +0800 Subject: [PATCH 118/120] Merge branch 'master' into develop # Conflicts: # app/command/DomainCommand.php # app/common/utils/FeishuMessage.php # app/queue/redis/FeishuMessage.php # app/queue/redis/InquiryToCrm.php --- app/command/DomainCommand.php | 2 +- app/command/FixInquiryLogOrderNo.php | 100 +++++++++++++++++++++++++++ app/common/utils/FeishuMessage.php | 18 +++-- app/queue/redis/FeishuMessage.php | 14 ++-- app/queue/redis/InquiryToCrm.php | 1 + 5 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 app/command/FixInquiryLogOrderNo.php diff --git a/app/command/DomainCommand.php b/app/command/DomainCommand.php index d3c7496..d23e755 100644 --- a/app/command/DomainCommand.php +++ b/app/command/DomainCommand.php @@ -41,7 +41,7 @@ class DomainCommand extends Command * @param string $oldDomain 旧域名(如: http://219.157.255.213:25326) * @return string 处理后的富文本内容 */ -function processRichTextImagesEnhanced($content, $newDomain = 'https://fdesk.hnxinyouhe.com', $oldDomain = 'http://219.157.255.213:25326') { +function processRichTextImagesEnhanced($content, $newDomain = 'https://x-server.hnxinyouhe.com', $oldDomain = 'http://219.157.255.213:25326') { // 标准化域名格式(去除末尾斜杠) $newDomain = rtrim($newDomain, '/'); $oldDomain = rtrim($oldDomain, '/'); diff --git a/app/command/FixInquiryLogOrderNo.php b/app/command/FixInquiryLogOrderNo.php new file mode 100644 index 0000000..ed959cd --- /dev/null +++ b/app/command/FixInquiryLogOrderNo.php @@ -0,0 +1,100 @@ +writeln('Starting duplicate order_no fix process...'); + + // Find all duplicate order_no values using Laravel-style query + $duplicates = Db::table('inquiry_log') + ->select('order_no', Db::raw('COUNT(*) as count')) + ->groupBy('order_no') + ->having('count', '>', 1) + ->get(); + + if ($duplicates->isEmpty()) { + $output->writeln('No duplicate order_no values found.'); + Db::commit(); + return self::SUCCESS; + } + + $output->writeln(sprintf('Found %d order_no values with duplicates.', $duplicates->count())); + + $totalFixed = 0; + + foreach ($duplicates as $dup) { + $orderNo = $dup->order_no; + + $output->writeln("Processing duplicates for order_no: {$orderNo}"); + + // Get all records with this order_no, ordered by created_at (oldest first) + $records = Db::table('inquiry_log') + ->where('order_no', $orderNo) + ->orderBy('created_at', 'asc') + ->get(); + + // Keep the first record as is, modify the rest + $keepOriginal = true; + + foreach ($records as $record) { + if ($keepOriginal) { + $output->writeln("Keeping original record ID {$record->id} with order_no {$record->order_no}"); + $keepOriginal = false; + continue; + } + + // Generate new order_no by appending suffix + $newOrderNo = orderNum(); + $output->writeln("Updating record ID {$record->id} from {$record->order_no} to {$newOrderNo}"); + + // Update the record + Db::table('inquiry_log') + ->where('id', $record->id) + ->update(['order_no' => $newOrderNo]); + + $totalFixed++; + + sleep(1); + } + } + + Db::commit(); + $output->writeln("Successfully fixed {$totalFixed} duplicate records."); + + // Verify no duplicates remain + $remainingDuplicates = Db::table('inquiry_log') + ->select('order_no', Db::raw('COUNT(*) as count')) + ->groupBy('order_no') + ->having('count', '>', 1) + ->get(); + + if ($remainingDuplicates->isEmpty()) { + $output->writeln('Verification: No duplicate order_no values remain.'); + } else { + $output->writeln('Warning: Some duplicates still remain. Please run the command again.'); + } + + return self::SUCCESS; + } catch (\Throwable $e) { + Db::rollback(); + $output->writeln("Error: " . $e->getMessage()); + Log::error("FixInquiryLogOrderNo command failed: " . $e->getMessage()); + return self::FAILURE; + } + } +} \ No newline at end of file diff --git a/app/common/utils/FeishuMessage.php b/app/common/utils/FeishuMessage.php index 311ffd9..4783a9c 100644 --- a/app/common/utils/FeishuMessage.php +++ b/app/common/utils/FeishuMessage.php @@ -7,13 +7,23 @@ use Webman\Exception\BusinessException; class FeishuMessage { - protected $app_id = 'cli_a72f13d6c022500d'; - protected $app_secret = 'Kxtbq70OQWrEhpltrufRZc76z7kjDqPT'; + protected $app_id = 'cli_a6183b882cb8500d'; + protected $app_secret = 'VqYib48u4EtDIa6nG9qH3f0fYCA1bCvs'; + protected $state = 'hesu'; protected $class = null; - public function __construct() + public function __construct($options = []) { - $this->class = new Feishu($this->app_id, $this->app_secret); + if (isset($options['app_id']) && $options['app_id']) { + $this->app_id = $options['app_id']; + } + if (isset($options['app_secret']) && $options['app_secret']) { + $this->app_secret = $options['app_secret']; + } + if (isset($options['state']) && $options['state']) { + $this->state = $options['state']; + } + $this->class = new Feishu($this->app_id, $this->app_secret, $this->state); } /** diff --git a/app/queue/redis/FeishuMessage.php b/app/queue/redis/FeishuMessage.php index c2663c6..7aa510c 100644 --- a/app/queue/redis/FeishuMessage.php +++ b/app/queue/redis/FeishuMessage.php @@ -49,6 +49,7 @@ class FeishuMessage implements Consumer 'app_id' => $feishuAppInfo->app_id, 'app_secret' => $feishuAppInfo->app_secret, + 'state' => $feishuAppInfo->state, 'msg_type' => 'interactive', 'receive_id_type' => 'open_id', 'receive_id' => $reInfo->open_id @@ -62,9 +63,10 @@ class FeishuMessage implements Consumer // 接受者处理 if ($data['receive_type'] == 'group') { - $appId = 'cli_a72f13d6c022500d'; - $appSecret = 'Kxtbq70OQWrEhpltrufRZc76z7kjDqPT'; - $receiveId = 'oc_4f574e6f8bc5b8f3e5175e67ebc5725c'; + $appId = 'cli_a6183b882cb8500d'; + $appSecret = 'VqYib48u4EtDIa6nG9qH3f0fYCA1bCvs'; + $state = 'hesu'; + $receiveId = 'oc_a52d9f89ad76735279088c78beade8dc'; $receiveIdType = 'chat_id'; } else { // 跟单人 @@ -75,6 +77,7 @@ class FeishuMessage implements Consumer // 推送信息 $appId = $feishuAppInfo->app_id; $appSecret = $feishuAppInfo->app_secret; + $state = $feishuAppInfo->state; $receiveId = $reInfo->open_id; $receiveIdType = 'open_id'; } @@ -91,13 +94,14 @@ class FeishuMessage implements Consumer 'app_id' => $appId, 'app_secret' => $appSecret, + 'state' => $state, 'msg_type' => 'interactive', 'receive_id_type' => $receiveIdType, 'receive_id' => $receiveId ]; } - - $feishuMsg = new FeishuMsg(); + + $feishuMsg = new FeishuMsg($options); $feishuMsg->messageSend($data['scene'], $options); } catch (\Throwable $th) { throw $th; diff --git a/app/queue/redis/InquiryToCrm.php b/app/queue/redis/InquiryToCrm.php index ecac73e..ac27813 100644 --- a/app/queue/redis/InquiryToCrm.php +++ b/app/queue/redis/InquiryToCrm.php @@ -24,6 +24,7 @@ class InquiryToCrm implements Consumer try { // 获取询盘信息 $info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', $data['id'])->where('sync_crm', 0)->first(); + if ($info) { // 获取用户信息 $recipient = 0; From 68bbdc0fba91a0480c610bba7fa107b51847701b Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Wed, 30 Jul 2025 10:13:26 +0800 Subject: [PATCH 119/120] =?UTF-8?q?=E5=BD=95=E5=85=A5=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/controller/CustomerController.php | 1 + app/crm/service/CustomerService.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/app/crm/controller/CustomerController.php b/app/crm/controller/CustomerController.php index 42003c0..e1e3f79 100644 --- a/app/crm/controller/CustomerController.php +++ b/app/crm/controller/CustomerController.php @@ -272,6 +272,7 @@ class CustomerController extends BaseController ]; if (Util::isEmpty($id)) { + $data['enter_user_id'] = $request->user->uid; $res = Customer::create($data); } else { unset($data['grade']); diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index b54eeab..7317ac3 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -2,6 +2,7 @@ namespace app\crm\service; +use app\common\model\User; use app\crm\logic\CustomerLogic; use app\crm\model\CarType; use app\crm\model\Channel; @@ -145,6 +146,8 @@ class CustomerService $customer['channel_name'] = empty($customer['channel_id'])?'':Channel::where(['id'=>$customer['channel_id']])->value($nameLang); // $customer['last_follow'] = $last_follow; + $customer['enter_user_name'] = empty($customer['enter_user_id']) ? '' : User::find($customer['enter_user_id'])->value('name'); + if ($tag) { foreach ($customer['tags'] as &$tag) { $tag['names'] = unserialize($tag['names']); From c8fb9f5b1996c0a4996c43f0ce9798304bf15706 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Wed, 30 Jul 2025 10:22:25 +0800 Subject: [PATCH 120/120] =?UTF-8?q?=E5=BD=95=E5=85=A5=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/crm/service/CustomerService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/crm/service/CustomerService.php b/app/crm/service/CustomerService.php index 7317ac3..c6518aa 100644 --- a/app/crm/service/CustomerService.php +++ b/app/crm/service/CustomerService.php @@ -146,7 +146,7 @@ class CustomerService $customer['channel_name'] = empty($customer['channel_id'])?'':Channel::where(['id'=>$customer['channel_id']])->value($nameLang); // $customer['last_follow'] = $last_follow; - $customer['enter_user_name'] = empty($customer['enter_user_id']) ? '' : User::find($customer['enter_user_id'])->value('name'); + $customer['enter_user_name'] = empty($customer['enter_user_id']) ? '' : User::where('id',$customer['enter_user_id'])->value('name'); if ($tag) { foreach ($customer['tags'] as &$tag) {