diff --git a/app/Amqp/Consumer/BaseConsumer.php b/app/Amqp/Consumer/BaseConsumer.php index 0b397db..486b01e 100644 --- a/app/Amqp/Consumer/BaseConsumer.php +++ b/app/Amqp/Consumer/BaseConsumer.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace App\Amqp\Consumer; +use App\Context\ApiUrlContext; use Hyperf\Amqp\Message\ConsumerMessage; use App\Context\QueueContext; use Hyperf\Amqp\Result; @@ -46,8 +47,8 @@ abstract class BaseConsumer extends ConsumerMessage if (!empty($data['user'])) { QueueContext::setUser($data['user']); } - if (!empty($company = $data['company'])) { - QueueContext::setCompanyInfo($company); + if (!empty($this->data['api_url'])) { + ApiUrlContext::setApiUrl($this->data['api_url']); } Db::beginTransaction(); diff --git a/app/Amqp/Producer/BaseProducer.php b/app/Amqp/Producer/BaseProducer.php index 8592a74..f746e92 100644 --- a/app/Amqp/Producer/BaseProducer.php +++ b/app/Amqp/Producer/BaseProducer.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace App\Amqp\Producer; +use App\Context\ApiUrlContext; use App\Context\UserContext; use Hyperf\Amqp\Message\ProducerMessage; @@ -38,6 +39,10 @@ abstract class BaseProducer extends ProducerMessage $data['user'] = UserContext::getCurrentUser(); } + if (ApiUrlContext::hasApiUrl()) { + $data['api_url'] = ApiUrlContext::getApiUrl(); + } + $this->payload = $data; $this->properties['delivery_mode'] = 2; // 消息持久化 } diff --git a/app/Constants/ErrorCode.php b/app/Constants/ErrorCode.php new file mode 100644 index 0000000..e35be88 --- /dev/null +++ b/app/Constants/ErrorCode.php @@ -0,0 +1,17 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\Context; + +use Hyperf\Context\Context; + +/** + * Author: ykxiao + * Date: 2025/7/11 + * Time: 下午9:00 + * Description: ApiUrlContext. 请求url上下文 + * + * (c) ykxiao + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ +class ApiUrlContext +{ + private const string API_URL_KEY = 'api_url'; + + public static function setApiUrl(string $apiUrl): void + { + Context::set(self::API_URL_KEY, $apiUrl); + } + + public static function getApiUrl(): ?string + { + return Context::get(self::API_URL_KEY); + } + + public static function clearApiUrl(): void + { + Context::set(self::API_URL_KEY, null); + } + + public static function hasApiUrl(): bool + { + return Context::has(self::API_URL_KEY); + } +} \ No newline at end of file diff --git a/app/Context/QueueContext.php b/app/Context/QueueContext.php index 6fba0c7..27d0c4b 100644 --- a/app/Context/QueueContext.php +++ b/app/Context/QueueContext.php @@ -44,25 +44,6 @@ class QueueContext return Context::get(self::USER_KEY); } - /** - * 设置当前公司信息 - * @param array $companyInfo - * @return void - */ - public static function setCompanyInfo(array $companyInfo): void - { - Context::set(self::COMPANY_KEY, $companyInfo); - } - - /** - * 获取当前公司信息 - * @return array|null - */ - public static function getCompanyInfo(): ?array - { - return Context::get(self::COMPANY_KEY); - } - public static function clear(): void { Context::destroy(self::USER_KEY); diff --git a/app/Context/UserContext.php b/app/Context/UserContext.php index f0dcdd9..b915629 100644 --- a/app/Context/UserContext.php +++ b/app/Context/UserContext.php @@ -35,7 +35,7 @@ class UserContext public static function setCurrentUser(array|string|int $user): void { $companyRepository = make(FirstCompanyRepository::class); - if (!empty($companyInfo = $companyRepository->getCompanyByFullName($user['user']['full_name']))) { + if (!empty($companyInfo = $companyRepository->getCompanyByFullName($user['full_name'], false))) { $user['company'] = $companyInfo; } diff --git a/app/Controller/CompanyController.php b/app/Controller/CompanyController.php index 6ea413f..fe0cd19 100644 --- a/app/Controller/CompanyController.php +++ b/app/Controller/CompanyController.php @@ -17,6 +17,7 @@ namespace App\Controller; use App\Repository\Company\CompanyRepository; use App\Request\CompanyRequest; +use Exception; use Hyperf\Di\Annotation\Inject; use Hyperf\HttpMessage\Server\Response; use Hyperf\Validation\Annotation\Scene; @@ -30,6 +31,7 @@ class CompanyController extends AbstractController * 添加公司. * @param CompanyRequest $request * @return Response + * @throws Exception */ #[Scene(scene: 'addCompany', argument: 'request')] public function addCompany(CompanyRequest $request): Response diff --git a/app/Controller/RoleController.php b/app/Controller/RoleController.php index e3f359c..e4aebc1 100644 --- a/app/Controller/RoleController.php +++ b/app/Controller/RoleController.php @@ -15,8 +15,13 @@ declare(strict_types=1); namespace App\Controller; -use App\JsonRpc\UserAuthServiceInterface; +use App\Constants\ActiveStatusConst; +use App\JsonRpc\RoleServiceInterface; +use App\Repository\Company\CompanyModulesRepository; +use App\Repository\Company\FirstCompanyRepository; use App\Request\RoleRequest; +use App\Service\PermissionService; +use Hyperf\Config\Annotation\Value; use Hyperf\Di\Annotation\Inject; use Hyperf\HttpMessage\Server\Response; use Hyperf\Validation\Annotation\Scene; @@ -24,7 +29,19 @@ use Hyperf\Validation\Annotation\Scene; class RoleController extends AbstractController { #[Inject] - protected UserAuthServiceInterface $userAuthServiceInterface; + protected RoleServiceInterface $roleServiceInterface; + + #[Inject] + protected PermissionService $permissionService; + + #[Inject] + protected CompanyModulesRepository $companyModulesRepository; + + #[Inject] + protected FirstCompanyRepository $firstCompanyRepository; + + #[Value('menu.data_permissions')] + protected array $menuDataPermissions; /** * 添加角色. @@ -37,15 +54,172 @@ class RoleController extends AbstractController $params = $request->all(); $data = [ - 'companyInfo' => $this->company(), + 'token' => $this->token(), 'id' => $params['id'] ?? null, 'role_name' => $params['role_name'], 'active_status' => $params['active_status'], 'sort' => $params['sort'], ]; // 添加角色. - $this->userAuthServiceInterface->addRole($data); + $this->roleServiceInterface->addRole($data); return $this->apiResponse->success(); } + + /** + * 角色列表. + * @return Response + */ + public function roleList(): Response + { + $params = $this->request->all(); + $data = [ + 'token' => $this->token(), + 'getPage' => $this->getPage(), + 'params' => $params + ]; + + $roleList = $this->roleServiceInterface->roleList($data); + + return $this->apiResponse->success($roleList); + } + + /** + * 禁用角色. + * @param RoleRequest $request + * @return Response + */ + #[Scene(scene: 'disableRoles', argument: 'request')] + public function disableRoles(RoleRequest $request): Response + { + $params = $request->all(); + $data = [ + 'token' => $this->token(), + 'params' => $params, + ]; + $rpcResult = $this->roleServiceInterface->disableRoles($data); + + $status = $params['status'] ? + ActiveStatusConst::getMessage(ActiveStatusConst::ACTIVE_ENABLE) : + ActiveStatusConst::getMessage(ActiveStatusConst::ACTIVE_DISABLE); + + $res = $rpcResult['result'] ?? []; + $this->opLogs('[' . $status . '角色]名称 ' . $res['role_name'] ?? ''); + return $this->apiResponse->success($rpcResult); + } + + /** + * 删除角色. + * @param RoleRequest $request + * @return Response + */ + #[Scene(scene: 'delRoles', argument: 'request')] + public function delRoles(RoleRequest $request): Response + { + $params = $request->all(); + $data = [ + 'token' => $this->token(), + 'params' => $params, + ]; + $rpcResult = $this->roleServiceInterface->deletedRoles($data); + + $res = $rpcResult['result'] ?? ''; + $this->opLogs('[删除角色]名称 ' . $res); + return $this->apiResponse->success($rpcResult); + } + + /** + * 分配用户角色. + * @param RoleRequest $request + * @return Response + */ + #[Scene(scene: 'assignUserRoles', argument: 'request')] + public function assignUserRoles(RoleRequest $request): Response + { + $params = $request->all(); + $params['menu_data_permission'] = $this->menuDataPermissions; + $data = [ + 'token' => $this->token(), + 'params' => $params, + ]; + + $rpcResult = $this->roleServiceInterface->assignUserRoles($data); + $res = $rpcResult['result'] ?? ''; + + $this->opLogs('[角色管理] 分配用户角色 ' . $res); + return $this->apiResponse->success(); + } + + /** + * 角色授权 + */ + #[Scene(scene: 'assignRolePermissions', argument: 'request')] + public function assignRolePermissions(RoleRequest $request): Response + { + $params = $request->all(); + $data = [ + 'token' => $this->token(), + 'params' => $params + ]; + $rpcResult = $this->roleServiceInterface->assignRolePermissions($data); + $res = $rpcResult['result'] ?? []; + + $this->opLogs('[角色授权]' . $res['role_name'] . ': 权限集 ' . implode(',', $res['actions'])); + return $this->apiResponse->success(); + } + + /** + * 角色权限列表 + * @param RoleRequest $request + * @return Response + */ + #[Scene(scene: 'roleChecked', argument: 'request')] + public function roleChecked(RoleRequest $request): Response + { + $params = [ + 'token' => $this->token(), + 'params' => $request->all() + ]; + $rpcResult = $this->roleServiceInterface->getRolePermissionsByRoleId($params); + + $res = $rpcResult['result'] ?? []; + $menu = $this->permissionService->checkMenu($res); + + return $this->apiResponse->success($menu); + } + + /** + * 公司功能模块授权. + * @param RoleRequest $request + * @return Response + */ + #[Scene(scene: 'addCompanyModule', argument: 'request')] + public function addCompanyModule(RoleRequest $request): Response + { + $params = $request->all(); + + $companyInfo = $this->firstCompanyRepository->getCompanyById($params['company_id']); + + $res = $this->companyModulesRepository->addModule($params); + $menuName = array_column($res['module_data_permissions'] ?? [], 'title'); + + $this->opLogs('[授权公司功能模块]' . $companyInfo['full_name'] . ' : ' . implode(',', $menuName)); + return $this->apiResponse->success(); + } + + /** + * 公司功能模块列表. + * @param RoleRequest $request + * @return Response + */ + #[Scene(scene: 'companyChecked', argument: 'request')] + public function companyChecked(RoleRequest $request): Response + { + $companyId = $request->input('company_id', 0); + $moduleList = $this->companyModulesRepository->codeList($companyId); + + $menu = $this->permissionService->checkMenu($moduleList); + + return $this->apiResponse->success($menu); + } } \ No newline at end of file diff --git a/app/Controller/SystemController.php b/app/Controller/SystemController.php new file mode 100644 index 0000000..9e9089a --- /dev/null +++ b/app/Controller/SystemController.php @@ -0,0 +1,39 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\Controller; + +use App\Service\PermissionService; +use Exception; +use Hyperf\Di\Annotation\Inject; +use Hyperf\HttpMessage\Server\Response; + +class SystemController extends AbstractController +{ + #[Inject] + protected PermissionService $permissionService; + + /** + * 菜单列表 + * @throws Exception + */ + public function menuList(): Response + { + $user = $this->user(); + $menu = $this->permissionService->getMenuByUserType($user); + + return $this->apiResponse->success($menu); + } +} \ No newline at end of file diff --git a/app/Controller/UserController.php b/app/Controller/UserController.php index 62a59ac..06c0bb8 100644 --- a/app/Controller/UserController.php +++ b/app/Controller/UserController.php @@ -60,6 +60,26 @@ class UserController extends AbstractController return $this->apiResponse->success($user); } + /** + * 用户登出. + * @param UserRequest $request + * @return Response + */ + #[Scene(scene: 'userLogout', argument: 'request')] + public function userLogout(UserRequest $request): Response + { + $user = $this->user(); + $token = $this->token(); + + $this->userAuthService->userLogout([ + 'token' => $token, + 'refresh_token' => $request->input('refresh_token') + ]); + + $this->opLogs('[PC端用户退出]' . $user['name']); + return $this->apiResponse->success(); + } + /** * 添加用户. * @param UserRequest $request @@ -88,8 +108,7 @@ class UserController extends AbstractController public function getUserList(UserRequest $request): Response { $data = [ - 'companyInfo' => $this->company(), - 'userInfo' => UserContext::getCurrentUser(), + 'token' => $this->token(), 'getPage' => $this->getPage(), 'params' => $request->all() ]; diff --git a/app/Dao/AbstractDao.php b/app/Dao/AbstractDao.php index 3c23842..b6656ca 100644 --- a/app/Dao/AbstractDao.php +++ b/app/Dao/AbstractDao.php @@ -55,10 +55,28 @@ abstract class AbstractDao private array $queryResult; /** + * 获取模型 * @return Model */ abstract protected function getModel(): string; + /** + * 获取query selector 字段 + * @return array + */ + abstract public function getFields(): array; + + /** + * 构建查询条件. + * @throws Exception + */ + public function buildWhere(Builder $builder, array $params = []): Builder + { + // 根据字段查询数据库,params存在的条件进行查询 + $fields = $this->getFields(); + return self::daoBuildWhere($builder, $params, $fields); + } + /** * 数据操作. */ diff --git a/app/Dao/Company/CompanyDao.php b/app/Dao/Company/CompanyDao.php index 52232eb..6fab661 100644 --- a/app/Dao/Company/CompanyDao.php +++ b/app/Dao/Company/CompanyDao.php @@ -35,4 +35,9 @@ class CompanyDao extends AbstractDao { return Company::class; } + + public function getFields(): array + { + return []; + } } \ No newline at end of file diff --git a/app/Dao/Company/CompanyModulesDao.php b/app/Dao/Company/CompanyModulesDao.php new file mode 100644 index 0000000..9eb8950 --- /dev/null +++ b/app/Dao/Company/CompanyModulesDao.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\Dao\Company; + +use App\Dao\AbstractDao; +use App\Model\CompanyModules; + +class CompanyModulesDao extends AbstractDao +{ + public function getModel(): string + { + return CompanyModules::class; + } + + public function getFields(): array + { + return []; + } +} \ No newline at end of file diff --git a/app/Dao/Purchase/PurchaseDao.php b/app/Dao/Purchase/PurchaseDao.php index b42f52c..83e514c 100644 --- a/app/Dao/Purchase/PurchaseDao.php +++ b/app/Dao/Purchase/PurchaseDao.php @@ -35,4 +35,9 @@ class PurchaseDao extends AbstractDao { return Purchase::class; } + + public function getFields(): array + { + return []; + } } \ No newline at end of file diff --git a/app/Helpers/functions.php b/app/Helpers/functions.php new file mode 100644 index 0000000..09140d2 --- /dev/null +++ b/app/Helpers/functions.php @@ -0,0 +1,125 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +use Hyperf\Collection\Arr; + +/** + * 合并权限CODE为一维数组. + */ +function mergePermissions(array $permissions): array +{ + $code_list = []; + foreach ($permissions as $value) { + foreach ($value as $permission) { + $code_list[] = $permission; + } + } + // 合并指定键下的值到一个二维数组中 + $mergedArray = []; + foreach ($code_list as $item) { + $values = array_values($item); + $mergedArray = array_merge($mergedArray, $values); + } + return $mergedArray; +} + +/** + * 树形数据转换为线性数据(多维数组转为一维数组). + */ +function treeToList(mixed $menu): array +{ + $mid = 1; # 初始主键ID + $pid = 0; # 初始父ID + return getFromChild($menu, $mid, $pid); +} + +/** + * 递归计算自增ID. + */ +function getFromChild(mixed $data, mixed &$mid, mixed $pid, string $childField = 'children'): array +{ + $res = []; + foreach ($data as $v) { + $vCopy = $v; + unset($vCopy[$childField]); + $vCopy['id'] = $mid++; + $vCopy['parent_id'] = $pid; + array_push($res, $vCopy); + if (! empty($v[$childField])) { + $res = Arr::collapse([$res, getFromChild($v[$childField], $mid, $vCopy['id'], $childField)]); + } + } + return $res; +} + +/** + * 系统菜单递归,无子集时不返回空子集. + */ +function listToTree(array $list, string $pk = 'id', string $pid = 'parent_id', string $child = 'children', int $root = 0): array +{ + if (empty($list)) { + return []; + } + $tree = []; + # 创建基于主键的数组引用 + $refer = []; + foreach ($list as $key => $data) { + $refer[$data[$pk]] = &$list[$key]; + } + foreach ($list as $key => $data) { + # 判断是否存在parent + $parentId = $data[$pid]; + if ($root == $parentId) { + $tree[] = &$list[$key]; + } else { + if (isset($refer[$parentId])) { + $parent = &$refer[$parentId]; + $parent[$child][] = &$list[$key]; + } else { + $tree[] = $list[$key]; + } + } + } + return $tree; +} + +/** + * 权限选中数组处理. + */ +function checkPermissions(array &$menu, array $menuPermissions, array $dataPermissions): array +{ + foreach ($menu as $key => $value) { + $menu[$key]['checked'] = false; + if (in_array($value['meta']['code'], $menuPermissions)) { + $menu[$key]['checked'] = true; + } + } + + // 处理数组,为 "data_permission" 添加 "checked" 键值对 + foreach ($menu as &$item) { + if (isset($item['meta']['data_permission'])) { + $dataPermission = $item['meta']['data_permission']; + + foreach ($dataPermission as &$permission) { + $permission['checked'] = false; + if (in_array($permission['actions'], $dataPermissions)) { + $permission['checked'] = true; + } + } + $item['meta']['data_permission'] = $dataPermission; + } + } + return $menu; +} \ No newline at end of file diff --git a/app/Job/BaseJob.php b/app/Job/BaseJob.php index 13f2ae0..80e444b 100644 --- a/app/Job/BaseJob.php +++ b/app/Job/BaseJob.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace App\Job; +use App\Context\ApiUrlContext; use App\Context\QueueContext; use App\Log\Log; use Exception; @@ -45,8 +46,8 @@ abstract class BaseJob extends Job QueueContext::setUser($user); } - if (!empty($company = $this->data['company'])) { - QueueContext::setCompanyInfo($company); + if (!empty($this->data['api_url'])) { + ApiUrlContext::setApiUrl($this->data['api_url']); } // 运行业务逻辑, 总是在事务中执行,保证事务的原子性 diff --git a/app/Job/RequestWriteLogsJob.php b/app/Job/RequestWriteLogsJob.php index 2dbb96b..29007c7 100644 --- a/app/Job/RequestWriteLogsJob.php +++ b/app/Job/RequestWriteLogsJob.php @@ -38,10 +38,9 @@ class RequestWriteLogsJob extends BaseJob $clientIPInfo = $rpcResult['result']['ip_info'] ?? ''; - $logMessage = sprintf('%s %s %s', + $logMessage = sprintf('%s %s', $this->data['client_ip'] . ' ' . $clientIPInfo, - $this->data['method'], - $this->data['uri'] + $this->data['method'] ); if (!empty($params)) { $logMessage .= ' params: ' . json_encode($params, JSON_UNESCAPED_UNICODE); diff --git a/app/JsonRpc/RoleServiceConsumer.php b/app/JsonRpc/RoleServiceConsumer.php new file mode 100644 index 0000000..7cf405d --- /dev/null +++ b/app/JsonRpc/RoleServiceConsumer.php @@ -0,0 +1,125 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\JsonRpc; + +use Hyperf\RpcClient\AbstractServiceClient; + +class RoleServiceConsumer extends AbstractServiceClient implements RoleServiceInterface +{ + protected string $serviceName = 'RoleService'; + + protected string $protocol = 'jsonrpc-http'; + + /** + * 添加角色. + * @param array $data + * @return void + */ + public function addRole(array $data): void + { + $this->__request(__FUNCTION__, $data); + } + + /** + * 获取角色列表. + * @param array $data + * @return array + */ + public function roleList(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } + + /** + * 禁用角色. + * @param array $data + * @return array + */ + public function disableRoles(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } + + /** + * 删除角色. + * @param array $data + * @return array + */ + public function deletedRoles(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } + + /** + * 分配用户角色. + * @param array $data + * @return array + */ + public function assignUserRoles(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } + + /** + * 角色授权. + * @param array $data + * @return array + */ + public function assignRolePermissions(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } + + /** + * 获取用户角色信息. + * @param array $data + * @return array + */ + public function getUserRoleInfo(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } + + /** + * 获取角色权限信息. + * @param array $data + * @return array + */ + public function getRolePermissionsByRoleId(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } + + /** + * 根据角色ID列表获取角色权限集合. + * @param array $data + * @return array + */ + public function getRolePermissionSetByRoleIds(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } + + /** + * 根据用户ID获取用户数据权限. + * @param array $data + * @return array + */ + public function getUserDataPermissions(array $data): array + { + return $this->__request(__FUNCTION__, $data); + } +} \ No newline at end of file diff --git a/app/JsonRpc/RoleServiceInterface.php b/app/JsonRpc/RoleServiceInterface.php new file mode 100644 index 0000000..303ce07 --- /dev/null +++ b/app/JsonRpc/RoleServiceInterface.php @@ -0,0 +1,89 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\JsonRpc; + +interface RoleServiceInterface +{ + /** + * 添加角色. + * @param array $data + * @return void + */ + public function addRole(array $data): void; + + /** + * 获取角色列表. + * @param array $data + * @return array + */ + public function roleList(array $data): array; + + /** + * 禁用角色. + * @param array $data + * @return array + */ + public function disableRoles(array $data): array; + + /** + * 删除角色. + * @param array $data + * @return array + */ + public function deletedRoles(array $data): array; + + /** + * 分配用户角色. + * @param array $data + * @return array + */ + public function assignUserRoles(array $data): array; + + /** + * 角色授权 + * @param array $data + * @return array + */ + public function assignRolePermissions(array $data): array; + + /** + * 获取用户角色信息 + * @param array $data + * @return array + */ + public function getUserRoleInfo(array $data): array; + + /** + * 获取角色权限信息 + * @param array $data + * @return array + */ + public function getRolePermissionsByRoleId(array $data): array; + + /** + * 根据角色ID列表获取角色权限集合 + * @param array $data + * @return array + */ + public function getRolePermissionSetByRoleIds(array $data): array; + + /** + * 获取用户数据权限信息 + * @param array $data + * @return array + */ + public function getUserDataPermissions(array $data): array; +} \ No newline at end of file diff --git a/app/JsonRpc/UserAuthServiceConsumer.php b/app/JsonRpc/UserAuthServiceConsumer.php index 45fbcc6..0379488 100644 --- a/app/JsonRpc/UserAuthServiceConsumer.php +++ b/app/JsonRpc/UserAuthServiceConsumer.php @@ -44,6 +44,16 @@ class UserAuthServiceConsumer extends AbstractServiceClient implements UserAuthS return $this->__request(__FUNCTION__, $data); } + /** + * 用户登出 + * @param array $data + * @return void + */ + public function userLogout(array $data): void + { + $this->__request(__FUNCTION__, $data); + } + /** * 添加用户. * @param array $data @@ -64,16 +74,6 @@ class UserAuthServiceConsumer extends AbstractServiceClient implements UserAuthS return $this->__request(__FUNCTION__, $data); } - /** - * 添加角色. - * @param array $data - * @return void - */ - public function addRole(array $data): void - { - $this->__request(__FUNCTION__, $data); - } - /** * 获取用户列表. * @param array $data diff --git a/app/JsonRpc/UserAuthServiceInterface.php b/app/JsonRpc/UserAuthServiceInterface.php index cf681fe..9b7d99a 100644 --- a/app/JsonRpc/UserAuthServiceInterface.php +++ b/app/JsonRpc/UserAuthServiceInterface.php @@ -24,6 +24,13 @@ interface UserAuthServiceInterface */ public function userLogin(array $data): array; + /** + * 用户登出 + * @param array $data + * @return void + */ + public function userLogout(array $data): void; + /** * 新增用户. */ @@ -36,13 +43,6 @@ interface UserAuthServiceInterface */ public function userInfoByToken(array $data): array; - /** - * 添加角色. - * @param array $data - * @return void - */ - public function addRole(array $data): void; - /** * 获取用户列表. * @param array $data diff --git a/app/Listener/QueueHandleListener.php b/app/Listener/QueueHandleListener.php index 9e250ba..eeb961e 100644 --- a/app/Listener/QueueHandleListener.php +++ b/app/Listener/QueueHandleListener.php @@ -21,7 +21,9 @@ use Hyperf\AsyncQueue\Event\RetryHandle; use Hyperf\Event\Annotation\Listener; use Hyperf\Event\Contract\ListenerInterface; use Hyperf\Logger\LoggerFactory; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; use Psr\Log\LoggerInterface; #[Listener] @@ -29,6 +31,10 @@ class QueueHandleListener implements ListenerInterface { protected LoggerInterface $logger; + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ public function __construct(ContainerInterface $container) { $this->logger = $container->get(LoggerFactory::class)->get('queue'); diff --git a/app/Log/AppendRequestIdProcessor.php b/app/Log/AppendRequestIdProcessor.php index 93100d3..26d3a7a 100644 --- a/app/Log/AppendRequestIdProcessor.php +++ b/app/Log/AppendRequestIdProcessor.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace App\Log; +use App\Context\ApiUrlContext; use Hyperf\Context\Context; use Hyperf\Coroutine\Coroutine; use Monolog\LogRecord; @@ -37,6 +38,12 @@ class AppendRequestIdProcessor implements ProcessorInterface public function __invoke(LogRecord $record): array|LogRecord { + $contextData = ApiUrlContext::getApiUrl(); + + if ($contextData) { + $record['extra']['api_url'] = $contextData; + } + $record['extra']['request_id'] = Context::getOrSet(self::REQUEST_ID, uniqid('xw_cloud')); $record['extra']['coroutine_id'] = Coroutine::id(); return $record; diff --git a/app/Middleware/CheckTokenMiddleware.php b/app/Middleware/CheckTokenMiddleware.php index 2681cf6..3f2fe36 100644 --- a/app/Middleware/CheckTokenMiddleware.php +++ b/app/Middleware/CheckTokenMiddleware.php @@ -18,7 +18,6 @@ namespace App\Middleware; use App\Context\UserContext; use App\Exception\ApiException; use App\JsonRpc\UserAuthServiceInterface; -use App\Repository\Company\FirstCompanyRepository; use Exception; use Hyperf\Di\Annotation\Inject; use Psr\Container\ContainerInterface; @@ -56,11 +55,12 @@ class CheckTokenMiddleware implements MiddlewareInterface $rpcResult = $this->userAuthServiceInterface->userInfoByToken(['token' => $token]); - $userInfo = $rpcResult['result']['user'] ?? []; + $userInfo = $rpcResult['result'] ?? []; if (!$userInfo) { throw new ApiException('用户信息不存在', 401); } + UserContext::setCurrentToken($token); UserContext::setCurrentUser($userInfo); return $handler->handle($request); diff --git a/app/Middleware/PermissionVerifyMiddleware.php b/app/Middleware/PermissionVerifyMiddleware.php new file mode 100644 index 0000000..0c5922a --- /dev/null +++ b/app/Middleware/PermissionVerifyMiddleware.php @@ -0,0 +1,23 @@ +handle($request); + } +} diff --git a/app/Middleware/RequestLogsMiddleware.php b/app/Middleware/RequestLogsMiddleware.php index a79255c..e733c0c 100644 --- a/app/Middleware/RequestLogsMiddleware.php +++ b/app/Middleware/RequestLogsMiddleware.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace App\Middleware; +use App\Context\ApiUrlContext; use App\Service\QueueService; use App\Service\SysService; use Hyperf\Di\Annotation\Inject; @@ -51,6 +52,8 @@ class RequestLogsMiddleware implements MiddlewareInterface { $method = $request->getMethod(); // 请求方法 $uri = $request->getUri()->getPath(); // 请求的URI路径 + ApiUrlContext::setApiUrl($uri); + $params = $request->getMethod() === 'POST' ? $request->getParsedBody() : $request->getQueryParams(); $clientIp = $this->sysService->getClientIpInfo($request); diff --git a/app/Model/CompanyModules.php b/app/Model/CompanyModules.php new file mode 100644 index 0000000..eaedf8c --- /dev/null +++ b/app/Model/CompanyModules.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\Model; + +class CompanyModules extends Model +{ + protected ?string $table = 'company_modules'; + + protected array $fillable = [ + 'company_id', + 'module_code', + 'module_data_permissions', + ]; + + // 查询格式转换 + protected array $casts = [ + 'created_at' => 'datetime:Y-m-d H:i:s', + 'updated_at' => 'datetime:Y-m-d H:i:s', + 'module_code' => 'json', + 'module_data_permissions' => 'json', + ]; +} \ No newline at end of file diff --git a/app/Model/Model.php b/app/Model/Model.php index 0afaf93..c7ae25c 100644 --- a/app/Model/Model.php +++ b/app/Model/Model.php @@ -24,4 +24,10 @@ abstract class Model extends BaseModel implements CacheableInterface protected ?string $dateFormat = 'U'; protected array $hidden = ['deleted_at', 'password']; + + protected array $casts = [ + 'created_at' => 'datetime:Y-m-d H:i:s', + 'updated_at' => 'datetime:Y-m-d H:i:s', + 'active_status' => 'boolean', + ]; } diff --git a/app/Process/WhfConsumerProcess.php b/app/Process/WhfConsumerProcess.php new file mode 100644 index 0000000..859c947 --- /dev/null +++ b/app/Process/WhfConsumerProcess.php @@ -0,0 +1,14 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\Repository\Company; + +use App\Dao\Company\CompanyModulesDao; +use App\Repository\AbstractRepository; + +class CompanyModulesRepository extends AbstractRepository +{ + public function __construct(CompanyModulesDao $dao) + { + $this->dao = $dao; + } + + /** + * 公司功能模块列表. + */ + public function codeList(int $company_id): array + { + $query = $this->dao->builder()->where('company_id', '=', $company_id); + $role_permission_code = []; + $data_permission_code = []; + if (!$query->exists()) { + return compact('role_permission_code', 'data_permission_code'); + } + $info = $query->first(['module_code', 'module_data_permissions'])->toArray(); + $role_permission_code = $info['module_code']; + $data_permission_code = $info['module_data_permissions']; + return compact('role_permission_code', 'data_permission_code'); + } + + /** + * 公司功能模块分配. + * @param array $data + * @return array + */ + public function addModule(array $data): array + { + $query = $this->dao->builder(); + + $params = []; + $params['company_id'] = $data['company_id']; + $params['module_code'] = $data['menu_permission']; + $params['module_data_permissions'] = mergePermissions($data['data_permission'] ?? []); + $query->updateOrCreate([ + 'company_id' => $params['company_id'], + ], $params); + + return $params; + } +} \ No newline at end of file diff --git a/app/Repository/Company/FirstCompanyRepository.php b/app/Repository/Company/FirstCompanyRepository.php index 61dccb7..35db4d0 100644 --- a/app/Repository/Company/FirstCompanyRepository.php +++ b/app/Repository/Company/FirstCompanyRepository.php @@ -16,6 +16,7 @@ declare(strict_types=1); namespace App\Repository\Company; use App\Dao\Company\FirstCompanyDao; +use App\Exception\ApiException; use App\Repository\AbstractRepository; use Carbon\Carbon; use Exception; @@ -99,13 +100,31 @@ class FirstCompanyRepository extends AbstractRepository /** * 根据公司全称获取公司信息. * @param string $fullName + * @param bool $isVerify * @return array|null */ - public function getCompanyByFullName(string $fullName): ?array + public function getCompanyByFullName(string $fullName, bool $isVerify = true): ?array { - return $this->dao->builder() - ->select($this->dao->getFields()) - ->where('full_name', '=', $fullName) - ->first()?->toArray(); + $fields = $this->dao->getFields(); + $query = $this->dao->builder()->select($fields)->where('full_name', '=', $fullName); + $company = $query->first(); + if (!$company && $isVerify) { + throw new ApiException('公司不存在'); + } + return $company?->toArray(); + } + + /** + * 根据公司ID查询公司信息. + */ + public function getCompanyById(int $companyId, bool $isVerify = true): array + { + $fields = $this->dao->getFields(); + $query = $this->dao->builder()->select($fields)->where('id', $companyId); + $company = $query->first()?->toArray(); + if (!$company && $isVerify) { + throw new ApiException('公司不存在'); + } + return $company ?? []; } } \ No newline at end of file diff --git a/app/Request/RoleRequest.php b/app/Request/RoleRequest.php index 96b5697..723f795 100644 --- a/app/Request/RoleRequest.php +++ b/app/Request/RoleRequest.php @@ -24,6 +24,26 @@ class RoleRequest extends AbstractRequest 'active_status', 'sort', ], + 'disableRoles' => ['id' => 'required|integer|min:1', 'status'], + 'delRoles' => ['ids'], + 'assignUserRoles' => ['user_id', 'role_ids', 'role_ids.*'], + 'assignRolePermissions' => [ + 'role_id', + 'menu_permission', + 'menu_permission.*', + 'data_permission', + 'data_permission.*', + 'data_permission.*.*', + ], + 'roleChecked' => ['role_id'], + 'addCompanyModule' => [ + 'company_id', + 'menu_permission', + 'menu_permission.*', + 'data_permission', + 'data_permission.*', + ], + 'companyChecked' => ['company_id'] ]; public function rules(): array @@ -33,6 +53,19 @@ class RoleRequest extends AbstractRequest 'role_name' => 'required|string', 'active_status' => 'required|integer', 'sort' => 'required|integer', + 'status' => 'required|boolean', + 'ids' => 'required|array|min:1', + 'ids.*' => 'required|integer|min:1|distinct', + 'user_id' => 'required|integer|min:1', + 'role_ids' => 'required|array|min:1', + 'role_ids.*' => 'required|integer|min:1|distinct', + 'role_id' => 'required|integer|min:1', + 'menu_permission' => 'required|array|min:1', + 'menu_permission.*' => 'required|string|max:128|distinct', + 'data_permission' => 'array|min:1', + 'data_permission.*' => 'required|array|min:1', + 'data_permission.*.*' => 'required|array|min:1|distinct', + 'company_id' => 'required|integer|integer|min:1', ]; } @@ -43,6 +76,18 @@ class RoleRequest extends AbstractRequest 'role_name' => '角色名称', 'active_status' => '角色状态', 'sort' => '排序', + 'status' => '状态', + 'ids' => '角色列表', + 'ids.*' => '角色列表ID', + 'role_ids' => '角色列表', + 'role_ids.*' => '角色列表ID', + 'user_id' => '用户ID', + 'menu_permission' => '菜单权限集', + 'menu_permission.*' => '菜单权限', + 'data_permission' => '数据权限', + 'data_permission.*' => '数据菜单权限', + 'data_permission.*.*' => '数据权限集', + 'company_id' => '公司ID', ]; } } \ No newline at end of file diff --git a/app/Request/UserRequest.php b/app/Request/UserRequest.php index 12018a6..144efe0 100644 --- a/app/Request/UserRequest.php +++ b/app/Request/UserRequest.php @@ -37,7 +37,8 @@ class UserRequest extends AbstractRequest 'getUserList' => [ 'page', // 页码 'pageSize', // 页大小 - ] + ], + 'userLogout' => ['refresh_token'] ]; public function rules(): array @@ -58,6 +59,7 @@ class UserRequest extends AbstractRequest 'role_ids.*' => 'integer|min:1|distinct', 'page' => 'integer|min:1', 'pageSize' => 'integer|min:1', + 'refresh_token' => 'required|string|max:1024' ]; } @@ -79,6 +81,7 @@ class UserRequest extends AbstractRequest 'role_ids.*' => '角色ID', 'page' => '页码', 'pageSize' => '页大小', + 'refresh_token' => '刷新令牌', ]; } } \ No newline at end of file diff --git a/app/Service/PermissionService.php b/app/Service/PermissionService.php new file mode 100644 index 0000000..fcf219d --- /dev/null +++ b/app/Service/PermissionService.php @@ -0,0 +1,288 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\Service; + +use App\Constants\RoleTypeConst; +use App\Context\UserContext; +use App\Exception\ApiException; +use App\Repository\Company\CompanyModulesRepository; +use Exception; +use Hyperf\Config\Annotation\Value; +use Hyperf\Di\Annotation\Inject; +use Psr\Http\Message\ServerRequestInterface; +use function Hyperf\Collection\collect; + +class PermissionService +{ + #[Value('menu.data_permissions')] + protected array $dataPermissions; + + // 跳过验证的菜单 + #[Value('menu.ignore_menu')] + protected array $ignoreMenu; + + #[Inject] + protected CompanyModulesRepository $companyModulesRepository; + + #[Inject] + protected SysService $sysService; + + #[Inject] + protected RelatedInfo $relatedInfo; + + /** + * 验证用户是否有权限执行当前操作. + * + * @param ServerRequestInterface $request 表示当前的服务器请求 + * @return bool 如果用户有权限,则返回true;否则返回false + * @throws Exception 如果用户没有登录,则抛出一个异常 + */ + public function verifyUserPermission(ServerRequestInterface $request): bool + { + $user = UserContext::getCurrentUser(); + + return match ($user['role_type']) { + RoleTypeConst::SUPER_ADMIN => true, + RoleTypeConst::ADMIN => $this->sysService->verifyPlatformAdmin($user, $request), + default => $this->checkUserRequestPermission($user, $request), + }; + } + + /** + * @param array $user + * @param ServerRequestInterface $request + * @return bool + * @throws Exception + */ + protected function checkUserRequestPermission(array $user, ServerRequestInterface $request): bool + { + $method = $this->sysService->method($request); + if (!$method) { + return false; + } + + $dataPermissions = $this->sysService->dataPermissions($user); + $actions = array_column($dataPermissions, 'actions'); + $roleActions = $this->sysService->roleAction($user); + + return in_array($method, $roleActions, true) || in_array($method, $actions, true); + } + + /** + * 返回权限选中列表。 + * 根据提供的权限码和菜单数据,检查并返回用户有权限访问的菜单树。 + * + * @param array $permissionCode 包含角色权限码和数据权限码的数组。 + * 'role_permission_code' 键包含角色的权限码, + * 'data_permission_code' 键包含数据权限的详情(如果有的话)。 + * @return array 返回经过权限检查后的菜单树 + */ + public function checkMenu(array $permissionCode): array + { + $user = UserContext::getCurrentUser(); + + $menu = match ($user['role_type']) { + RoleTypeConst::SUPER_ADMIN => $this->adminMenu()['menu'], + RoleTypeConst::ADMIN => $this->platformAdminMenu()['menu'], + default => $this->userMenu($user)['menu'], + }; + + $menuList = treeToList($menu); + + $menuPermissions = $permissionCode['role_permission_code'] ?? []; + $dataPermissions = collect($permissionCode['data_permission_code'] ?? []) + ->pluck('actions') + ->toArray(); + + checkPermissions($menuList, $menuPermissions, $dataPermissions); + + return listToTree($menuList); + } + + /** + * 获取指定用户的权限列表。 + * + * @param mixed $user 用户信息,预期包含is_admin和is_platform字段来区分用户类型 + * @return array 返回一个包含菜单权限 menu_permissions 和数据权限 data_permissions 的数组 + */ + public function myPermissions(mixed $user): array + { + return match ($user['role_type']) { + RoleTypeConst::SUPER_ADMIN => $this->formatPermissions($this->adminMenu()['permissions']), + RoleTypeConst::ADMIN => $this->formatPermissions($this->platformAdminMenu()['permissions']), + default => [ + 'menu_permissions' => $this->sysService->menuPermissions($user), + 'data_permissions' => $this->sysService->dataPermissions($user), + ], + }; + } + + protected function formatPermissions(array $permissions): array + { + return [ + 'menu_permissions' => $permissions['menu_permissions'], + 'data_permissions' => mergePermissions($permissions['data_permissions']), + ]; + } + + /** + * 获取超级管理员的菜单和权限配置。 + * 该函数不接受任何参数。 + * + * @return array 返回一个包含菜单结构和权限信息的数组。 + * 其中' menu'键包含经过树化处理的菜单结构, + * 'permissions'键包含两个子项:'menu_permissions'和'data_permissions'。 + * 'menu_permissions'表示菜单权限代码数组, + * 'data_permissions'表示数据权限数组。 + */ + public function adminMenu(): array + { + $menu = (new SysService())->getDefaultMenu(); + $menu_permissions = collect($menu)->pluck('meta.code')->toArray(); + $data_permissions = array_values($this->dataPermissions); + + return [ + 'menu' => listToTree($menu), + 'permissions' => compact('menu_permissions', 'data_permissions'), + ]; + } + + /** + * 生成公司管理员的功能菜单模块. + * + * 根据提供的模块代码数组,筛选出对应菜单项,并将其转换为树状结构,同时返回菜单项的权限信息。 + * + * @return array 返回一个包含菜单树结构和权限信息的数组 + */ + public function platformAdminMenu(): array + { + $user = UserContext::getCurrentUser(); + if (empty($user)) { + throw new ApiException('当前用户信息获取失败'); + } + + $companyModule = $this->companyModulesRepository->codeList($user['company']['id']); + $moduleCode = $companyModule['role_permission_code']; + $company_data_permissions = array_column($companyModule['data_permission_code'] ?? [], 'actions'); + + // 获取系统默认菜单 + $menu = (new SysService())->getDefaultMenu(); + $filteredMenu = $this->checkSysMenu($menu, $moduleCode); + + // 筛选出当前模块代码对应的菜单项,并提取出菜单代码 + $menu_permissions = collect($filteredMenu)->pluck('meta.code')->toArray(); + + // 获取数据权限 + $data_permission = $this->dataPermissions; + + // 将数据权限整理为键值对形式的数组 + $data_permissions = []; + foreach ($data_permission as $value) { + $value = collect($value)->whereIn('actions', $company_data_permissions)->values()->toArray(); + if (empty($value)) { + continue; + } + $data_permissions[] = $value; + } + + // 返回处理后的菜单树和权限信息 + return [ + 'menu' => listToTree($filteredMenu), // 转换菜单项为树状结构 + 'permissions' => compact('menu_permissions', 'data_permissions'), // 权限信息 + ]; + } + + /** + * 根据用户角色获取普通用户菜单权限。 + * + * @param mixed $user 用户信息,预期为包含角色权限信息的对象 + * @return array 返回包含菜单结构和权限信息的数组。 + * 其中: + * - 'menu' 键包含经过树化处理的菜单结构。 + * - 'permissions' 键包含两个子项: + * - 'menu_permissions' 用户的角色菜单权限代码集合。 + * - 'data_permissions' 用户的数据权限信息。 + */ + public function userMenu(mixed $user): array + { + $rolePermissions = $this->relatedInfo->getRolePermissions($user['id']); + $data_permissions = collect($this->relatedInfo->dataPermissions($user['id']))->pluck('data_permissions')->toArray(); + + $menu_permissions = array_unique(array_merge( + collect($rolePermissions)->pluck('role_permission_code')->collapse()->unique()->values()->toArray(), + $this->ignoreMenu + )); + + $menu = (new SysService())->getDefaultMenu(); + $filteredMenu = $this->checkSysMenu($menu, $menu_permissions); + + return [ + 'menu' => listToTree($filteredMenu), + 'permissions' => compact('menu_permissions', 'data_permissions'), + ]; + } + + /** + * 合并处理权限CODE。 + * 该函数接收一个包含菜单信息的参数,并从中提取权限码,然后合并这些权限码。 + * + * @param mixed $menu 包含菜单权限和数据权限信息的数组或对象 + * @return array 返回一个包含角色权限码和数据权限码的关联数组 + */ + public function mergeCode(mixed $menu): array + { + // 提取菜单权限码 + $role_permission_code = $menu['menu_permissions'] ?? []; + // 提取数据权限码 + $data_permissions = $menu['data_permissions'] ?? []; + $data_permission_code = []; + foreach ($data_permissions as $value) { + $data_permission_code[] = $value; // 将数据权限码添加到数组中 + } + // 合并数据权限码 + $data_permission_code = mergePermissions($data_permission_code); + // 返回合并后的权限码 + return compact('role_permission_code', 'data_permission_code'); + } + + /** + * 获取用户的菜单权限。 + * 该函数接收一个用户信息数组作为参数,并返回该用户的菜单权限。 + * + * @param array $user 用户信息数组 + * @return array 返回用户的菜单权限。 + * @throws Exception + */ + public function getMenuByUserType(array $user): array + { + return match (true) { + $user['role_type'] === RoleTypeConst::SUPER_ADMIN => $this->adminMenu(), + $user['role_type'] === RoleTypeConst::ADMIN => $this->platformAdminMenu(), + default => $this->userMenu($user), + }; + } + + /** + * 筛选出当前模块代码对应的菜单项,转换为数组。 + */ + public function checkSysMenu(array $menu, array $module_code): array + { + // 非平台用户的菜单筛选 + return collect($menu) + ->whereIn('meta.code', $module_code) + ->toArray(); + } +} \ No newline at end of file diff --git a/app/Service/QueueService.php b/app/Service/QueueService.php index 2af2249..d16c8ac 100644 --- a/app/Service/QueueService.php +++ b/app/Service/QueueService.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace App\Service; +use App\Context\ApiUrlContext; use App\Context\UserContext; use App\Job\ColumnConfigJob; use App\Job\OpLogsJob; @@ -60,6 +61,10 @@ class QueueService // 将当前用户信息添加到参数中 $params['user'] = UserContext::getCurrentUser(); + if (ApiUrlContext::hasApiUrl()) { + $params['api_url'] = ApiUrlContext::getApiUrl(); + } + $this->params = $params; return $this; } diff --git a/app/Service/RelatedInfo.php b/app/Service/RelatedInfo.php new file mode 100644 index 0000000..59c7675 --- /dev/null +++ b/app/Service/RelatedInfo.php @@ -0,0 +1,104 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +declare(strict_types=1); + +namespace App\Service; + +use App\JsonRpc\RoleServiceInterface; +use App\Repository\Company\FirstCompanyRepository; +use Hyperf\Collection\Collection; +use Hyperf\Di\Annotation\Inject; +use function Hyperf\Collection\collect; + +class RelatedInfo +{ + #[Inject] + protected RoleServiceInterface $roleServiceInterface; + + #[Inject] + protected FirstCompanyRepository $firstCompanyRepository; + + /** + * 用户角色关联. + * @param int $uid 用户ID + * @return array 用户角色关联信息 + */ + public function userRoles(int $uid): array + { + $data = [ + 'user_id' => $uid, + ]; + + $rpcResult = $this->roleServiceInterface->getUserRoleInfo($data); + return $rpcResult['result'] ?? []; + } + + /** + * 公司信息关联. + * @param string $companyName 公司名称 + * @return array|null 公司信息关联信息 + */ + public function companyInfo(string $companyName): ?array + { + return $this->firstCompanyRepository->getCompanyByFullName($companyName, false); + } + + /** + * 公司信息关联. + * @param int $companyId + * @return array + */ + public function companyInfoById(int $companyId): array + { + return $this->firstCompanyRepository->getCompanyById($companyId, false); + } + + /** + * 获取用户角色权限. + * @param int $uid 用户ID + * @return Collection 角色权限集合 + */ + public function getRolePermissions(int $uid): Collection + { + // 获取用户角色表中的 role_ids 数组 + $roleIds = static::userRoles($uid)['role_ids_info'] ?? []; + $userRolesIds = array_keys($roleIds); + + $data = [ + 'role_ids' => $userRolesIds + ]; + // 通过数组中的 role_id 查找对应的角色权限 + + $rpcResult = $this->roleServiceInterface->getRolePermissionSetByRoleIds($data); + $res = $rpcResult['result'] ?? []; + + return collect($res); + } + + /** + * 用户数据权限关联. + * @param int $uid 用户ID + * @return array 数据权限信息 + * @return array + */ + public function dataPermissions(int $uid): array + { + $data = [ + 'user_id' => $uid, + ]; + + $rpcResult = $this->roleServiceInterface->getUserDataPermissions($data); + return $rpcResult['result'] ?? []; + } +} \ No newline at end of file diff --git a/app/Service/SysService.php b/app/Service/SysService.php index 085260d..d851e17 100644 --- a/app/Service/SysService.php +++ b/app/Service/SysService.php @@ -15,7 +15,12 @@ declare(strict_types=1); namespace App\Service; +use App\Repository\Company\CompanyModulesRepository; +use Hyperf\Config\Annotation\Value; +use Hyperf\Di\Annotation\Inject; +use Hyperf\HttpServer\Router\Dispatched; use Psr\Http\Message\ServerRequestInterface; +use function Hyperf\Collection\collect; /** * Author: ykxiao @@ -30,6 +35,104 @@ use Psr\Http\Message\ServerRequestInterface; */ class SysService { + #[Inject] + protected RelatedInfo $relatedInfo; + + #[Inject] + protected CompanyModulesRepository $companyModulesRepository; + + #[Value('menu.menu')] + protected array $menu; + + #[Value('menu.data_permissions')] + protected array $dataPermissions; + + public function getDefaultMenu(): array + { + $list = treeToList($this->menu); + foreach ($list as $item => $value) { + $code = $value['meta']['code']; + $value['meta']['data_permission'] = []; + if (array_key_exists($code, $this->dataPermissions)) { + $value['meta']['data_permission'] = $this->dataPermissions[$code]; + } + $list[$item] = $value; + } + return $list; + } + + /** + * 获取当前方法名 + * @param ServerRequestInterface $request + * @return string + */ + public function method(ServerRequestInterface $request): string + { + $dispatched = $request->getAttribute(Dispatched::class); + + $method = null; + if ($dispatched instanceof Dispatched) { + $handler = $dispatched->handler->callback ?? null; + if ($handler) { + $method = $handler[1]; + } + } + return $method; + } + + /** + * 获取当前用户的角色权限。 + * @param array $user + * @return array + */ + public function roleAction(array $user): array + { + // 获取用户角色拥有的菜单权限 + $default_menu = (new SysService())->getDefaultMenu(); + return collect($default_menu) + ->whereIn('meta.code', $this->menuPermissions($user)) + ->pluck('actions') + ->collapse() + ->toArray(); + } + + /** + * 获取用户的菜单权限。 + * + * @param mixed $user 用户信息,预期包含用户ID + * @return array 返回一个包含用户所有菜单权限的数组 + */ + public function menuPermissions(mixed $user): array + { + // 获取用户角色的所有权限 + $role_permissions = $this->relatedInfo->getRolePermissions($user['id']); + + // 收集角色权限码,去除重复,转换为数组格式后返回 + return $role_permissions + ->pluck('role_permission_code') + ->collapse() + ->unique() + ->toArray(); + } + + /** + * 菜单权限。 + * 该函数用于获取指定用户的菜单权限。 + * + * @param mixed $user 用户信息,预期为包含用户ID的数组或对象 + * @return array 返回一个包含用户菜单权限动作的数组 + */ + public function dataPermissions(mixed $user): array + { + $dataPermissions = $this->relatedInfo->dataPermissions($user['id']); + + // 获取并转换用户的所有数据权限 + return collect($dataPermissions) + ->pluck('data_permissions') + ->collapse() + ->toArray(); + } + /** * 获取客户端IP信息 * @param ServerRequestInterface $request @@ -45,4 +148,31 @@ class SysService return $clientIP ?: '127.0.0.1'; } + + /** + * 验证平台管理员权限。 + */ + public function verifyPlatformAdmin(mixed $user, mixed $request): bool + { + $companyInfo = $user['company_info']; + $module = $this->companyModulesRepository->codeList($companyInfo['id']); + + // 菜单权限 + $default_menu = (new SysService())->getDefaultMenu(); + $role_permissions_result = collect($default_menu) + ->whereIn('meta.code', $module['role_permission_code']) + ->flatMap(fn($item) => $item['actions']) + ->toArray(); + + $data_permissions_result = collect($module['data_permission_code']) + ->pluck('actions') + ->toArray(); + + $method = $this->method($request); + if ($method) { + return in_array($method, $role_permissions_result, true) || in_array($method, $data_permissions_result, true); + } + + return false; + } } \ No newline at end of file diff --git a/composer.json b/composer.json index e21d799..f13d4d4 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,9 @@ "psr-4": { "App\\": "app/" }, - "files": [] + "files": [ + "app/Helpers/functions.php" + ] }, "autoload-dev": { "psr-4": { diff --git a/config/autoload/async_queue.php b/config/autoload/async_queue.php index d8613e5..0ea81cd 100644 --- a/config/autoload/async_queue.php +++ b/config/autoload/async_queue.php @@ -18,7 +18,7 @@ return [ 'redis' => [ 'pool' => 'default', ], - 'channel' => '{queue}', + 'channel' => '{default}', 'timeout' => 2, 'retry_seconds' => 5, 'handle_timeout' => 10, @@ -33,7 +33,7 @@ return [ 'redis' => [ 'pool' => 'default', ], - 'channel' => '{drp.custom}', + 'channel' => '{queue:whf}', 'timeout' => 2, 'retry_seconds' => 5, 'handle_timeout' => 10, diff --git a/config/autoload/dependencies.php b/config/autoload/dependencies.php index 06fc625..de29b49 100644 --- a/config/autoload/dependencies.php +++ b/config/autoload/dependencies.php @@ -14,6 +14,8 @@ use App\JsonRpc\EasyAppServiceConsumer; use App\JsonRpc\EasyAppServiceInterface; use App\JsonRpc\InventoryServiceConsumer; use App\JsonRpc\InventoryServiceInterface; +use App\JsonRpc\RoleServiceConsumer; +use App\JsonRpc\RoleServiceInterface; use App\JsonRpc\UserAuthServiceConsumer; use App\JsonRpc\UserAuthServiceInterface; use App\Log\StdoutLoggerFactory; @@ -24,4 +26,5 @@ return [ UserAuthServiceInterface::class => UserAuthServiceConsumer::class, EasyAppServiceInterface::class => EasyAppServiceConsumer::class, InventoryServiceInterface::class => InventoryServiceConsumer::class, + RoleServiceInterface::class => RoleServiceConsumer::class, ]; diff --git a/config/autoload/op_logs.php b/config/autoload/op_logs.php index 8762678..763c482 100644 --- a/config/autoload/op_logs.php +++ b/config/autoload/op_logs.php @@ -18,5 +18,30 @@ return [ 'id' => 1, 'action' => 'UserController@userLogin', 'name' => '用户登录', - ] + ], + [ + 'id' => 2, + 'action' => 'UserController@userLogout', + 'name' => '用户登出', + ], + [ + 'id' => 3, + 'action' => 'RoleController@disableRoles', + 'name' => '禁用|启用角色', + ], + [ + 'id' => 4, + 'action' => 'RoleController@delRoles', + 'name' => '删除角色', + ], + [ + 'id' => 5, + 'action' => 'RoleController@addCompanyModule', + 'name' => '公司授权', + ], + [ + 'id' => 6, + 'action' => 'RoleController@assignRolePermissions', + 'name' => '角色授权', + ], ]; \ No newline at end of file diff --git a/config/autoload/services.php b/config/autoload/services.php index 44c8f12..06244bc 100644 --- a/config/autoload/services.php +++ b/config/autoload/services.php @@ -13,6 +13,7 @@ declare(strict_types=1); use App\JsonRpc\EasyAppServiceInterface; use App\JsonRpc\InventoryServiceInterface; +use App\JsonRpc\RoleServiceInterface; use App\JsonRpc\UserAuthServiceInterface; use function Hyperf\Support\value; @@ -23,6 +24,7 @@ return [ 'UserAuthService' => UserAuthServiceInterface::class, 'EasyAppService' => EasyAppServiceInterface::class, 'InventoryService' => InventoryServiceInterface::class, + 'RoleService' => RoleServiceInterface::class, ]; foreach ($services as $name => $interface) { $consumers[] = [ diff --git a/config/routes.php b/config/routes.php index 7083725..0a5038a 100644 --- a/config/routes.php +++ b/config/routes.php @@ -15,8 +15,10 @@ use App\Controller\CompanyController; use App\Controller\FirstCompanyController; use App\Controller\PurchaseController; use App\Controller\RoleController; +use App\Controller\SystemController; use App\Controller\UserController; use App\Middleware\CheckTokenMiddleware; +use App\Middleware\PermissionVerifyMiddleware; use Hyperf\HttpServer\Router\Router; Router::get('/favicon.ico', function () { @@ -29,6 +31,14 @@ Router::addGroup('/api/v1/', function () { }); Router::addGroup('/api/v1', function () { + // 需携带 Token 接口 + Router::addGroup('/', function () { + Router::post('user.logout', [UserController::class, 'userLogout']); # 用户登出 + + Router::get('menu.list', [SystemController::class, 'menuList']); # 系统菜单列表 + }); + + // 需携带 Token 接口且需验证权限 Router::addGroup('/', function () { /** * 用户管理 @@ -40,7 +50,17 @@ Router::addGroup('/api/v1', function () { /** * 角色管理 */ - Router::post('role.add', [RoleController::class, 'addRole']); # 获取用户信息 + Router::post('role.add', [RoleController::class, 'addRole']); # 新增角色 + Router::post('role.list', [RoleController::class, 'roleList']); # 获取角色列表 + Router::post('roles.status', [RoleController::class, 'disableRoles']); # 禁用&启用角色 + Router::post('roles.delete', [RoleController::class, 'delRoles']); # 删除角色 + Router::post('assign.user.role', [RoleController::class, 'assignUserRoles']); # 分配用户角色 + Router::post('assign.role.auth', [RoleController::class, 'assignRolePermissions']); # 角色授权 + Router::post('role.permission.checked', [RoleController::class, 'roleChecked']); # 角色权限选择列表 + + // 平台公司管理 + Router::post('company.module.add', [RoleController::class, 'addCompanyModule']); # 公司模块授权 + Router::post('company.permission.checked', [RoleController::class, 'companyChecked']); # 公司模块选择列表 /** * 采购入库管理 @@ -48,9 +68,13 @@ Router::addGroup('/api/v1', function () { Router::post('purchase.add', [PurchaseController::class, 'addPurchase']); # 新增采购入库单 /** - * 公司管理 + * 平台管理 + */ + Router::post('company.add.first', [FirstCompanyController::class, 'addFirstCompany']); # 新增平台公司 + + /** + * 供应商&客户管理 */ Router::post('company.add', [CompanyController::class, 'addCompany']); # 新增公司 - Router::post('company.add.first', [FirstCompanyController::class, 'addFirstCompany']); # 新增平台公司 - }); + }, ['middleware' => [PermissionVerifyMiddleware::class]]); }, ['middleware' => [CheckTokenMiddleware::class]]); \ No newline at end of file diff --git a/migrations/2025_07_10_154954_create_company_modules_table.php b/migrations/2025_07_10_154954_create_company_modules_table.php new file mode 100755 index 0000000..616d611 --- /dev/null +++ b/migrations/2025_07_10_154954_create_company_modules_table.php @@ -0,0 +1,34 @@ +unsignedInteger('id', true); + $table->integer('company_id')->default(0)->unsigned()->comment('公司ID'); + $table->json('module_code')->comment('公司功能模块CODE列表'); + $table->json('module_data_permissions')->comment('数据权限'); + MigrateService::migrateCreateInfo($table); + $table->comment('公司功能模块管理表'); + + $table->index(['company_id'], 'idx_company_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('company_modules'); + } +}