This commit is contained in:
Your Name
2026-09-01 10:42:16 +08:00
parent 456dd667df
commit 5bd5eae62d
358 changed files with 2276 additions and 495 deletions
@@ -5,16 +5,14 @@ declare(strict_types=1);
namespace app\adminapi\controller\firstvisit;
use app\adminapi\controller\BaseAdminController;
use app\adminapi\logic\auth\AuthLogic;
use app\adminapi\logic\firstvisit\WecomAcquisitionCustomerLogic;
use app\adminapi\logic\firstvisit\WecomPromotionLogic;
use app\common\service\qywx\QywxPromotionContactApiService;
use app\common\service\qywx\QywxPromotionMediaService;
use app\common\service\qywx\QywxPromotionOperatorAccess;
class WecomPromotionController extends BaseAdminController
{
private const PAGE_PERMISSION = 'firstvisit.wecomPromotion/overview';
public function tagOptions()
{
if (!$this->hasPagePermission()) {
@@ -25,7 +23,7 @@ class WecomPromotionController extends BaseAdminController
public function createTag()
{
if (!$this->hasPagePermission()) {
if (!$this->hasBasePagePermission()) {
return $this->fail('权限不足');
}
if (!$this->request->isPost()) {
@@ -68,9 +66,13 @@ class WecomPromotionController extends BaseAdminController
if (!$this->hasPagePermission()) {
return $this->fail('权限不足');
}
$params = $this->request->post();
if ((int) ($params['id'] ?? 0) <= 0 && !$this->hasBasePagePermission()) {
return $this->fail('共享操作人只能编辑已授权方案,不能新建分流方案');
}
return $this->run(fn () => $this->success('分流方案已保存', WecomPromotionLogic::savePool(
$this->request->post(),
$params,
$this->adminId,
$this->adminInfo
)));
@@ -91,7 +93,7 @@ class WecomPromotionController extends BaseAdminController
public function batchSetOperators()
{
if (!$this->hasPagePermission()) {
if (!$this->hasBasePagePermission()) {
return $this->fail('权限不足');
}
@@ -104,7 +106,7 @@ class WecomPromotionController extends BaseAdminController
public function deletePool()
{
if (!$this->hasPagePermission()) {
if (!$this->hasBasePagePermission()) {
return $this->fail('权限不足');
}
$id = (int) $this->request->post('id', 0);
@@ -158,7 +160,7 @@ class WecomPromotionController extends BaseAdminController
public function checkApiPermission()
{
if (!$this->hasPagePermission()) {
if (!$this->hasBasePagePermission()) {
return $this->fail('权限不足');
}
@@ -167,7 +169,7 @@ class WecomPromotionController extends BaseAdminController
public function syncRemoteLinks()
{
if (!$this->hasPagePermission()) {
if (!$this->hasBasePagePermission()) {
return $this->fail('权限不足');
}
$poolId = (int) $this->request->post('pool_id', 0);
@@ -195,7 +197,7 @@ class WecomPromotionController extends BaseAdminController
public function deleteRemoteLink()
{
if (!$this->hasPagePermission()) {
if (!$this->hasBasePagePermission()) {
return $this->fail('权限不足');
}
$id = (int) $this->request->post('id', 0);
@@ -250,7 +252,7 @@ class WecomPromotionController extends BaseAdminController
public function deleteLink()
{
if (!$this->hasPagePermission()) {
if (!$this->hasBasePagePermission()) {
return $this->fail('权限不足');
}
$id = (int) $this->request->post('id', 0);
@@ -273,10 +275,11 @@ class WecomPromotionController extends BaseAdminController
private function hasPagePermission(): bool
{
if ((int) ($this->adminInfo['root'] ?? 0) === 1) {
return true;
}
return QywxPromotionOperatorAccess::hasPagePermission($this->adminId, $this->adminInfo);
}
return in_array(self::PAGE_PERMISSION, AuthLogic::getAuthByAdminId($this->adminId), true);
private function hasBasePagePermission(): bool
{
return QywxPromotionOperatorAccess::hasBasePagePermission($this->adminId, $this->adminInfo);
}
}
@@ -16,8 +16,9 @@ declare (strict_types=1);
namespace app\adminapi\http\middleware;
use app\adminapi\logic\LoginLogic;
use app\common\service\pharmacy\PharmacyUploadPermissionAlias;
use app\adminapi\logic\LoginLogic;
use app\common\service\pharmacy\PharmacyUploadPermissionAlias;
use app\common\service\qywx\QywxPromotionOperatorAccess;
use app\common\{
cache\AdminAuthCache,
service\JsonService
@@ -69,10 +70,23 @@ class AuthMiddleware
$adminAuthCache = new AdminAuthCache($request->adminInfo['admin_id']);
// 当前访问路径
$accessUri = strtolower($request->controller() . '/' . $request->action());
// 全部路由
$allUri = $this->formatUrl($adminAuthCache->getAllUri());
// 当前访问路径
$accessUri = strtolower($request->controller() . '/' . $request->action());
// 获客助手的子接口多数不是独立菜单权限。整组动作统一绑定页面权限,
// 共享操作人的动态页面权限也必须先经过这一层,再由业务层校验具体方案。
if (str_starts_with($accessUri, 'firstvisit.wecompromotion/')) {
$adminUris = $this->formatUrl($adminAuthCache->getAdminUri() ?? []);
if ($this->isKnownWecomPromotionAction($accessUri)
&& in_array(strtolower(QywxPromotionOperatorAccess::PAGE_PERMISSION), $adminUris, true)) {
return $next($request);
}
return JsonService::fail('权限不足,无法访问或操作');
}
// 全部路由
$allUri = $this->formatUrl($adminAuthCache->getAllUri());
// 判断该当前访问的uri是否存在,不存在无需验证
if (!in_array($accessUri, $allUri, true)
@@ -204,6 +218,31 @@ class AuthMiddleware
'tcm.prescription/audit',
], true);
}
private function isKnownWecomPromotionAction(string $accessUri): bool
{
return in_array($accessUri, [
'firstvisit.wecompromotion/tagoptions',
'firstvisit.wecompromotion/createtag',
'firstvisit.wecompromotion/uploadwelcomemedia',
'firstvisit.wecompromotion/overview',
'firstvisit.wecompromotion/savepool',
'firstvisit.wecompromotion/savewidget',
'firstvisit.wecompromotion/batchsetoperators',
'firstvisit.wecompromotion/deletepool',
'firstvisit.wecompromotion/savelink',
'firstvisit.wecompromotion/savemember',
'firstvisit.wecompromotion/togglemember',
'firstvisit.wecompromotion/checkapipermission',
'firstvisit.wecompromotion/syncremotelinks',
'firstvisit.wecompromotion/remotelinkdetail',
'firstvisit.wecompromotion/deleteremotelink',
'firstvisit.wecompromotion/synccustomers',
'firstvisit.wecompromotion/customerstatistics',
'firstvisit.wecompromotion/togglelink',
'firstvisit.wecompromotion/deletelink',
], true);
}
/**
* 处方库 lists:与开方、处方库维护菜单权限互通(避免开方页「从处方库导入」403)
+21 -6
View File
@@ -18,6 +18,7 @@ use app\common\model\auth\Admin;
use app\common\model\auth\AdminRole;
use app\common\model\auth\SystemMenu;
use app\common\model\auth\SystemRoleMenu;
use app\common\service\qywx\QywxPromotionOperatorAccess;
/**
@@ -74,11 +75,9 @@ class AuthLogic
->column('perms');
$hasAllAuth = array_diff($allAuth, $roleAuth);
if (empty($hasAllAuth)) {
return ['*'];
}
$permissions = empty($hasAllAuth) ? ['*'] : $roleAuth;
return $roleAuth;
return self::appendSharedPromotionPermission($permissions, (int) ($admin['id'] ?? 0));
}
@@ -94,12 +93,28 @@ class AuthLogic
$roleIds = AdminRole::where('admin_id', $adminId)->column('role_id');
$menuId = SystemRoleMenu::whereIn('role_id', $roleIds)->column('menu_id');
return SystemMenu::distinct(true)
$permissions = SystemMenu::distinct(true)
->where([
['is_disable', '=', 0],
['perms', '<>', ''],
['id', 'in', array_unique($menuId)],
])
->column('perms');
return self::appendSharedPromotionPermission($permissions, $adminId);
}
}
private static function appendSharedPromotionPermission(array $permissions, int $adminId): array
{
if (in_array('*', $permissions, true)
|| in_array(QywxPromotionOperatorAccess::PAGE_PERMISSION, $permissions, true)
|| !QywxPromotionOperatorAccess::hasSharedPagePermission($adminId)) {
return $permissions;
}
$permissions[] = QywxPromotionOperatorAccess::PAGE_PERMISSION;
return array_values(array_unique($permissions));
}
}
+47 -1
View File
@@ -20,6 +20,7 @@ use app\common\logic\BaseLogic;
use app\common\model\auth\Admin;
use app\common\model\auth\SystemMenu;
use app\common\model\auth\SystemRoleMenu;
use app\common\service\qywx\QywxPromotionOperatorAccess;
/**
@@ -51,6 +52,12 @@ class MenuLogic extends BaseLogic
if ($admin['root'] != 1) {
$roleMenu = SystemRoleMenu::whereIn('role_id', $admin['role_id'])->column('menu_id');
if (QywxPromotionOperatorAccess::hasSharedPagePermission((int) $adminId)) {
$roleMenu = array_values(array_unique(array_merge(
array_map('intval', $roleMenu),
self::sharedPromotionMenuIds()
)));
}
$where[] = ['id', 'in', $roleMenu];
}
@@ -62,6 +69,45 @@ class MenuLogic extends BaseLogic
}
/** @return list<int> */
private static function sharedPromotionMenuIds(): array
{
$menus = SystemMenu::where('is_disable', 0)
->field('id,pid,perms')
->select()
->toArray();
$byId = [];
$pageIds = [];
foreach ($menus as $menu) {
$id = (int) ($menu['id'] ?? 0);
if ($id <= 0) {
continue;
}
$byId[$id] = $menu;
if ((string) ($menu['perms'] ?? '') === QywxPromotionOperatorAccess::PAGE_PERMISSION) {
$pageIds[] = $id;
}
}
$result = [];
foreach ($pageIds as $pageId) {
$chain = [];
$currentId = $pageId;
while ($currentId > 0) {
if (!isset($byId[$currentId])) {
$chain = [];
break;
}
$chain[] = $currentId;
$currentId = (int) ($byId[$currentId]['pid'] ?? 0);
}
$result = array_merge($result, $chain);
}
return array_values(array_unique($result));
}
/**
* @notes 添加菜单
* @param array $params
@@ -181,4 +227,4 @@ class MenuLogic extends BaseLogic
return linear_to_tree($data, 'children');
}
}
}
@@ -6,6 +6,7 @@ namespace app\adminapi\logic\firstvisit;
use app\common\service\DataScope\DataScopeService;
use app\common\service\qywx\QywxCustomerAcquisitionCustomerService;
use app\common\service\qywx\QywxPromotionOperatorAccess;
use RuntimeException;
use think\facade\Db;
@@ -23,7 +24,7 @@ class WecomAcquisitionCustomerLogic
self::applyScope(
$query,
'l',
DataScopeService::getVisibleAdminIds($adminId, $adminInfo),
QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo),
self::operatorPoolIds($adminId)
);
if ($localLinkId > 0) {
@@ -60,7 +61,8 @@ class WecomAcquisitionCustomerLogic
{
$page = max(1, (int) ($params['page_no'] ?? $params['page'] ?? 1));
$pageSize = min(100, max(1, (int) ($params['page_size'] ?? 20)));
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
$hasBasePagePermission = QywxPromotionOperatorAccess::hasBasePagePermission($adminId, $adminInfo);
$visibleIds = QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo);
$operatorPoolIds = self::operatorPoolIds($adminId);
$base = self::customerQuery($params, $visibleIds, $operatorPoolIds);
$total = (int) (clone $base)->count();
@@ -87,7 +89,9 @@ class WecomAcquisitionCustomerLogic
return [
'meta' => [
'scope_label' => DataScopeService::scopeLabel(DataScopeService::getEffectiveScope($adminInfo)),
'scope_label' => $hasBasePagePermission
? DataScopeService::scopeLabel(DataScopeService::getEffectiveScope($adminInfo))
: '仅共享方案',
'generated_at' => date('Y-m-d H:i:s'),
],
'summary' => [
@@ -188,18 +192,7 @@ class WecomAcquisitionCustomerLogic
/** @return list<int> */
private static function operatorPoolIds(int $adminId): array
{
if ($adminId <= 0) {
return [];
}
$ids = Db::name('qywx_promotion_pool_operator')
->where('admin_id', $adminId)
->whereNull('delete_time')
->column('pool_id');
return array_values(array_unique(array_filter(array_map(
static fn ($value): int => (int) $value,
$ids
), static fn (int $value): bool => $value > 0)));
return QywxPromotionOperatorAccess::activePoolIds($adminId);
}
private static function maskIdentifier(string $value): string
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace app\adminapi\logic\firstvisit;
use app\adminapi\logic\dept\DeptLogic;
use app\common\cache\AdminAuthCache;
use app\common\service\DataScope\DataScopeService;
use app\common\service\qywx\QywxCustomerAcquisitionApiService;
use app\common\service\qywx\QywxCustomerAcquisitionLinkService;
@@ -13,10 +14,12 @@ use app\common\service\qywx\QywxPromotionConfig;
use app\common\service\qywx\QywxPromotionContactApiService;
use app\common\service\qywx\QywxPromotionMediaService;
use app\common\service\qywx\QywxPromotionMemberSchedulerService;
use app\common\service\qywx\QywxPromotionOperatorAccess;
use app\common\service\qywx\QywxPromotionRangeSyncService;
use app\common\service\qywx\QywxPromotionWidgetService;
use RuntimeException;
use think\facade\Db;
use think\facade\Log;
/** 一诊 / 企业微信获客助手管理逻辑。 */
class WecomPromotionLogic
@@ -25,7 +28,8 @@ class WecomPromotionLogic
{
self::assertMemberDispatchSchema();
self::assertPoolOperatorSchema();
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
$hasBasePagePermission = QywxPromotionOperatorAccess::hasBasePagePermission($adminId, $adminInfo);
$visibleIds = QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo);
$operatorPoolIds = self::operatorPoolIds($adminId);
$poolsQuery = Db::name('qywx_promotion_pool')->alias('p')
->leftJoin('admin u', 'u.id = p.owner_admin_id')
@@ -174,7 +178,8 @@ class WecomPromotionLogic
));
$pool['is_shared_with_me'] = in_array($adminId, $pool['operator_admin_ids'], true);
$pool['can_operate'] = true;
$pool['can_manage_access'] = self::ownerInScope((int) ($pool['owner_admin_id'] ?? 0), $visibleIds);
$pool['can_manage_access'] = $hasBasePagePermission
&& self::ownerInScope((int) ($pool['owner_admin_id'] ?? 0), $visibleIds);
$pool['can_delete'] = $pool['can_manage_access'];
$pool['dispatch_sync'] = $sync;
$pool['using_backup'] = $reception['using_backup'];
@@ -196,7 +201,9 @@ class WecomPromotionLogic
return [
'meta' => [
'admin_id' => $adminId,
'scope_label' => DataScopeService::scopeLabel(DataScopeService::getEffectiveScope($adminInfo)),
'scope_label' => $hasBasePagePermission
? DataScopeService::scopeLabel(DataScopeService::getEffectiveScope($adminInfo))
: '仅共享方案',
'generated_at' => date('Y-m-d H:i:s'),
],
'config' => $config,
@@ -210,7 +217,9 @@ class WecomPromotionLogic
'links' => $links,
'member_options' => $memberOptions,
'operator_options' => $operatorOptions,
'department_options' => DeptLogic::getAllDataScoped($adminId, $adminInfo),
'department_options' => $hasBasePagePermission
? DeptLogic::getAllDataScoped($adminId, $adminInfo)
: [],
'automation_installed' => QywxPromotionConfig::installed(),
'customer_acquisition_link_example' => QywxCustomerAcquisitionLinkService::example(),
];
@@ -220,6 +229,9 @@ class WecomPromotionLogic
{
self::assertMemberDispatchSchema();
$id = max(0, (int) ($params['id'] ?? 0));
if ($id <= 0 && !QywxPromotionOperatorAccess::hasBasePagePermission($adminId, $adminInfo)) {
throw new RuntimeException('共享操作人只能编辑已授权方案,不能新建分流方案');
}
$existingPool = $id > 0
? self::assertScopedRow('qywx_promotion_pool', $id, $adminId, $adminInfo)
: null;
@@ -459,7 +471,12 @@ class WecomPromotionLogic
?QywxCustomerAcquisitionApiService $api = null
): void
{
self::assertBasePagePermission($adminId, $adminInfo);
self::assertScopedRow('qywx_promotion_pool', $id, $adminId, $adminInfo, false);
$operatorAdminIds = self::normalizePositiveIds(Db::name('qywx_promotion_pool_operator')
->where('pool_id', $id)
->whereNull('delete_time')
->column('admin_id'));
$links = Db::name('qywx_promotion_link')
->where('pool_id', $id)
->order('id', 'asc')
@@ -563,6 +580,7 @@ class WecomPromotionLogic
'update_time' => $now,
]);
});
self::clearOperatorAuthCaches($operatorAdminIds);
}
/**
@@ -574,6 +592,7 @@ class WecomPromotionLogic
public static function batchSetOperators(array $params, int $adminId, array $adminInfo): array
{
self::assertPoolOperatorSchema();
self::assertBasePagePermission($adminId, $adminInfo);
$poolIds = self::normalizePositiveIds((array) ($params['pool_ids'] ?? []));
$operatorAdminIds = self::normalizePositiveIds((array) (
$params['operator_admin_ids'] ?? $params['admin_ids'] ?? []
@@ -598,9 +617,8 @@ class WecomPromotionLogic
throw new RuntimeException('不能将当前账号设置为自己的共享操作人');
}
$pools = [];
foreach ($poolIds as $poolId) {
$pools[$poolId] = self::assertScopedRow(
self::assertScopedRow(
'qywx_promotion_pool',
$poolId,
$adminId,
@@ -609,7 +627,7 @@ class WecomPromotionLogic
);
}
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
$visibleIds = QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo);
if ($visibleIds !== null) {
foreach ($operatorAdminIds as $operatorAdminId) {
if (!in_array($operatorAdminId, $visibleIds, true)) {
@@ -621,40 +639,24 @@ class WecomPromotionLogic
if ($action === 'grant') {
$adminQuery->where('disable', 0);
}
$operatorAdmins = $adminQuery->field('id,root')->select()->toArray();
$operatorAdmins = $adminQuery->field('id')->select()->toArray();
$existingAdminIds = self::normalizePositiveIds(array_column($operatorAdmins, 'id'));
if (count($existingAdminIds) !== count($operatorAdminIds)) {
throw new RuntimeException($action === 'grant'
? '选择的操作人不存在或账号已被禁用'
: '选择的操作人不存在');
}
if ($action === 'grant') {
$pagePermissionAdminIds = self::promotionPagePermissionAdminIdSet($operatorAdminIds);
foreach ($operatorAdmins as $operatorAdmin) {
if ((int) ($operatorAdmin['root'] ?? 0) !== 1
&& !isset($pagePermissionAdminIds[(int) ($operatorAdmin['id'] ?? 0)])) {
throw new RuntimeException('选择的操作人尚未获得企业微信获客助手页面权限');
}
}
}
$now = time();
$affected = Db::transaction(function () use (
$action,
$poolIds,
$pools,
$operatorAdminIds,
$adminId,
$now
): int {
$changed = 0;
foreach ($poolIds as $poolId) {
$ownerAdminId = (int) ($pools[$poolId]['owner_admin_id'] ?? 0);
foreach ($operatorAdminIds as $operatorAdminId) {
// 方案归属人天然拥有权限,无需写入共享关系。
if ($operatorAdminId === $ownerAdminId) {
continue;
}
$query = Db::name('qywx_promotion_pool_operator')
->where('pool_id', $poolId)
->where('admin_id', $operatorAdminId);
@@ -689,6 +691,7 @@ class WecomPromotionLogic
return $changed;
});
self::clearOperatorAuthCaches($operatorAdminIds);
return [
'action' => $action,
@@ -836,13 +839,14 @@ class WecomPromotionLogic
*/
public static function syncRemoteLinks(int $poolId, int $adminId, array $adminInfo): array
{
$pool = self::assertScopedRow('qywx_promotion_pool', $poolId, $adminId, $adminInfo);
self::assertBasePagePermission($adminId, $adminInfo);
$pool = self::assertScopedRow('qywx_promotion_pool', $poolId, $adminId, $adminInfo, false);
$legacyCount = (int) Db::name('qywx_promotion_link')
->where('pool_id', $poolId)
->whereNull('delete_time')
->whereRaw("(remote_link_id IS NULL OR remote_link_id = '')")
->count();
$visibleAdminIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
$visibleAdminIds = QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo);
$visibleUserIds = null;
if ($visibleAdminIds !== null) {
$visibleUserIds = array_fill_keys(array_column(self::memberOptions(
@@ -914,7 +918,7 @@ class WecomPromotionLogic
}
$api = new QywxCustomerAcquisitionApiService();
$remote = self::normaliseRemoteLink($api->getLink($remoteLinkId), $remoteLinkId);
$visibleUserIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo) === null
$visibleUserIds = QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo) === null
? null
: array_fill_keys(array_column(self::memberOptions(
$adminId,
@@ -932,7 +936,8 @@ class WecomPromotionLogic
/** 官方链接由方案统一删除,避免绕过方案删除租约和同步状态机。 */
public static function deleteRemoteLink(int $id, int $adminId, array $adminInfo): void
{
$row = self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo);
self::assertBasePagePermission($adminId, $adminInfo);
$row = self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo, false);
$remoteLinkId = trim((string) ($row['remote_link_id'] ?? ''));
if ($remoteLinkId === '') {
throw new RuntimeException('历史手工链接只能从本地移除');
@@ -955,7 +960,8 @@ class WecomPromotionLogic
public static function deleteLink(int $id, int $adminId, array $adminInfo): void
{
$row = self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo);
self::assertBasePagePermission($adminId, $adminInfo);
$row = self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo, false);
if (trim((string) ($row['remote_link_id'] ?? '')) !== '') {
throw new RuntimeException('官方获客链接不能仅从本地移除,请使用“删除分流方案”同步删除企业微信链接');
}
@@ -968,7 +974,7 @@ class WecomPromotionLogic
/** @return list<array{id:int,name:string,disable:int,can_grant:bool,display_dept_id:int,dept_ids:list<int>,dept_names:list<string>}> */
private static function operatorOptions(int $adminId, array $adminInfo): array
{
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
$visibleIds = QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo);
if ($visibleIds === []) {
return [];
}
@@ -978,24 +984,21 @@ class WecomPromotionLogic
if ($visibleIds !== null) {
$query->whereIn('a.id', $visibleIds);
}
$admins = $query->field('a.id,a.name,a.root,a.disable')->order('a.disable', 'asc')->order('a.name', 'asc')->order('a.id', 'asc')->select()->toArray();
$admins = $query->field('a.id,a.name,a.disable')->order('a.disable', 'asc')->order('a.name', 'asc')->order('a.id', 'asc')->select()->toArray();
if ($admins === []) {
return [];
}
$adminIds = self::normalizePositiveIds(array_column($admins, 'id'));
$departments = self::adminDepartmentMaps($adminIds);
$pagePermissionAdminIds = self::promotionPagePermissionAdminIdSet($adminIds);
$result = [];
foreach ($admins as $admin) {
$aid = (int) $admin['id'];
$deptIds = array_values(array_unique(array_filter($departments[$aid]['ids'] ?? [])));
$hasPagePermission = (int) ($admin['root'] ?? 0) === 1
|| isset($pagePermissionAdminIds[$aid]);
$result[] = [
'id' => $aid,
'name' => (string) ($admin['name'] ?? ('账号 ' . $aid)),
'disable' => (int) ($admin['disable'] ?? 0),
'can_grant' => (int) ($admin['disable'] ?? 0) === 0 && $hasPagePermission,
'can_grant' => (int) ($admin['disable'] ?? 0) === 0,
'display_dept_id' => (int) ($deptIds[0] ?? 0),
'dept_ids' => $deptIds,
'dept_names' => array_values(array_unique($departments[$aid]['names'] ?? [])),
@@ -1038,7 +1041,7 @@ class WecomPromotionLogic
/** @return list<array{id:int,name:string,userid:string,display_dept_id:int,dept_ids:list<int>,dept_names:list<string>}> */
private static function memberOptions(int $adminId, array $adminInfo, array $extraAdminIds = []): array
{
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
$visibleIds = QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo);
$allowedIds = $visibleIds === null
? null
: self::normalizePositiveIds(array_merge($visibleIds, $extraAdminIds));
@@ -1439,7 +1442,7 @@ class WecomPromotionLogic
throw new RuntimeException('数据不存在或已删除');
}
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
$visibleIds = QywxPromotionOperatorAccess::visibleAdminIds($adminId, $adminInfo);
$pool = $table === 'qywx_promotion_pool'
? $row
: ($table === 'qywx_promotion_link'
@@ -1506,14 +1509,7 @@ class WecomPromotionLogic
/** @return list<int> */
private static function operatorPoolIds(int $adminId): array
{
if ($adminId <= 0) {
return [];
}
return self::normalizePositiveIds(Db::name('qywx_promotion_pool_operator')
->where('admin_id', $adminId)
->whereNull('delete_time')
->column('pool_id'));
return QywxPromotionOperatorAccess::activePoolIds($adminId);
}
private static function isPoolOperator(int $poolId, int $adminId): bool
@@ -1566,33 +1562,6 @@ class WecomPromotionLogic
return $departments;
}
/** @param list<int> $adminIds @return array<int,true> */
private static function promotionPagePermissionAdminIdSet(array $adminIds): array
{
if ($adminIds === []) {
return [];
}
$menuIds = self::normalizePositiveIds(Db::name('system_menu')
->where('perms', 'firstvisit.wecomPromotion/overview')
->where('is_disable', 0)
->column('id'));
if ($menuIds === []) {
return [];
}
$roleIds = self::normalizePositiveIds(Db::name('system_role_menu')
->whereIn('menu_id', $menuIds)
->column('role_id'));
if ($roleIds === []) {
return [];
}
$permittedAdminIds = self::normalizePositiveIds(Db::name('admin_role')
->whereIn('role_id', $roleIds)
->whereIn('admin_id', $adminIds)
->column('admin_id'));
return array_fill_keys($permittedAdminIds, true);
}
/** @return list<int> */
private static function normalizePositiveIds(array $ids): array
{
@@ -1620,6 +1589,30 @@ class WecomPromotionLogic
return $time === false ? 0 : $time;
}
private static function assertBasePagePermission(int $adminId, array $adminInfo): void
{
if (!QywxPromotionOperatorAccess::hasBasePagePermission($adminId, $adminInfo)) {
throw new RuntimeException('共享操作人无权执行新建、删除、转授权或全局同步操作');
}
}
/** @param list<int> $adminIds */
private static function clearOperatorAuthCaches(array $adminIds): void
{
foreach (self::normalizePositiveIds($adminIds) as $operatorAdminId) {
try {
(new AdminAuthCache($operatorAdminId))->clearAuthCache();
} catch (\Throwable $error) {
// 授权关系已提交,缓存清理失败不应回滚数据;业务接口仍有实时权限校验。
Log::warning(sprintf(
'清理获客助手共享操作人权限缓存失败 admin_id=%d: %s',
$operatorAdminId,
$error->getMessage()
));
}
}
}
private static function assertMemberDispatchSchema(): void
{
try {
@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace app\common\service\qywx;
use app\common\service\DataScope\DataScopeService;
use think\facade\Db;
/** 分流方案共享操作人产生的页面入口与专用数据范围。 */
final class QywxPromotionOperatorAccess
{
public const PAGE_PERMISSION = 'firstvisit.wecomPromotion/overview';
public static function hasBasePagePermission(int $adminId, array $adminInfo = []): bool
{
if ((int) ($adminInfo['root'] ?? 0) === 1) {
return true;
}
if ($adminId <= 0) {
return false;
}
return Db::name('admin_role')->alias('ar')
->join('system_role_menu rm', 'rm.role_id = ar.role_id')
->join('system_menu m', 'm.id = rm.menu_id')
->where('ar.admin_id', $adminId)
->where('m.perms', self::PAGE_PERMISSION)
->where('m.is_disable', 0)
->count() > 0;
}
public static function hasSharedPagePermission(int $adminId): bool
{
if ($adminId <= 0 || !self::pageMenuEnabled()) {
return false;
}
try {
return Db::name('qywx_promotion_pool_operator')->alias('po')
->join('qywx_promotion_pool p', 'p.id = po.pool_id')
->where('po.admin_id', $adminId)
->whereNull('po.delete_time')
->whereNull('p.delete_time')
->count() > 0;
} catch (\Throwable $error) {
if (self::isMissingTable($error)) {
return false;
}
throw $error;
}
}
public static function hasPagePermission(int $adminId, array $adminInfo = []): bool
{
return self::hasBasePagePermission($adminId, $adminInfo)
|| self::hasSharedPagePermission($adminId);
}
/** 基础页面权限沿用角色数据范围;纯共享账号只能通过 operator pool 范围访问。 */
public static function visibleAdminIds(int $adminId, array $adminInfo): ?array
{
return self::hasBasePagePermission($adminId, $adminInfo)
? DataScopeService::getVisibleAdminIds($adminId, $adminInfo)
: [];
}
/** @return list<int> */
public static function activePoolIds(int $adminId): array
{
if ($adminId <= 0) {
return [];
}
try {
$ids = Db::name('qywx_promotion_pool_operator')->alias('po')
->join('qywx_promotion_pool p', 'p.id = po.pool_id')
->where('po.admin_id', $adminId)
->whereNull('po.delete_time')
->whereNull('p.delete_time')
->column('po.pool_id');
} catch (\Throwable $error) {
if (self::isMissingTable($error)) {
return [];
}
throw $error;
}
return array_values(array_unique(array_filter(array_map(
static fn ($value): int => (int) $value,
$ids
), static fn (int $value): bool => $value > 0)));
}
private static function pageMenuEnabled(): bool
{
return Db::name('system_menu')
->where('perms', self::PAGE_PERMISSION)
->where('is_disable', 0)
->count() > 0;
}
private static function isMissingTable(\Throwable $error): bool
{
$message = strtolower($error->getMessage());
return str_contains($message, '42s02')
|| str_contains($message, '1146')
|| str_contains($message, 'no such table');
}
}