更新
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\firstvisit;
|
||||
|
||||
use app\common\service\DataScope\DataScopeService;
|
||||
use app\common\service\qywx\QywxPromotionOpenWorkService;
|
||||
use RuntimeException;
|
||||
use think\facade\Db;
|
||||
|
||||
/** 一诊 / 企业微信推广助手管理逻辑。 */
|
||||
class WecomPromotionLogic
|
||||
{
|
||||
public static function overview(int $adminId, array $adminInfo, string $domain): array
|
||||
{
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
$accountsQuery = Db::name('qywx_promotion_account')->alias('a')
|
||||
->leftJoin('admin u', 'u.id = a.owner_admin_id')
|
||||
->leftJoin('dept d', 'd.id = a.dept_id')
|
||||
->whereNull('a.delete_time');
|
||||
$accounts = $accountsQuery
|
||||
->field('a.id,a.corp_id,a.corp_name,a.agent_id,a.auth_status,a.owner_admin_id,a.dept_id,a.authorized_at,a.last_refresh_at,a.create_time,u.name as owner_name,d.name as dept_name')
|
||||
->order('a.auth_status', 'desc')
|
||||
->order('a.id', 'desc')
|
||||
->select()->toArray();
|
||||
foreach ($accounts as &$account) {
|
||||
$account['corp_id_masked'] = self::mask((string) ($account['corp_id'] ?? ''));
|
||||
unset($account['corp_id']);
|
||||
}
|
||||
unset($account);
|
||||
|
||||
$poolsQuery = Db::name('qywx_promotion_pool')->alias('p')
|
||||
->leftJoin('admin u', 'u.id = p.owner_admin_id')
|
||||
->leftJoin('dept d', 'd.id = p.dept_id')
|
||||
->whereNull('p.delete_time');
|
||||
self::applyOwnerScope($poolsQuery, 'p', $visibleIds);
|
||||
$pools = $poolsQuery
|
||||
->field('p.id,p.name,p.public_key,p.status,p.fallback_url,p.click_count,p.owner_admin_id,p.dept_id,p.create_time,p.update_time,u.name as owner_name,d.name as dept_name')
|
||||
->order('p.id', 'desc')
|
||||
->select()->toArray();
|
||||
|
||||
$poolIds = array_values(array_filter(array_map('intval', array_column($pools, 'id'))));
|
||||
$links = [];
|
||||
if ($poolIds !== []) {
|
||||
$links = Db::name('qywx_promotion_link')->alias('l')
|
||||
->leftJoin('qywx_promotion_account a', 'a.id = l.account_id AND a.delete_time IS NULL')
|
||||
->whereNull('l.delete_time')
|
||||
->whereIn('l.pool_id', $poolIds)
|
||||
->field('l.id,l.pool_id,l.account_id,l.name,l.group_name,l.wecom_url,l.weight,l.status,l.daily_limit,l.today_count,l.today_date,l.active_start,l.active_end,l.click_count,l.last_click_time,l.remark,l.create_time,l.update_time,a.corp_name,a.auth_status')
|
||||
->order('l.status', 'desc')
|
||||
->order('l.weight', 'desc')
|
||||
->order('l.id', 'desc')
|
||||
->select()->toArray();
|
||||
}
|
||||
|
||||
$domain = rtrim($domain, '/');
|
||||
foreach ($pools as &$pool) {
|
||||
$key = (string) $pool['public_key'];
|
||||
$scriptUrl = $domain . '/api/qywx-promotion/js/' . $key;
|
||||
$goUrl = $domain . '/api/qywx-promotion/go/' . $key;
|
||||
$pool['script_url'] = $scriptUrl;
|
||||
$pool['go_url'] = $goUrl;
|
||||
$pool['install_code'] = '<script src="' . $scriptUrl . '" defer></script>';
|
||||
$pool['trigger_code'] = '<a href="#" data-wecom-promotion="' . $key . '">添加企业微信</a>';
|
||||
}
|
||||
unset($pool);
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$todayClicks = 0;
|
||||
$onlineLinks = 0;
|
||||
foreach ($links as $link) {
|
||||
if ((int) ($link['status'] ?? 0) === 1) {
|
||||
$onlineLinks++;
|
||||
}
|
||||
if ((string) ($link['today_date'] ?? '') === $today) {
|
||||
$todayClicks += (int) ($link['today_count'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$config = QywxPromotionOpenWorkService::configurationStatus();
|
||||
$config['provider_callback_url'] = $domain . '/api/qywx-promotion/provider/callback';
|
||||
$config['auth_callback_url'] = QywxPromotionOpenWorkService::configuredRedirectUri(
|
||||
$domain . '/api/qywx-promotion/auth/callback'
|
||||
);
|
||||
|
||||
return [
|
||||
'meta' => [
|
||||
'scope_label' => DataScopeService::scopeLabel(DataScopeService::getEffectiveScope($adminInfo)),
|
||||
'can_authorize' => self::canAuthorize($adminId, $adminInfo),
|
||||
'generated_at' => date('Y-m-d H:i:s'),
|
||||
],
|
||||
'config' => $config,
|
||||
'summary' => [
|
||||
'authorized_accounts' => count(array_filter($accounts, static fn (array $row): bool => (int) ($row['auth_status'] ?? 0) === 1)),
|
||||
'pool_count' => count($pools),
|
||||
'online_links' => $onlineLinks,
|
||||
'today_clicks' => $todayClicks,
|
||||
],
|
||||
'accounts' => $accounts,
|
||||
'pools' => $pools,
|
||||
'links' => $links,
|
||||
'allowed_link_hosts' => array_values((array) config('qywx_promotion.allowed_link_hosts', [])),
|
||||
];
|
||||
}
|
||||
|
||||
public static function authorizationUrl(int $adminId, array $adminInfo, string $domain): array
|
||||
{
|
||||
if (!self::canAuthorize($adminId, $adminInfo)) {
|
||||
throw new RuntimeException('只有系统管理员可以发起企业微信应用授权');
|
||||
}
|
||||
$redirectUri = rtrim($domain, '/') . '/api/qywx-promotion/auth/callback';
|
||||
|
||||
return ['url' => QywxPromotionOpenWorkService::authorizationUrl($adminId, $redirectUri)];
|
||||
}
|
||||
|
||||
public static function verifyAccount(int $id, int $adminId, array $adminInfo): array
|
||||
{
|
||||
if (!self::canAuthorize($adminId, $adminInfo)) {
|
||||
throw new RuntimeException('只有系统管理员可以验证企业微信授权凭证');
|
||||
}
|
||||
self::assertAuthorizedAccount($id, false);
|
||||
|
||||
return QywxPromotionOpenWorkService::verifyAccount($id);
|
||||
}
|
||||
|
||||
public static function savePool(array $params, int $adminId, array $adminInfo): array
|
||||
{
|
||||
$id = max(0, (int) ($params['id'] ?? 0));
|
||||
$name = trim((string) ($params['name'] ?? ''));
|
||||
if ($name === '' || mb_strlen($name) > 60) {
|
||||
throw new RuntimeException('请输入 1-60 个字符的分流方案名称');
|
||||
}
|
||||
$fallback = trim((string) ($params['fallback_url'] ?? ''));
|
||||
if (!QywxPromotionOpenWorkService::isAllowedPromotionUrl($fallback, true)) {
|
||||
throw new RuntimeException('兜底链接必须是已允许的 HTTPS 企业微信链接');
|
||||
}
|
||||
$now = time();
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'status' => (int) ($params['status'] ?? 1) === 1 ? 1 : 0,
|
||||
'fallback_url' => $fallback,
|
||||
'update_time' => $now,
|
||||
];
|
||||
if ($id > 0) {
|
||||
self::assertScopedRow('qywx_promotion_pool', $id, $adminId, $adminInfo);
|
||||
Db::name('qywx_promotion_pool')->where('id', $id)->update($data);
|
||||
} else {
|
||||
$data += [
|
||||
'public_key' => bin2hex(random_bytes(16)),
|
||||
'owner_admin_id' => $adminId,
|
||||
'dept_id' => self::primaryDeptId($adminId),
|
||||
'click_count' => 0,
|
||||
'create_time' => $now,
|
||||
];
|
||||
$id = (int) Db::name('qywx_promotion_pool')->insertGetId($data);
|
||||
}
|
||||
|
||||
return ['id' => $id];
|
||||
}
|
||||
|
||||
public static function deletePool(int $id, int $adminId, array $adminInfo): void
|
||||
{
|
||||
self::assertScopedRow('qywx_promotion_pool', $id, $adminId, $adminInfo);
|
||||
$now = time();
|
||||
Db::transaction(function () use ($id, $now): void {
|
||||
Db::name('qywx_promotion_pool')->where('id', $id)->update(['delete_time' => $now, 'update_time' => $now]);
|
||||
Db::name('qywx_promotion_link')->where('pool_id', $id)->whereNull('delete_time')->update(['delete_time' => $now, 'update_time' => $now]);
|
||||
});
|
||||
}
|
||||
|
||||
public static function saveLink(array $params, int $adminId, array $adminInfo): array
|
||||
{
|
||||
$id = max(0, (int) ($params['id'] ?? 0));
|
||||
$poolId = max(0, (int) ($params['pool_id'] ?? 0));
|
||||
$pool = self::assertScopedRow('qywx_promotion_pool', $poolId, $adminId, $adminInfo);
|
||||
$name = trim((string) ($params['name'] ?? ''));
|
||||
if ($name === '' || mb_strlen($name) > 80) {
|
||||
throw new RuntimeException('请输入 1-80 个字符的推广链接名称');
|
||||
}
|
||||
$url = trim((string) ($params['wecom_url'] ?? ''));
|
||||
if (!QywxPromotionOpenWorkService::isAllowedPromotionUrl($url)) {
|
||||
throw new RuntimeException('推广链接必须是已允许的 HTTPS 企业微信链接');
|
||||
}
|
||||
$accountId = max(0, (int) ($params['account_id'] ?? 0));
|
||||
if ($accountId > 0) {
|
||||
self::assertAuthorizedAccount($accountId, true);
|
||||
}
|
||||
$startAt = self::parseTime($params['active_start'] ?? null);
|
||||
$endAt = self::parseTime($params['active_end'] ?? null);
|
||||
if ($startAt > 0 && $endAt > 0 && $startAt >= $endAt) {
|
||||
throw new RuntimeException('生效结束时间必须晚于开始时间');
|
||||
}
|
||||
$now = time();
|
||||
$data = [
|
||||
'pool_id' => $poolId,
|
||||
'account_id' => $accountId,
|
||||
'name' => $name,
|
||||
'group_name' => mb_substr(trim((string) ($params['group_name'] ?? '默认分组')), 0, 60),
|
||||
'wecom_url' => $url,
|
||||
'weight' => min(100, max(1, (int) ($params['weight'] ?? 1))),
|
||||
'status' => (int) ($params['status'] ?? 1) === 1 ? 1 : 0,
|
||||
'daily_limit' => min(1000000, max(0, (int) ($params['daily_limit'] ?? 0))),
|
||||
'active_start' => $startAt,
|
||||
'active_end' => $endAt,
|
||||
'remark' => mb_substr(trim((string) ($params['remark'] ?? '')), 0, 255),
|
||||
'update_time' => $now,
|
||||
];
|
||||
if ($id > 0) {
|
||||
self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo);
|
||||
Db::name('qywx_promotion_link')->where('id', $id)->update($data);
|
||||
} else {
|
||||
$data += [
|
||||
'owner_admin_id' => (int) ($pool['owner_admin_id'] ?? 0) ?: $adminId,
|
||||
'dept_id' => (int) ($pool['dept_id'] ?? 0) ?: self::primaryDeptId($adminId),
|
||||
'click_count' => 0,
|
||||
'today_count' => 0,
|
||||
'today_date' => null,
|
||||
'last_click_time' => 0,
|
||||
'create_time' => $now,
|
||||
];
|
||||
$id = (int) Db::name('qywx_promotion_link')->insertGetId($data);
|
||||
}
|
||||
|
||||
return ['id' => $id];
|
||||
}
|
||||
|
||||
public static function toggleLink(int $id, int $status, int $adminId, array $adminInfo): void
|
||||
{
|
||||
self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo);
|
||||
Db::name('qywx_promotion_link')->where('id', $id)->update([
|
||||
'status' => $status === 1 ? 1 : 0,
|
||||
'update_time' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function deleteLink(int $id, int $adminId, array $adminInfo): void
|
||||
{
|
||||
self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo);
|
||||
Db::name('qywx_promotion_link')->where('id', $id)->update([
|
||||
'delete_time' => time(),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function assertScopedRow(string $table, int $id, int $adminId, array $adminInfo): array
|
||||
{
|
||||
if ($id <= 0) {
|
||||
throw new RuntimeException('数据不存在');
|
||||
}
|
||||
$query = Db::name($table)->where('id', $id)->whereNull('delete_time');
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds !== null) {
|
||||
if ($visibleIds === []) {
|
||||
throw new RuntimeException('无权访问该数据');
|
||||
}
|
||||
$query->whereIn('owner_admin_id', $visibleIds);
|
||||
}
|
||||
$row = $query->find();
|
||||
if (!$row) {
|
||||
throw new RuntimeException('数据不存在或超出当前权限范围');
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
private static function assertAuthorizedAccount(int $id, bool $requireActive): array
|
||||
{
|
||||
if ($id <= 0) {
|
||||
throw new RuntimeException('授权企业不存在');
|
||||
}
|
||||
$query = Db::name('qywx_promotion_account')->where('id', $id)->whereNull('delete_time');
|
||||
if ($requireActive) {
|
||||
$query->where('auth_status', 1);
|
||||
}
|
||||
$row = $query->find();
|
||||
if (!$row) {
|
||||
throw new RuntimeException($requireActive ? '授权企业无效或已取消授权' : '授权企业不存在');
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
private static function applyOwnerScope($query, string $alias, ?array $visibleIds): void
|
||||
{
|
||||
if ($visibleIds === null) {
|
||||
return;
|
||||
}
|
||||
if ($visibleIds === []) {
|
||||
$query->whereRaw('1 = 0');
|
||||
return;
|
||||
}
|
||||
$query->whereIn($alias . '.owner_admin_id', $visibleIds);
|
||||
}
|
||||
|
||||
private static function canAuthorize(int $adminId, array $adminInfo): bool
|
||||
{
|
||||
if ((int) ($adminInfo['root'] ?? 0) === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Db::name('admin_role')->alias('ar')
|
||||
->join('system_role r', 'r.id = ar.role_id AND r.delete_time IS NULL')
|
||||
->where('ar.admin_id', $adminId)
|
||||
->where('r.name', '管理员')
|
||||
->count() > 0;
|
||||
}
|
||||
|
||||
private static function primaryDeptId(int $adminId): int
|
||||
{
|
||||
return (int) (Db::name('admin_dept')->where('admin_id', $adminId)->order('dept_id', 'asc')->value('dept_id') ?? 0);
|
||||
}
|
||||
|
||||
private static function parseTime(mixed $value): int
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return 0;
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
return max(0, (int) $value);
|
||||
}
|
||||
$time = strtotime((string) $value);
|
||||
|
||||
return $time === false ? 0 : $time;
|
||||
}
|
||||
|
||||
private static function mask(string $value): string
|
||||
{
|
||||
$length = strlen($value);
|
||||
if ($length <= 8) {
|
||||
return $value === '' ? '' : str_repeat('*', $length);
|
||||
}
|
||||
|
||||
return substr($value, 0, 4) . str_repeat('*', max(4, $length - 8)) . substr($value, -4);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user