初始版本
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
-- 分流链接:平台分配域名(支持多个,一行一个)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_platform_domain', 'split', '平台分配域名', '一行填写一个根域名,如 example.com;不含 http:// 与路径,保存后在分流链接弹窗中可选用', 'text', '', '', '', '', ' rows="8" placeholder="example.com another.com"', NULL
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_platform_domain' LIMIT 1);
|
||||
|
||||
UPDATE `fa_config`
|
||||
SET `group` = 'split',
|
||||
`type` = 'text',
|
||||
`title` = '平台分配域名',
|
||||
`tip` = '一行填写一个根域名,如 example.com;不含 http:// 与路径,保存后在分流链接弹窗中可选用',
|
||||
`extend` = ' rows="8" placeholder="example.com another.com"'
|
||||
WHERE `name` = 'split_platform_domain';
|
||||
@@ -0,0 +1,54 @@
|
||||
-- 工单云控同步:Node 服务地址与各类型同步周期(分钟,0=不自动同步)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_scrm_node_host', 'split', '云控 Node 服务地址', '所有云控蜘蛛共用的 Headless 服务根地址', 'string', '', 'http://127.0.0.1:3001', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_scrm_node_host' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_fail_pause_threshold', 'split', '连续同步失败暂停阈值', '连续同步失败达到该次数后自动关闭工单并暂停定时同步;同步成功后将清零重新计数;0 表示不因失败自动暂停', 'number', '', '5', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_fail_pause_threshold' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_a2c', 'split', 'A2C云控同步周期(分钟)', '0 表示不自动同步', 'number', '', '5', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_a2c' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_haiwang', 'split', '海王同步周期(分钟)', '0 表示不自动同步', 'number', '', '5', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_haiwang' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_huojian', 'split', '火箭云控同步周期(分钟)', '0 表示不自动同步', 'number', '', '5', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_huojian' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_xinghe', 'split', '星河云控同步周期(分钟)', '0 表示不自动同步', 'number', '', '5', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_xinghe' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_ss_customer', 'split', 'SS云控(Customer)同步周期(分钟)', '0 表示不自动同步', 'number', '', '5', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_ss_customer' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_ceo_scrm', 'split', 'CEO SCRM同步周期(分钟)', '0 表示不自动同步(蜘蛛未实现)', 'number', '', '0', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_ceo_scrm' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_taiji', 'split', '太极云控同步周期(分钟)', '0 表示不自动同步', 'number', '', '0', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_taiji' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_ss_channel', 'split', 'SS云控(Channel)同步周期(分钟)', '0 表示不自动同步', 'number', '', '0', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_ss_channel' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_yifafa', 'split', '译发发云控同步周期(分钟)', '0 表示不自动同步', 'number', '', '0', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_yifafa' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_whatshub', 'split', 'Whatshub云控同步周期(分钟)', '0 表示不自动同步', 'number', '', '0', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_whatshub' LIMIT 1);
|
||||
|
||||
INSERT INTO `fa_config` (`name`, `group`, `title`, `tip`, `type`, `visible`, `value`, `content`, `rule`, `extend`, `setting`)
|
||||
SELECT 'split_sync_interval_sihai', 'split', '四海云控同步周期(分钟)', '0 表示不自动同步', 'number', '', '0', '', '', '', NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `fa_config` WHERE `name` = 'split_sync_interval_sihai' LIMIT 1);
|
||||
@@ -0,0 +1,29 @@
|
||||
-- 分流管理 - 链接管理表
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS `fa_split_link`;
|
||||
CREATE TABLE `fa_split_link` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
|
||||
`countries` varchar(500) NOT NULL DEFAULT '' COMMENT '投放国家(ISO2逗号分隔)',
|
||||
`link_code` char(9) NOT NULL DEFAULT '' COMMENT '分流链接(9位小写字母)',
|
||||
`description` varchar(255) NOT NULL DEFAULT '' COMMENT '链接描述',
|
||||
`auto_reply` text COMMENT '自动回复语句(一行一条)',
|
||||
`ip_protect` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT 'IP防护:0=关闭,1=开启',
|
||||
`random_shuffle` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '随机打乱:0=关闭,1=开启',
|
||||
`status` enum('normal','hidden') NOT NULL DEFAULT 'normal' COMMENT '状态:normal=正常,hidden=停用',
|
||||
`createtime` bigint(16) DEFAULT NULL COMMENT '创建时间',
|
||||
`updatetime` bigint(16) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `link_code` (`link_code`),
|
||||
KEY `admin_id` (`admin_id`),
|
||||
KEY `status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='分流链接表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', 0, 'split', '分流管理', 'fa fa-random', '', '', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split' LIMIT 1);
|
||||
@@ -0,0 +1,12 @@
|
||||
-- 分流链接:自动回复语句字段(一行一条,关联 link_code)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `fa_split_link`
|
||||
ADD COLUMN `auto_reply` text COMMENT '自动回复语句(一行一条)' AFTER `description`;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', r.id, 'split.link/autoreply', '自动回复', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` r
|
||||
WHERE r.name = 'split.link' AND r.ismenu = 0
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.link/autoreply' LIMIT 1)
|
||||
LIMIT 1;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- 分流链接:像素配置 JSON 字段 + 权限节点
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `fa_split_link`
|
||||
ADD COLUMN `pixel_config` mediumtext COMMENT '像素配置JSON' AFTER `auto_reply`;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.link/pixel', '像素配置', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.link' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.link/pixel' LIMIT 1)
|
||||
LIMIT 1;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 移除分流模块下重复的「管理我的域名」菜单(统一使用顶级「域名管理」)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
DELETE FROM `fa_auth_rule` WHERE `name` LIKE 'split.mydomain%';
|
||||
@@ -0,0 +1,79 @@
|
||||
-- 分流管理 - 号码管理表与菜单权限
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS `fa_split_number`;
|
||||
CREATE TABLE `fa_split_number` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '号码ID',
|
||||
`admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
|
||||
`split_link_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '关联分流链接ID',
|
||||
`ticket_name` varchar(100) NOT NULL DEFAULT '' COMMENT '工单名称',
|
||||
`number` varchar(50) NOT NULL DEFAULT '' COMMENT '号码',
|
||||
`number_type` varchar(32) NOT NULL DEFAULT '' COMMENT '号码类型',
|
||||
`number_type_custom` varchar(50) NOT NULL DEFAULT '' COMMENT '自定义号码类型',
|
||||
`visit_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '访问次数',
|
||||
`inbound_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '进线人数',
|
||||
`manual_manage` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '手动管理:0=否,1=是',
|
||||
`status` enum('normal','hidden') NOT NULL DEFAULT 'normal' COMMENT '状态:normal=正常,hidden=停用',
|
||||
`weigh` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序权重(越大越靠前)',
|
||||
`createtime` bigint(16) DEFAULT NULL COMMENT '创建时间',
|
||||
`updatetime` bigint(16) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_link_number` (`split_link_id`,`number`),
|
||||
KEY `admin_id` (`admin_id`),
|
||||
KEY `split_link_id` (`split_link_id`),
|
||||
KEY `status` (`status`),
|
||||
KEY `weigh` (`weigh`),
|
||||
KEY `number` (`number`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='分流号码表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', r.id, 'split.number', '号码管理', 'fa fa-phone', '', '', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` r
|
||||
WHERE r.name = 'split' AND r.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.number' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.number/index', '查看', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.number' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.number/index' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.number/add', '添加', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.number' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.number/add' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.number/edit', '编辑', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.number' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.number/edit' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.number/del', '删除', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.number' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.number/del' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.number/batchupdate', '批量更新号码', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.number' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.number/batchupdate' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.number/multi', '批量更新', 'fa fa-circle-o', '', '列表状态开关', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.number' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.number/multi' LIMIT 1)
|
||||
LIMIT 1;
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate\split;
|
||||
|
||||
use app\admin\model\split\Link;
|
||||
use app\admin\model\split\Number as NumberModel;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 分流号码验证器(部署源文件,由 deploy_split_number.php 复制到 application)
|
||||
*/
|
||||
class Number extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'number_type' => 'require|checkNumberType',
|
||||
'number_type_custom' => 'max:50|checkNumberTypeCustom',
|
||||
'split_link_id' => 'require|integer|gt:0|checkSplitLink',
|
||||
'ticket_name' => 'require|max:100',
|
||||
'numbers' => 'require|checkNumbers',
|
||||
'number' => 'require|max:50',
|
||||
'status' => 'require|checkStatus',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'number_type.require' => '请选择号码类型',
|
||||
'split_link_id.require' => '请选择分流链接',
|
||||
'ticket_name.require' => '请填写工单名称',
|
||||
'ticket_name.max' => '工单名称不能超过100个字符',
|
||||
'numbers.require' => '请填写号码',
|
||||
'number.require' => '请填写号码',
|
||||
'number.max' => '号码不能超过50个字符',
|
||||
'status.require' => '请选择状态',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['number_type', 'number_type_custom', 'split_link_id', 'ticket_name', 'numbers', 'status'],
|
||||
'edit' => ['number_type', 'number_type_custom', 'split_link_id', 'ticket_name', 'number', 'status'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkNumberType($value)
|
||||
{
|
||||
$key = (string) $value;
|
||||
$list = (new NumberModel())->getNumberTypeList();
|
||||
return isset($list[$key]) ? true : '号码类型无效';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param mixed $rule
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkNumberTypeCustom($value, $rule, array $data = [])
|
||||
{
|
||||
if (($data['number_type'] ?? '') !== 'custom') {
|
||||
return true;
|
||||
}
|
||||
if (trim((string) $value) === '') {
|
||||
return '请填写自定义号码类型';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param mixed $rule
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkSplitLink($value, $rule, array $data = [])
|
||||
{
|
||||
$linkId = (int) $value;
|
||||
$link = Link::get($linkId);
|
||||
if (!$link) {
|
||||
return '所选分流链接不存在';
|
||||
}
|
||||
if (($link['status'] ?? '') !== 'normal') {
|
||||
return '所选分流链接已停用';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkNumbers($value)
|
||||
{
|
||||
$list = NumberModel::parseNumbersText((string) $value);
|
||||
return $list !== [] ? true : '请至少填写一个有效号码(每行一个)';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkStatus($value)
|
||||
{
|
||||
$key = (string) $value;
|
||||
$list = (new NumberModel())->getStatusList();
|
||||
return isset($list[$key]) ? true : '状态无效';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
-- 号码表:排序权重(可选,跳转轮转按 id 顺序;随机打乱通过新号码随机插入顺序实现)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `fa_split_number`
|
||||
ADD COLUMN `weigh` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序权重(越大越靠前)' AFTER `status`;
|
||||
|
||||
UPDATE `fa_split_number` SET `weigh` = `id` WHERE `weigh` = 0;
|
||||
@@ -0,0 +1,89 @@
|
||||
-- 分流管理 - 工单管理表与菜单权限
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS `fa_split_ticket`;
|
||||
CREATE TABLE `fa_split_ticket` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
|
||||
`ticket_type` varchar(32) NOT NULL DEFAULT '' COMMENT '工单类型',
|
||||
`ticket_name` varchar(100) NOT NULL DEFAULT '' COMMENT '工单名称',
|
||||
`ticket_url` varchar(1000) NOT NULL DEFAULT '' COMMENT '工单链接',
|
||||
`ticket_total` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '工单总量',
|
||||
`split_link_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '关联分流链接ID',
|
||||
`number_type` varchar(32) NOT NULL DEFAULT '' COMMENT '号码类型',
|
||||
`number_type_custom` varchar(50) NOT NULL DEFAULT '' COMMENT '自定义号码类型',
|
||||
`start_time` bigint(16) DEFAULT NULL COMMENT '开始时间',
|
||||
`end_time` bigint(16) DEFAULT NULL COMMENT '到期时间',
|
||||
`order_limit` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单号上限',
|
||||
`assign_ratio` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '下号比率',
|
||||
`account` varchar(50) NOT NULL DEFAULT '' COMMENT '工单账号',
|
||||
`password` varchar(50) NOT NULL DEFAULT '' COMMENT '工单密码',
|
||||
`status` enum('normal','hidden') NOT NULL DEFAULT 'normal' COMMENT '状态:normal=正常,hidden=停用',
|
||||
`complete_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '完成数量(同步)',
|
||||
`inbound_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '进线人数(同步)',
|
||||
`speed_per_hour` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '当前速度:每小时进线(同步)',
|
||||
`number_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '号码数量含离线封号(同步)',
|
||||
`number_offline_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '离线号码数(同步)',
|
||||
`number_banned_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '封号号码数(同步)',
|
||||
`online_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '在线人数(同步)',
|
||||
`sync_status` enum('success','error','pending') NOT NULL DEFAULT 'pending' COMMENT '同步状态',
|
||||
`sync_time` bigint(16) DEFAULT NULL COMMENT '最近同步时间',
|
||||
`sync_message` varchar(255) NOT NULL DEFAULT '' COMMENT '同步失败摘要',
|
||||
`createtime` bigint(16) DEFAULT NULL COMMENT '创建时间',
|
||||
`updatetime` bigint(16) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `admin_id` (`admin_id`),
|
||||
KEY `split_link_id` (`split_link_id`),
|
||||
KEY `ticket_type` (`ticket_type`),
|
||||
KEY `start_time` (`start_time`),
|
||||
KEY `end_time` (`end_time`),
|
||||
KEY `status` (`status`),
|
||||
KEY `sync_status` (`sync_status`),
|
||||
KEY `sync_time` (`sync_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='分流工单表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- 菜单:工单管理(父级 split)
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', r.id, 'split.ticket', '工单管理', 'fa fa-ticket', '', '', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` r
|
||||
WHERE r.name = 'split' AND r.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.ticket' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.ticket/index', '查看', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.ticket' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.ticket/index' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.ticket/add', '添加', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.ticket' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.ticket/add' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.ticket/edit', '编辑', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.ticket' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.ticket/edit' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.ticket/del', '删除', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.ticket' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.ticket/del' LIMIT 1)
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.ticket/multi', '批量更新', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.ticket' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.ticket/multi' LIMIT 1)
|
||||
LIMIT 1;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 工单表补充 status 字段(列表开关依赖此列)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `fa_split_ticket`
|
||||
ADD COLUMN `status` enum('normal','hidden') NOT NULL DEFAULT 'normal' COMMENT '状态:normal=正常,hidden=停用' AFTER `password`,
|
||||
ADD KEY `status` (`status`);
|
||||
@@ -0,0 +1,28 @@
|
||||
-- 工单表:统计字段 + ticket_total 整数化(供列表展示与后续同步接口写入)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- 1. 同步统计字段(若已存在则跳过需手工处理)
|
||||
ALTER TABLE `fa_split_ticket`
|
||||
ADD COLUMN `complete_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '完成数量(同步)' AFTER `status`,
|
||||
ADD COLUMN `inbound_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '进线人数(同步)' AFTER `complete_count`,
|
||||
ADD COLUMN `speed_per_hour` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '当前速度:每小时进线(同步)' AFTER `inbound_count`,
|
||||
ADD COLUMN `number_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '号码数量含离线封号(同步)' AFTER `speed_per_hour`,
|
||||
ADD COLUMN `number_offline_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '离线号码数(同步)' AFTER `number_count`,
|
||||
ADD COLUMN `number_banned_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '封号号码数(同步)' AFTER `number_offline_count`,
|
||||
ADD COLUMN `online_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '在线人数(同步)' AFTER `number_banned_count`,
|
||||
ADD COLUMN `sync_status` enum('success','error','pending') NOT NULL DEFAULT 'pending' COMMENT '同步状态' AFTER `online_count`,
|
||||
ADD COLUMN `sync_time` bigint(16) DEFAULT NULL COMMENT '最近同步时间' AFTER `sync_status`,
|
||||
ADD COLUMN `sync_message` varchar(255) NOT NULL DEFAULT '' COMMENT '同步失败摘要' AFTER `sync_time`;
|
||||
|
||||
ALTER TABLE `fa_split_ticket`
|
||||
ADD KEY `sync_status` (`sync_status`),
|
||||
ADD KEY `sync_time` (`sync_time`);
|
||||
|
||||
-- 2. ticket_total:varchar -> int unsigned(非数字旧值变为 0)
|
||||
UPDATE `fa_split_ticket` SET `ticket_total` = '0' WHERE `ticket_total` REGEXP '[^0-9]' OR `ticket_total` = '';
|
||||
ALTER TABLE `fa_split_ticket`
|
||||
MODIFY COLUMN `ticket_total` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '工单总量';
|
||||
|
||||
-- 3. 号码表组合索引:加速按工单汇总 visit_count
|
||||
ALTER TABLE `fa_split_number`
|
||||
ADD KEY `idx_ticket_agg` (`admin_id`, `split_link_id`, `ticket_name`(50));
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 工单云控定时同步:扩展字段 + 手动同步权限节点
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `fa_split_ticket`
|
||||
ADD COLUMN `sync_fail_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '连续同步失败次数' AFTER `sync_message`,
|
||||
ADD COLUMN `speed_snapshot_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '速度计算基准完成数' AFTER `sync_fail_count`,
|
||||
ADD COLUMN `speed_snapshot_time` bigint(16) DEFAULT NULL COMMENT '速度计算基准时间' AFTER `speed_snapshot_count`;
|
||||
|
||||
ALTER TABLE `fa_split_number`
|
||||
ADD COLUMN `platform_status` enum('online','offline','unknown') NOT NULL DEFAULT 'unknown' COMMENT '云控平台在线状态' AFTER `manual_manage`,
|
||||
ADD COLUMN `last_sync_visit_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '上次同步时访问次数' AFTER `platform_status`,
|
||||
ADD COLUMN `last_sync_inbound_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '上次同步时进线人数' AFTER `last_sync_visit_count`,
|
||||
ADD COLUMN `no_inbound_click_streak` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '进线无增长期间连续点击增量' AFTER `last_sync_inbound_count`;
|
||||
|
||||
INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`)
|
||||
SELECT 'file', m.id, 'split.ticket/sync', '同步状态', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal'
|
||||
FROM `fa_auth_rule` m
|
||||
WHERE m.name = 'split.ticket' AND m.ismenu = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM `fa_auth_rule` WHERE `name` = 'split.ticket/sync' LIMIT 1)
|
||||
LIMIT 1;
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate\split;
|
||||
|
||||
use app\admin\model\split\Link;
|
||||
use app\admin\model\split\Ticket as TicketModel;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 分流工单验证器(部署源文件,由 deploy_split_ticket.php 复制到 application)
|
||||
*/
|
||||
class Ticket extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'ticket_type' => 'require|checkTicketType',
|
||||
'ticket_name' => 'require|max:100',
|
||||
'ticket_url' => 'require|max:1000',
|
||||
'ticket_total' => 'require|integer|egt:0',
|
||||
'split_link_id' => 'require|integer|gt:0|checkSplitLink',
|
||||
'number_type' => 'require|checkNumberType',
|
||||
'number_type_custom' => 'max:50|checkNumberTypeCustom',
|
||||
'start_time' => 'require|checkStartTime',
|
||||
'end_time' => 'require|checkEndTime',
|
||||
'order_limit' => 'require|integer|egt:0',
|
||||
'assign_ratio' => 'require|integer|egt:0',
|
||||
'account' => 'max:50',
|
||||
'password' => 'max:50',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'ticket_type.require' => '请选择工单类型',
|
||||
'ticket_name.require' => '请填写工单名称',
|
||||
'ticket_name.max' => '工单名称不能超过100个字符',
|
||||
'ticket_url.require' => '请填写工单链接',
|
||||
'ticket_url.max' => '工单链接不能超过1000个字符',
|
||||
'ticket_total.require' => '请填写工单总量',
|
||||
'ticket_total.integer' => '工单总量必须为整数',
|
||||
'ticket_total.egt' => '工单总量不能小于0',
|
||||
'split_link_id.require' => '请选择分流链接',
|
||||
'number_type.require' => '请选择号码类型',
|
||||
'start_time.require' => '请选择开始时间',
|
||||
'end_time.require' => '请选择到期时间',
|
||||
'order_limit.require' => '请填写单号上限',
|
||||
'order_limit.egt' => '单号上限不能小于0',
|
||||
'assign_ratio.require' => '请填写下号比率',
|
||||
'assign_ratio.egt' => '下号比率不能小于0',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'add' => [
|
||||
'ticket_type', 'ticket_name', 'ticket_url', 'ticket_total', 'split_link_id',
|
||||
'number_type', 'number_type_custom', 'start_time', 'end_time',
|
||||
'order_limit', 'assign_ratio', 'account', 'password',
|
||||
],
|
||||
'edit' => [
|
||||
'ticket_type', 'ticket_name', 'ticket_url', 'ticket_total', 'split_link_id',
|
||||
'number_type', 'number_type_custom', 'start_time', 'end_time',
|
||||
'order_limit', 'assign_ratio', 'account', 'password',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkTicketType($value)
|
||||
{
|
||||
$key = (string) $value;
|
||||
$list = (new TicketModel())->getTicketTypeList();
|
||||
return isset($list[$key]) ? true : '工单类型无效';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkNumberType($value)
|
||||
{
|
||||
$key = (string) $value;
|
||||
$list = (new TicketModel())->getNumberTypeList();
|
||||
return isset($list[$key]) ? true : '号码类型无效';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param mixed $rule
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkNumberTypeCustom($value, $rule, array $data = [])
|
||||
{
|
||||
if (($data['number_type'] ?? '') !== 'custom') {
|
||||
return true;
|
||||
}
|
||||
if (trim((string) $value) === '') {
|
||||
return '请填写自定义号码类型';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param mixed $rule
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkSplitLink($value, $rule, array $data = [])
|
||||
{
|
||||
$linkId = (int) $value;
|
||||
$link = Link::get($linkId);
|
||||
if (!$link) {
|
||||
return '所选分流链接不存在';
|
||||
}
|
||||
if (($link['status'] ?? '') !== 'normal') {
|
||||
return '所选分流链接已停用';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkStartTime($value)
|
||||
{
|
||||
$ts = is_numeric($value) ? (int) $value : strtotime((string) $value);
|
||||
return ($ts !== false && $ts > 0) ? true : '开始时间格式无效';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param mixed $rule
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkEndTime($value, $rule, array $data = [])
|
||||
{
|
||||
$endTs = is_numeric($value) ? (int) $value : strtotime((string) $value);
|
||||
if ($endTs === false || $endTs <= 0) {
|
||||
return '到期时间格式无效';
|
||||
}
|
||||
$startRaw = $data['start_time'] ?? '';
|
||||
$startTs = is_numeric($startRaw) ? (int) $startRaw : strtotime((string) $startRaw);
|
||||
if ($startTs === false || $startTs <= 0) {
|
||||
return true;
|
||||
}
|
||||
if ($endTs <= $startTs) {
|
||||
return '到期时间必须晚于开始时间';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user