Merge branch 'develop'
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
User ← user_roles → Role ← role_resources → Resource (type=menu/button)
|
||||
↘︎
|
||||
role_permissions → Permission
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\ResourceServer;
|
||||
use GuzzleHttp\Psr7\Response as Psr7Response;
|
||||
use app\service\AuthService;
|
||||
use app\model\User;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
class AuthController_bak
|
||||
{
|
||||
|
||||
protected AuthorizationServer $authServer;
|
||||
protected ResourceServer $resourceServer;
|
||||
|
||||
public function __construct(AuthorizationServer $auth, ResourceServer $res)
|
||||
{
|
||||
$this->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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\validate\RoleValidate;
|
||||
use app\common\service\RoleService;
|
||||
use support\Request;
|
||||
|
||||
class RoleController extends \app\common\controller\BaseController
|
||||
{
|
||||
/**
|
||||
* 获取角色列表
|
||||
*/
|
||||
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 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,40 @@ use support\Request;
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 不需要头信息的方法
|
||||
*/
|
||||
protected $noNeedHeader = ['feishuAuth'];
|
||||
|
||||
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 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,54 +52,64 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
// }
|
||||
}
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
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;
|
||||
|
||||
@@ -37,4 +37,12 @@ abstract class BaseController
|
||||
// 验证成功,返回原始数据
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数处理
|
||||
*/
|
||||
public function requestData(Request $request)
|
||||
{
|
||||
return $request->all() + $request->basic_data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use support\Request;
|
||||
use app\common\service\CorpService;
|
||||
use app\common\service\CorpFeishuAppService;
|
||||
|
||||
class PresetController extends BaseController
|
||||
{
|
||||
|
||||
protected $noNeedLogin = ['corps', 'feishuApp'];
|
||||
|
||||
public function corps()
|
||||
{
|
||||
try {
|
||||
$result = (new CorpService())->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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\service\OssService;
|
||||
use app\common\traits\ResponseTrait;
|
||||
use support\Request;
|
||||
use Shopwwi\WebmanFilesystem\Facade\Storage;
|
||||
|
||||
class UploadController
|
||||
{
|
||||
|
||||
use ResponseTrait;
|
||||
|
||||
public function upload(Request $request)
|
||||
{
|
||||
try {
|
||||
$scene = $request->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use support\Request;
|
||||
use app\common\service\UserService;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
public function info(Request $request)
|
||||
{
|
||||
try {
|
||||
$uid = $request->user->uid;
|
||||
$result = (new UserService())->getUserInfo($uid);
|
||||
return $this->success($result, '获取用户信息');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+267
-239
@@ -2,10 +2,13 @@
|
||||
|
||||
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\UserSn;
|
||||
use app\common\utils\Feishu;
|
||||
use app\common\utils\Jwt;
|
||||
use support\Redis;
|
||||
use support\exception\BusinessException;
|
||||
@@ -19,75 +22,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']);
|
||||
|
||||
// 记录 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);
|
||||
if ($user->isLogin) {
|
||||
$this->validateSystemPermission($user, $system);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,36 +50,132 @@ class AuthLogic
|
||||
*/
|
||||
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);
|
||||
|
||||
// 创建用户记录
|
||||
$user = $this->createUser($data);
|
||||
// 获取企业ID
|
||||
$corpInfo = $this->validateCorp($data['corp_abbr']);
|
||||
$data['corp_id'] = $corpInfo->id;
|
||||
|
||||
// 更新用户编号的用户 ID
|
||||
$this->updateUserId($user->id, $sn->user_sn);
|
||||
// 处理账户
|
||||
$data = $data + normalizeAccount($data['account']);
|
||||
|
||||
// 创建用户记录
|
||||
$user = $this->createUser($data);
|
||||
|
||||
// 注册后如果直接登录(返回token)
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
// 更新用户编号的用户 ID
|
||||
$this->updateUserId($user->id, $sn->user_sn);
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
return $this->generateTokensAndReturn($user, $system, $data);
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找或创建飞书用户
|
||||
*/
|
||||
private function getOrCreateUser(array $feishuUserInfo)
|
||||
{
|
||||
try {
|
||||
$phone = $this->sanitizePhone($feishuUserInfo['mobile'] ?? '');
|
||||
$user = User::where('phone', $phone)->where('corp_id', $feishuUserInfo['corp_id'])->first();
|
||||
|
||||
if (!$user) {
|
||||
// 获取编号
|
||||
$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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤和格式化手机号
|
||||
*/
|
||||
private function sanitizePhone($mobile)
|
||||
{
|
||||
return isset($mobile) ? str_replace('+86', '', $mobile) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并记录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 +187,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 +230,6 @@ class AuthLogic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验token有效性
|
||||
*/
|
||||
@@ -278,78 +253,27 @@ 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);
|
||||
$systemInfo = $this->validateSystem($systemCode);
|
||||
$fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemInfo->id);
|
||||
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,
|
||||
'system_id' => $systemInfo->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 +288,7 @@ class AuthLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统验证
|
||||
* 验证系统信息
|
||||
*/
|
||||
private function validateSystem(string $systemCode)
|
||||
{
|
||||
@@ -376,7 +300,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, int $systemId)
|
||||
{
|
||||
$feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first();
|
||||
if (!$feishuAppInfo) {
|
||||
throw new BusinessException('飞书应用不存在', 403);
|
||||
}
|
||||
return $feishuAppInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户系统权限
|
||||
*/
|
||||
private function validateSystemPermission(User $user, Systems $system)
|
||||
{
|
||||
@@ -388,6 +336,42 @@ class AuthLogic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的用户编号
|
||||
*
|
||||
* @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 字段
|
||||
@@ -433,43 +417,87 @@ class AuthLogic
|
||||
return $refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证扫码令牌
|
||||
*/
|
||||
private function validateQrCodeToken(string $qrCodeToken)
|
||||
{
|
||||
$userId = Redis::get("qr_code_token:{$qrCodeToken}");
|
||||
if (!$userId) {
|
||||
throw new BusinessException('扫码令牌无效或已过期', 401);
|
||||
}
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用飞书API获取用户信息(示例)
|
||||
* 刷新token
|
||||
* @param string $refreshToken
|
||||
* @param string|null $systemCode 可选,指定系统
|
||||
* @return array
|
||||
*/
|
||||
private function getFeishuUserInfo(string $code)
|
||||
public function refreshToken(string $refreshToken, ?string $systemCode = null): array
|
||||
{
|
||||
// 实际实现需要调用飞书 API
|
||||
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 [
|
||||
'user_id' => 'feishu_user_123',
|
||||
'name' => '飞书用户',
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
// 'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找或创建飞书用户(示例)
|
||||
* 登出/失效token
|
||||
*/
|
||||
private function getOrCreateUser(array $feishuUserInfo)
|
||||
public function logout(?string $token, ?string $systemCode = null)
|
||||
{
|
||||
$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();
|
||||
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]);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制拉黑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]);
|
||||
}
|
||||
}
|
||||
+451
-216
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\UserTokenLog;
|
||||
use app\common\model\Systems;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use app\common\utils\Jwt;
|
||||
use support\Cache;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class AuthLogic_bak2
|
||||
{
|
||||
/**
|
||||
* 账号密码登录
|
||||
*/
|
||||
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);
|
||||
|
||||
// 记录 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class BaseLogic
|
||||
{
|
||||
/**
|
||||
* 获取列表
|
||||
*/
|
||||
public function getList(array $params, string $modelClass)
|
||||
{
|
||||
$query = $modelClass::query();
|
||||
|
||||
// 你可以在此处添加分页、排序等功能
|
||||
if (isset($params['page']) && isset($params['per_page'])) {
|
||||
$query->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\CorpFeishuApp;
|
||||
|
||||
class CorpFeishuAppLogic
|
||||
{
|
||||
|
||||
|
||||
public function getFeishuApp($corpId)
|
||||
{
|
||||
return CorpFeishuApp::where('corp_id', $corpId)->first(['id', 'corp_id', 'name', 'app_id', 'r_url', 'state']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\Corp;
|
||||
|
||||
class CorpLogic
|
||||
{
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return Corp::where('status', 1)->orderBy('sort', 'asc')->get(['id', 'name', 'abbr', 'tenant_key']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\User;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目录/菜单树,节点包含权限与打开方式,拼接完整路径,并对子级菜单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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\Role;
|
||||
use support\exception\BusinessException;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\User;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class UserLogic
|
||||
{
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
public function userInfo(int $uid)
|
||||
{
|
||||
$user = User::find($uid);
|
||||
|
||||
if (!$user) {
|
||||
throw new BusinessException('账户不存在', 404);
|
||||
}
|
||||
|
||||
if ($user->status != 1) {
|
||||
throw new BusinessException('账号已禁用', 403);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* corp 企业-信息
|
||||
* @property integer $id ID(主键)
|
||||
* @property string $name 名称
|
||||
* @property string $abbr 缩写
|
||||
* @property string $tenant_key 飞书企业标识
|
||||
* @property integer $sort 排序
|
||||
* @property integer $status 状态
|
||||
* @property integer $created_at 创建时间
|
||||
* @property integer $updated_at 更新时间
|
||||
*/
|
||||
class Corp extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'corp';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* corp_feishu_app 企业-飞书应用
|
||||
* @property integer $id ID(主键)
|
||||
* @property integer $corp_id 公司ID
|
||||
* @property string $name 名称
|
||||
* @property string $tenant_key 企业标识
|
||||
* @property string $app_id 应用ID
|
||||
* @property string $app_secret 应用秘钥
|
||||
* @property string $base_uri 基础域
|
||||
* @property string $r_url 授权地址
|
||||
* @property string $scopes 授权范围
|
||||
* @property string $state 携带参数
|
||||
* @property string $system_code 绑定系统码
|
||||
* @property integer $sort 排序
|
||||
* @property integer $status 状态
|
||||
* @property integer $created_at 创建时间
|
||||
* @property integer $updated_at 更新时间
|
||||
*/
|
||||
class CorpFeishuApp extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'corp_feishu_app';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
}
|
||||
@@ -41,6 +41,8 @@ class Role extends Model
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
// 角色下所有用户
|
||||
public function users()
|
||||
|
||||
@@ -58,7 +58,12 @@ class User extends Model
|
||||
'phone',
|
||||
'email',
|
||||
'status',
|
||||
'system_code'
|
||||
'corp_id',
|
||||
'user_type',
|
||||
'union_id',
|
||||
'user_id',
|
||||
'open_id',
|
||||
'tenant_key'
|
||||
];
|
||||
|
||||
// 用户所有角色
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* user_feishu_app 用户-飞书应用
|
||||
* @property integer $id ID(主键)
|
||||
* @property integer $uid 用户ID
|
||||
* @property integer $corp_id 公司ID
|
||||
* @property string $name 名称
|
||||
* @property string $mobile 手机
|
||||
* @property string $union_id ISV的唯一标识
|
||||
* @property string $user_id 用户 user_id
|
||||
* @property string $open_id 应用内的唯一标识
|
||||
* @property string $tenant_key 当前企业标识
|
||||
* @property integer $sort 排序
|
||||
* @property integer $status 状态
|
||||
* @property integer $created_at 创建时间
|
||||
* @property integer $updated_at 更新时间
|
||||
*/
|
||||
class UserFeishuApp_bak extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'user_feishu_app';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'uid',
|
||||
'corp_id',
|
||||
'name',
|
||||
'mobile',
|
||||
'union_id',
|
||||
'user_id',
|
||||
'open_id',
|
||||
'tenant_key',
|
||||
'sort',
|
||||
'status'
|
||||
];
|
||||
}
|
||||
@@ -4,11 +4,23 @@ namespace app\common\service;
|
||||
|
||||
use app\common\logic\AuthLogic;
|
||||
use app\common\model\User;
|
||||
use app\common\utils\Feishu;
|
||||
use support\Db;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
|
||||
/**
|
||||
* 飞书授权
|
||||
*/
|
||||
public function feishuAuthorize(string $corpAbbr, string $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)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录,支持多类型
|
||||
*/
|
||||
@@ -19,7 +31,7 @@ class AuthService
|
||||
|
||||
return match ($loginType) {
|
||||
'feishu' => $logic->feishuLogin($data),
|
||||
'qrcode' => $logic->qrCodeLogin($data),
|
||||
// 'qrcode' => $logic->qrCodeLogin($data),
|
||||
default => $logic->uPwdLogin($data),
|
||||
};
|
||||
}
|
||||
@@ -74,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)
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\model\Corp;
|
||||
use app\common\model\Systems;
|
||||
use support\Redis;
|
||||
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)
|
||||
{
|
||||
if (!$loginType && !$systemCode && !$corpAbbr) {
|
||||
throw new BusinessException(htmlpage());
|
||||
}
|
||||
|
||||
// 检查并缓存登录类型
|
||||
if (!$this->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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\CorpFeishuAppLogic;
|
||||
|
||||
class CorpFeishuAppService
|
||||
{
|
||||
|
||||
protected $logic;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logic = (new CorpFeishuAppLogic());
|
||||
}
|
||||
|
||||
public function getFeishuApp(int $corpId)
|
||||
{
|
||||
return $this->logic->getFeishuApp($corpId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\CorpLogic;
|
||||
|
||||
class CorpService
|
||||
{
|
||||
|
||||
protected $logic;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logic = (new CorpLogic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公司list
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
return $this->logic->getList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use OSS\OssClient;
|
||||
use OSS\Core\OssException;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class OssService
|
||||
{
|
||||
private $accessKeyId;
|
||||
private $accessKeySecret;
|
||||
private $bucket;
|
||||
private $endpoint;
|
||||
private $ossClient;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\model\User;
|
||||
use support\Cache;
|
||||
|
||||
class PermService_bak
|
||||
{
|
||||
|
||||
// 用户资源缓存key
|
||||
protected static function cacheKey(int $userId): string
|
||||
{
|
||||
return "user_resources_{$userId}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有资源(合并角色+直接分配),结果缓存
|
||||
*/
|
||||
public static 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目录/菜单树,节点包含权限与打开方式
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\RoleLogic;
|
||||
|
||||
class RoleService
|
||||
{
|
||||
protected $roleLogic;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\traits;
|
||||
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait DataTransformer
|
||||
{
|
||||
/**
|
||||
* 核心方法:对任意数据进行转换并返回数组
|
||||
*
|
||||
* @param mixed $rawData 原始数据,可为 Paginator / Model / Collection / array 等
|
||||
* @param callable|null $postProcessor 可选的后置处理回调,接受处理完的数组再返回新的数组
|
||||
* @param callable|null $fieldFormatter 可选的字段命名转换回调,如 snake_case
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function transformData(
|
||||
mixed $rawData,
|
||||
?callable $postProcessor = null,
|
||||
?callable $fieldFormatter = null
|
||||
): array {
|
||||
try {
|
||||
$fieldFormatter = $fieldFormatter ?: fn($field) => $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);
|
||||
}
|
||||
}
|
||||
@@ -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 响应消息
|
||||
|
||||
@@ -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 获取失败');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class RoleValidate extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'id' => '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'],
|
||||
];
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -6,79 +6,17 @@ use support\Request;
|
||||
|
||||
class IndexController
|
||||
{
|
||||
|
||||
protected $noNeedLogin = ['index', 'view'];
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
return '
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Code</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.watermark-container {
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.watermark {
|
||||
position: absolute;
|
||||
color: rgba(0, 0, 0, 0.08);
|
||||
font-size: 20px;
|
||||
transform: rotate(-30deg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="watermark-container" id="watermark-container"></div>
|
||||
|
||||
<div style="padding: 100px;">
|
||||
<p>🌞 hi~</p>
|
||||
<p> 愿你代码如诗,逻辑清晰,Bug 自动远离!</p>
|
||||
<p> 记得适当休息,喝杯好茶或咖啡,为灵感续航 ☕</p>
|
||||
<p> 加油,永远相信美好的事情即将发生。 ✨</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.getElementById("watermark-container");
|
||||
const text = "fdesk 总台系统";
|
||||
const xCount = 8; // 横向水印数量
|
||||
const yCount = 6; // 纵向水印数量
|
||||
const xSpacing = window.innerWidth / xCount;
|
||||
const ySpacing = window.innerHeight / yCount;
|
||||
|
||||
for (let i = 0; i < yCount; i++) {
|
||||
for (let j = 0; j < xCount; j++) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "watermark";
|
||||
div.textContent = text;
|
||||
div.style.left = `${j * xSpacing}px`;
|
||||
div.style.top = `${i * ySpacing}px`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
';
|
||||
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 +24,4 @@ class IndexController
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,76 @@
|
||||
/**
|
||||
* Here is your custom functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 标准化账号参数
|
||||
*
|
||||
* @param string $account 用户输入的账号(可能是手机号或邮箱)
|
||||
* @return array 返回 ['phone' => $account] 或 ['email' => $account]
|
||||
*/
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共html页面
|
||||
*/
|
||||
if (!function_exists('htmlpage')) {
|
||||
function htmlpage()
|
||||
{
|
||||
static $html;
|
||||
if (!$html) {
|
||||
$html = file_get_contents(public_path('mzl.html'));
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取配置
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 调试用
|
||||
*/
|
||||
if (!function_exists('dpr')) {
|
||||
function dpr(\Throwable $e)
|
||||
{
|
||||
var_dump("=== === 报错打印 === ===", "文件:".$e->getFile(), "行数:".$e->getLine(), "详情:".$e->getMessage(), "=== === 报错打印 === ===");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace app\middleware;
|
||||
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class AccessControl implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, callable $handler) : Response
|
||||
{
|
||||
// 如果是options请求则返回一个空响应,否则继续向洋葱芯穿越,并得到一个响应
|
||||
$response = $request->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace app\middleware;
|
||||
|
||||
use app\common\utils\Jwt;
|
||||
use ReflectionClass;
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class AuthCheck implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, callable $handler) : Response
|
||||
{
|
||||
$controller = new ReflectionClass($request->controller);
|
||||
$noNeedLogin = $controller->getDefaultProperties()['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)) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace app\middleware;
|
||||
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
use app\common\service\BasicDataService;
|
||||
use ReflectionClass;
|
||||
|
||||
class BasicData implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, callable $handler): Response
|
||||
{
|
||||
$controller = new ReflectionClass($request->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// 定义所有需要多语言支持的实体
|
||||
'entities' => [
|
||||
'ticket_categorie' => [
|
||||
'translation_model' => \app\ticket\model\CategorieTranslation::class,
|
||||
'foreign_key' => 'categorie_id',
|
||||
],
|
||||
],
|
||||
|
||||
// 默认支持的目标语种列表
|
||||
'locales' => ['zh','en','ru','ar'],
|
||||
];
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace app\queue\translation\jobs;
|
||||
|
||||
use support\Log;
|
||||
use app\queue\translation\service\TranslatorClient;
|
||||
|
||||
class TranslationJob
|
||||
{
|
||||
protected $config = [];
|
||||
protected array $payload;
|
||||
|
||||
public function __construct(array $payload)
|
||||
{
|
||||
$this->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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\translation\queue;
|
||||
|
||||
use support\Log;
|
||||
use Webman\RedisQueue\Consumer;
|
||||
use app\queue\translation\jobs\TranslationJob;
|
||||
|
||||
class Translation implements Consumer
|
||||
{
|
||||
// 要消费的队列名
|
||||
public $queue = 'transTask';
|
||||
|
||||
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
|
||||
public $connection = 'default';
|
||||
|
||||
// 消费
|
||||
public function consume($data)
|
||||
{
|
||||
try {
|
||||
// 无需反序列化
|
||||
// var_export($data); // 输出 ['to' => '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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace app\queue\translation\service;
|
||||
|
||||
// use Webman\RedisQueue\Client;
|
||||
use Webman\RedisQueue\Redis;
|
||||
|
||||
class TranslationService
|
||||
{
|
||||
|
||||
// 配置
|
||||
protected $config = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace app\queue\translation\service;
|
||||
|
||||
use app\queue\utils\XunfeiFanyi;
|
||||
use support\Log;
|
||||
|
||||
class TranslatorClient
|
||||
{
|
||||
|
||||
// protected string $endpoint;
|
||||
// protected string $apiKey;
|
||||
|
||||
// public function __construct()
|
||||
// {
|
||||
// $this->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('翻译服务调用失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\utils;
|
||||
|
||||
class Translat
|
||||
{
|
||||
/**
|
||||
* 从$data中提取出源文本字段,并从$data里删除它们
|
||||
*/
|
||||
public static function extractSourceFields(array &$data,array $translat): array
|
||||
{
|
||||
$source = [];
|
||||
foreach ($translat as $field) {
|
||||
if (array_key_exists($field, $data)) {
|
||||
$source[$field] = $data[$field];
|
||||
unset($data[$field]);
|
||||
}
|
||||
}
|
||||
return $source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\utils;
|
||||
|
||||
|
||||
class XunfeiFanyi
|
||||
{
|
||||
|
||||
protected $error = "";
|
||||
|
||||
public function tocurl($url, $header, $content)
|
||||
{
|
||||
$ch = curl_init();
|
||||
if(substr($url,0,5)=='https'){
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
|
||||
curl_setopt($ch, CURLOPT_SSLVERSION, 1);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
if (is_array($header)) {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
if (!empty($content)) {
|
||||
if (is_array($content)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($content));
|
||||
} else if (is_string($content)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
|
||||
}
|
||||
}
|
||||
$response = curl_exec($ch);
|
||||
$error=curl_error($ch);
|
||||
//var_dump($error);
|
||||
if($error){
|
||||
die($error);
|
||||
}
|
||||
$header = curl_getinfo($ch);
|
||||
curl_close($ch);
|
||||
$data = array('header' => $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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\controller;
|
||||
|
||||
use app\ticket\service\TicketService;
|
||||
use support\Request;
|
||||
|
||||
class AttachmentController extends BaseController
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use support\exception\BusinessException;
|
||||
use app\common\traits\ResponseTrait;
|
||||
use think\Validate;
|
||||
|
||||
abstract class BaseController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
protected mixed $validator = null;
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
*
|
||||
* @param Validate $validator 验证器实例
|
||||
* @param array $data 待验证的数据
|
||||
* @param string $scene 验证场景
|
||||
* @return array 验证后的数据
|
||||
* @throws BusinessException 验证失败时抛出业务异常
|
||||
*/
|
||||
public function validate(Validate $validator, array $data, string $scene = ''): array
|
||||
{
|
||||
// 如果指定了验证场景,并且验证器支持场景方法,则设置验证场景
|
||||
if ($scene && method_exists($validator, 'scene')) {
|
||||
$validator->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\controller;
|
||||
|
||||
use app\ticket\service\CategorieService;
|
||||
use support\Request;
|
||||
|
||||
class CategorieController extends BaseController
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = (new CategorieService())->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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\controller;
|
||||
|
||||
use app\ticket\service\ConversationService;
|
||||
use support\Request;
|
||||
|
||||
class ConversationController extends BaseController
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$ticket_id = $request->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', $request->user->uid);
|
||||
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', $request->user->uid);
|
||||
$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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\controller;
|
||||
|
||||
use app\ticket\service\TicketService;
|
||||
use support\Request;
|
||||
|
||||
class TicketController extends BaseController
|
||||
{
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = (new TicketService())->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 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());
|
||||
}
|
||||
}
|
||||
|
||||
public function add(Request $request)
|
||||
{
|
||||
try {
|
||||
$request->setPost('user_id', $request->user->uid);
|
||||
|
||||
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', $request->user->uid);
|
||||
$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', $request->user->uid);
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\logic;
|
||||
|
||||
use app\common\model\UserTokenLog;
|
||||
use app\common\model\Systems;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use app\common\utils\Jwt;
|
||||
use app\ticket\model\Attachment;
|
||||
use app\ticket\model\Ticket;
|
||||
use support\Redis;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class AttachmentLogic
|
||||
{
|
||||
public function adds(array $data,int $ticket_id,int $user_id)
|
||||
{
|
||||
try {
|
||||
foreach ($data as $k=>$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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\logic;
|
||||
|
||||
use app\ticket\model\Categorie;
|
||||
use support\Log;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class CategorieLogic
|
||||
{
|
||||
public function list($params)
|
||||
{
|
||||
$where = [];
|
||||
$size = $params['size'] ?? 10;
|
||||
$current = $params['current'] ?? 1;
|
||||
return Categorie::with('translation')->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\logic;
|
||||
|
||||
use app\ticket\model\Conversation;
|
||||
use support\Log;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class ConversationLogic
|
||||
{
|
||||
public function list($params)
|
||||
{
|
||||
$where = [];
|
||||
if (!empty($params['ticket_id'])) {
|
||||
$where['ticket_id'] = $params['ticket_id'];
|
||||
}
|
||||
$size = $params['size'] ?? 10;
|
||||
$current = $params['current'] ?? 1;
|
||||
return Conversation::where($where)->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\logic;
|
||||
|
||||
use app\common\model\UserTokenLog;
|
||||
use app\common\model\Systems;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use app\common\utils\Jwt;
|
||||
use app\ticket\model\Ticket;
|
||||
use support\Redis;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class TicketLogic
|
||||
{
|
||||
public function list($params)
|
||||
{
|
||||
$where = [];
|
||||
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)
|
||||
{
|
||||
$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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\model;
|
||||
|
||||
use app\common\model\Resource;
|
||||
use app\common\model\Role;
|
||||
use app\common\model\Systems;
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* users 用户表
|
||||
* @property integer $id 用户ID(主键)
|
||||
* @property string $ticket_id 工单编号
|
||||
* @property string $user_id 分类id
|
||||
* @property string $original_name 内容
|
||||
* @property string $storage_path 排序
|
||||
* @property string $mime_type 状态
|
||||
* @property integer $file_size 创建人id
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
*/
|
||||
class Attachment extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'tkt_ticket_attachment';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
protected $fillable = [
|
||||
'ticket_id',
|
||||
'user_id',
|
||||
'original_name',
|
||||
'storage_path',
|
||||
'mime_type',
|
||||
'file_size'
|
||||
];
|
||||
|
||||
public function ticket(){
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* ticket_categorie 工单分类表
|
||||
* @property integer $id 主键
|
||||
* @property string $name 分类名称
|
||||
* @property string $description 分类描述
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
* @property string $deleted_at 删除时间
|
||||
*/
|
||||
class Categorie extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
protected $connection = 'mysql';
|
||||
protected $table = 'tkt_ticket_categorie';
|
||||
protected $primaryKey = 'id';
|
||||
public $timestamps = true;
|
||||
protected $dateFormat = 'U';
|
||||
protected $dates = ['created_at', 'updated_at'];
|
||||
protected $casts = [
|
||||
'updated_at' => '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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* ticket_categorie_translation 工单分类多语言表
|
||||
* @property integer $id 主键
|
||||
* @property integer $categorie_id 分类ID
|
||||
* @property string $locale 语言
|
||||
* @property string $name 分类名称
|
||||
* @property string $description 分类描述
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
*/
|
||||
class CategorieTranslation extends Model
|
||||
{
|
||||
protected $connection = 'mysql';
|
||||
protected $table = 'tkt_ticket_categorie_translation';
|
||||
protected $primaryKey = 'id';
|
||||
public $timestamps = true;
|
||||
protected $dateFormat = 'U';
|
||||
protected $dates = ['created_at', 'updated_at'];
|
||||
protected $casts = [
|
||||
'updated_at' => 'datetime:Y-m-d H:i:s',
|
||||
'created_at' => 'datetime:Y-m-d H:i:s',
|
||||
];
|
||||
protected $fillable = [
|
||||
'categorie_id',
|
||||
'locale',
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* ticket_conversation 工单会话表
|
||||
* @property integer $id 主键
|
||||
* @property integer $ticket_id 工单ID
|
||||
* @property integer $user_id 用户ID
|
||||
* @property string $content 会话内容
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
*/
|
||||
class Conversation extends Model
|
||||
{
|
||||
protected $connection = 'mysql';
|
||||
protected $table = 'tkt_ticket_conversation';
|
||||
protected $primaryKey = 'id';
|
||||
public $timestamps = true;
|
||||
protected $dateFormat = 'U';
|
||||
protected $dates = ['created_at', 'updated_at'];
|
||||
protected $casts = [
|
||||
'updated_at' => 'datetime:Y-m-d H:i:s',
|
||||
'created_at' => 'datetime:Y-m-d H:i:s',
|
||||
];
|
||||
protected $fillable = [
|
||||
'ticket_id',
|
||||
'user_id',
|
||||
'content',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\model;
|
||||
|
||||
use app\common\model\Resource;
|
||||
use app\common\model\Role;
|
||||
use app\common\model\Systems;
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* users 用户表
|
||||
* @property integer $id 用户ID(主键)
|
||||
* @property string $code 工单编号
|
||||
* @property string $categorie_id 分类id
|
||||
* @property string $content 内容
|
||||
* @property string $sort 排序
|
||||
* @property integer $status 状态
|
||||
* @property string $user_id 创建人id
|
||||
* @property string $created_at
|
||||
* @property string $updated_at
|
||||
*/
|
||||
class Ticket extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'tkt_ticket';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
protected $dates = ['created_at', 'updated_at'];
|
||||
|
||||
protected $casts = [
|
||||
'updated_at' => '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');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
|
||||
class CategorieService
|
||||
{
|
||||
public function list($params)
|
||||
{
|
||||
$result = (new CategorieLogic())->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
|
||||
// );
|
||||
CategorieTranslation::updateOrCreate(
|
||||
[
|
||||
'categorie_id' => $data['id'],
|
||||
'locale' => $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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\ticket\service;
|
||||
|
||||
use app\ticket\logic\ConversationLogic;
|
||||
use config\constants\Constants;
|
||||
use support\Db;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class ConversationService
|
||||
{
|
||||
public function list($params)
|
||||
{
|
||||
$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();
|
||||
$result = (new ConversationLogic())->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>飞书扫码登录示例</title>
|
||||
<!-- 引入二维码 SDK -->
|
||||
<script src="https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 二维码展示容器 -->
|
||||
<div id="login_container" style="width:500px;height:600px;"></div>
|
||||
|
||||
<script>
|
||||
// 1. 配置你的参数
|
||||
// const CLIENT_ID = '你的_APP_ID';
|
||||
// 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://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({
|
||||
id: 'login_container',
|
||||
goto: GOTO,
|
||||
width: '500',
|
||||
height:'500'
|
||||
});
|
||||
|
||||
// 3. 监听扫码并授权后的消息
|
||||
function handleMessage(event) {
|
||||
// 校验来源和数据合法性
|
||||
if (QRLoginObj.matchOrigin(event.origin) && QRLoginObj.matchData(event.data)) {
|
||||
console.log(event.data);
|
||||
|
||||
// return;
|
||||
const tmpCode = event.data.tmp_code;
|
||||
// 带上 tmp_code 重定向到后端回调接口
|
||||
window.location.href = `${GOTO}&tmp_code=${tmpCode}`;
|
||||
}
|
||||
}
|
||||
window.addEventListener
|
||||
? window.addEventListener('message', handleMessage, false)
|
||||
: window.attachEvent('onmessage', handleMessage);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+5
-1
@@ -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": "^2.1",
|
||||
"symfony/translation": "^7.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
|
||||
Generated
+501
-2
@@ -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": "30cc3cbb162022972a21efed9b69d1e1",
|
||||
"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,40 @@
|
||||
},
|
||||
"time": "2025-03-14T03:52:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webman/redis-queue",
|
||||
"version": "v2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webman-php/redis-queue.git",
|
||||
"reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webman-php/redis-queue/zipball/12e1c9915ec64c91c0c9c255606bd0c7de27334c",
|
||||
"reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-redis": "*",
|
||||
"php": ">=8.1",
|
||||
"workerman/redis-queue": "^1.2",
|
||||
"workerman/webman-framework": "^2.1 || dev-master"
|
||||
},
|
||||
"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/v2.1.0"
|
||||
},
|
||||
"time": "2025-01-24T12:56:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "workerman/coroutine",
|
||||
"version": "v1.1.3",
|
||||
@@ -5811,6 +6226,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",
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace config\constants;
|
||||
|
||||
class Constants
|
||||
{
|
||||
//工单状态
|
||||
public const TICKET_STATUS = [
|
||||
'PENDING' => 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'] => 'Закрыть',
|
||||
];
|
||||
|
||||
}
|
||||
@@ -12,4 +12,11 @@
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [];
|
||||
|
||||
return [
|
||||
'' => [
|
||||
\app\middleware\AccessControl::class,
|
||||
\app\middleware\BasicData::class,
|
||||
\app\middleware\AuthCheck::class
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
*-------------------------------------------------------------------------p*
|
||||
* 配置文件
|
||||
*-------------------------------------------------------------------------h*
|
||||
* @copyright Copyright (c) 2015-2022 Shopwwi Inc. (http://www.shopwwi.com)
|
||||
*-------------------------------------------------------------------------c*
|
||||
* @license http://www.shopwwi.com s h o p w w i . c o m
|
||||
*-------------------------------------------------------------------------e*
|
||||
* @link http://www.shopwwi.com by 象讯科技 phcent.com
|
||||
*-------------------------------------------------------------------------n*
|
||||
* @since shopwwi象讯·PHP商城系统Pro
|
||||
*-------------------------------------------------------------------------t*
|
||||
*/
|
||||
|
||||
return [
|
||||
'enable' => 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',
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
'enable' => true,
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Webman\RedisQueue\Command\MakeConsumerCommand;
|
||||
|
||||
return [
|
||||
MakeConsumerCommand::class
|
||||
];
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @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],
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
return [
|
||||
'consumer' => [
|
||||
'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')
|
||||
]
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
return [
|
||||
'default' => [
|
||||
// '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,
|
||||
]
|
||||
],
|
||||
];
|
||||
+56
-3
@@ -28,7 +28,60 @@ 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('/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('/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 () {
|
||||
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']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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']);
|
||||
|
||||
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']);
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'basi_cdata' => [
|
||||
'/', // 首页
|
||||
'/index/index', // 首页
|
||||
'/index/view', // 飞书调试
|
||||
'/common/preset/corps', // 公司列表
|
||||
'/common/preset/feishu/app', // 飞书应用信息
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Code</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.watermark-container {
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.watermark {
|
||||
position: absolute;
|
||||
color: rgba(0, 0, 0, 0.08);
|
||||
font-size: 20px;
|
||||
transform: rotate(-30deg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="watermark-container" id="watermark-container"></div>
|
||||
|
||||
<div style="padding: 100px;">
|
||||
<p>🌞 hi~</p>
|
||||
<p> 愿你代码如诗,逻辑清晰,Bug 自动远离!</p>
|
||||
<p> 记得适当休息,喝杯好茶或咖啡,为灵感续航 ☕</p>
|
||||
<p> 加油,永远相信美好的事情即将发生。 ✨</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.getElementById("watermark-container");
|
||||
const text = "fdesk 总台系统";
|
||||
const xCount = 8; // 横向水印数量
|
||||
const yCount = 6; // 纵向水印数量
|
||||
const xSpacing = window.innerWidth / xCount;
|
||||
const ySpacing = window.innerHeight / yCount;
|
||||
|
||||
for (let i = 0; i < yCount; i++) {
|
||||
for (let j = 0; j < xCount; j++) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "watermark";
|
||||
div.textContent = text;
|
||||
div.style.left = `${j * xSpacing}px`;
|
||||
div.style.top = `${i * ySpacing}px`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'status_not_modified' => 'الدولة لا يمكن تعديلها عكسيا',
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'status_not_modified' => 'The status cannot be reversed for modification',
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'status_not_modified' => 'Статус не может быть изменен в обратном направлении',
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'status_not_modified' => '状态不能逆向修改',
|
||||
];
|
||||
Reference in New Issue
Block a user