Merge branch 'feature/mzl_dev'

# Conflicts:
#	app/command/TbCommand.php
#	app/common/utils/email/Emails.php
#	app/controller/IndexController.php
#	app/functions.php
This commit is contained in:
mzl
2025-08-04 18:11:44 +08:00
parent be212317eb
commit a315ad33f1
8 changed files with 408 additions and 5 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ class TbCommand extends Command
// }
$m = new Emails();
$m->process();
$m->fenpai();
return self::SUCCESS;
}
+1 -1
View File
@@ -93,7 +93,7 @@ class AuthLogic
->orWhere('email', $email);
})
->first();
$user->isLogin = true;
if (!$user) {
// 获取编号
$userSn = $this->getAvailableUserSn();
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class EmailParsedResult extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'email_parsed_result';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
// protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'email_id', 'date', 'email', 'name', 'tel', 'company', 'country', 'form_url', 'message', 'ip', 'iplocation', 'ilang', 'mailto', 'navigator_language', 'navigator_platform', 'screen_width', 'screen_height', 'ucountry', 'status', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+178
View File
@@ -0,0 +1,178 @@
<?php
namespace app\common\utils;
// UID旋转器(依次循环获取)
class UidRotator
{
private static $uids = [];
private static $index = 0;
private static $initialized = false;
/**
* 初始化或重置分配器
*
* @param array $uids UID数组
* @param int $startIndex 起始索引位置
*/
public static function initialize(array $uids = [], int $startIndex = 0)
{
if (!empty($uids)) {
self::$uids = $uids;
}
self::$index = $startIndex % max(1, count(self::$uids));
self::$initialized = true;
}
/**
* 动态设置新的UID数组
*
* @param array $newUids 新的UID数组
* @param bool $resetIndex 是否重置索引
*/
public static function setUids(array $newUids, bool $resetIndex = true)
{
self::$uids = $newUids;
if ($resetIndex) {
self::$index = 0;
} else {
// 确保索引不越界
self::$index = min(self::$index, max(0, count(self::$uids) - 1));
}
self::$initialized = true;
}
/**
* 添加新的UID到数组
*
* @param int|array $newUids 单个UID或UID数组
*/
public static function addUids($newUids)
{
if (!self::$initialized) {
self::initialize();
}
if (is_array($newUids)) {
self::$uids = array_merge(self::$uids, $newUids);
} else {
self::$uids[] = $newUids;
}
}
/**
* 获取下一个分配的UID
*
* @return int|null 返回UID,没有可用UID时返回null
*/
public static function next()
{
if (!self::$initialized) {
self::initialize();
}
if (empty(self::$uids)) {
return null;
}
$uid = self::$uids[self::$index];
// 移动到下一个位置(循环)
self::$index = (self::$index + 1) % count(self::$uids);
return $uid;
}
/**
* 获取当前指针位置
*/
public static function getPosition(): int
{
return self::$index;
}
/**
* 设置当前指针位置
*
* @param int $position 新的位置索引
*/
public static function setPosition(int $position)
{
if (!self::$initialized) {
self::initialize();
}
if (!empty(self::$uids)) {
self::$index = $position % count(self::$uids);
}
}
/**
* 获取当前UID数组
*/
public static function getUids(): array
{
return self::$uids;
}
/**
* 重置指针到起始位置
*/
public static function reset()
{
self::$index = 0;
}
/**
* 随机打乱当前UID数组
*/
public static function shuffle()
{
shuffle(self::$uids);
self::$index = 0;
}
}
// // 使用方法:
// // 初始化分配器
// UidRotator::initialize([64, 63, 83, 93]);
// echo UidRotator::next(); // 64
// echo UidRotator::next(); // 63
// // 动态添加新UID
// UidRotator::addUids(108);
// UidRotator::addUids([118, 131]);
// echo UidRotator::next(); // 83
// // 设置新数组并重置索引
// UidRotator::setUids([150, 143, 156]);
// echo UidRotator::next(); // 150
// echo UidRotator::next(); // 143
// // 设置新数组但保持当前索引位置
// UidRotator::setUids([157, 158, 159, 160], false);
// echo UidRotator::next(); // 159 (因为之前索引是2)
// // 从特定位置开始
// UidRotator::initialize([64, 63, 83, 93], 2);
// echo UidRotator::next(); // 83
// // 获取当前状态
// $currentPosition = UidRotator::getPosition();
// $allUids = UidRotator::getUids();
// // 设置新位置
// UidRotator::setPosition(1);
// echo UidRotator::next(); // 63
// // 随机打乱数组
// UidRotator::shuffle();
// print_r(UidRotator::getUids()); // 随机顺序的数组
+150
View File
@@ -2,7 +2,15 @@
namespace app\common\utils\email;
use app\common\model\Country;
use app\common\model\CrmCustomer;
use support\Db;
use support\Log;
use support\exception\BusinessException;
use app\common\model\EmailParsedResult;
use app\common\model\InquiryChannel;
use app\common\model\InquiryLog;
use app\common\model\User;
class Emails
{
@@ -407,4 +415,146 @@ class Emails
return false;
}
}
public function fenpai()
{
try {
// $uids = [
// 64, 63, 83, 93, 108, 118, 131, 132, 145, 150, 143, 156, 157, 158
// ];
// 配置参数
$batchSize = 100; // 每批处理量
$delaySeconds = 1; // 每封邮件间隔秒数
$throttleAfter = 50; // 每发送多少封后增加休息
$throttleSeconds = 5; // 批量休息秒数
// 固定录单人
$enterUserId = 75;
$enterUser = User::where('id', $enterUserId)->first();
// 固定渠道
$channel = 8;
$channelInfo = InquiryChannel::where('id', $channel)->first();
// 初始化 UID 轮询器
\app\common\utils\UidRotator::initialize([64, 63, 83, 93, 108, 118, 131, 132, 145, 150, 143, 156, 157, 158]);
EmailParsedResult::where('status', 0)
->chunkById($batchSize, function ($records) use ($delaySeconds, $throttleAfter, $throttleSeconds, $enterUser, $channelInfo) {
$count = 0;
foreach ($records as $record) {
try {
// Mail::to($record->mailto)->send(new CustomEmail($record));
// 获取国家信息
$countryName = removeParentheses($record->iplocation);
$countryInfo = Country::where('cn_addr', $countryName)->first();
// var_dump($countryName);
// var_dump($countryInfo);
// var_dump('====== ====== ====== ====== ====== ====== ');
// 获取下一个分配的UID
$assignedUid = \app\common\utils\UidRotator::next();
// var_dump($assignedUid);
// var_dump('====== ====== ====== ====== ====== ====== ');
// 获取分配的用户信息
$recipientInfo = User::where('id', $assignedUid)->first();
// var_dump($recipientInfo);
// var_dump('====== ====== ====== ====== ====== ====== ');
// 生成录单
$log = [
'order_no' => orderNum(),
'enter_user_id' => $enterUser->id,
'recipient' => $assignedUid,
'quality' => 2,
'channel' => $channelInfo->id,
'email' => $record->email,
'tel' => $record->tel,
'whatsapp' => '',
'country' => $countryInfo->id,
'contacts' => $record->name,
'content' => $record->message,
'r_time' => time(),
't_time' => time(),
'status' => 2,
'sync_crm' => 1
];
// var_dump($log);
// var_dump('====== ====== ====== ====== ====== ====== ');
$inquiryLogResult = InquiryLog::create($log);
if (!$inquiryLogResult) {
throw new BusinessException('录单失败~');
}
// 同步客户
$crmCustomer = new CrmCustomer();
$crmCustomer->sn = $inquiryLogResult->order_no;
$crmCustomer->corp_abbr = $recipientInfo->corp_id;
$crmCustomer->user_id = $assignedUid;
$crmCustomer->type = 1;
$crmCustomer->channel_id = $inquiryLogResult->channel;
$crmCustomer->country_id = $inquiryLogResult->country;
$crmCustomer->magnitude = $inquiryLogResult->quality;
$crmCustomer->email = $inquiryLogResult->email;
$crmCustomer->phone = $inquiryLogResult->tel;
$crmCustomer->WhatsApp = $inquiryLogResult->whatsapp;
$crmCustomer->contacts = $inquiryLogResult->contacts;
$crmCustomer->desc_collect = $inquiryLogResult->content;
$crmCustomer->enter_user_id = $inquiryLogResult->enter_user_id;
// var_dump($crmCustomer);
// var_dump('====== ====== ====== ====== ====== ====== ');
if (!$crmCustomer->save()) {
throw new BusinessException('同步失败~');
}
// 邮件模板
$data = [
'order_no' => $inquiryLogResult->order_no,
'enter_user_id' => $enterUser->name,
'recipient' => $recipientInfo->name,
'quality' => (new \app\inquiry\service\InquiryLogService())->qualityName($inquiryLogResult->quality),
'channel' => $channelInfo->name,
'email' => $inquiryLogResult->email && $inquiryLogResult->email != '' ? $inquiryLogResult->email : ' - ',
'tel' => $inquiryLogResult->tel && $inquiryLogResult->tel != '' ? $inquiryLogResult->tel : ' - ',
'whatsapp' => $inquiryLogResult->whatsapp && $inquiryLogResult->whatsapp != '' ? $inquiryLogResult->whatsapp : ' - ',
'country' => $countryName,
'contacts' => $inquiryLogResult->contacts && $inquiryLogResult->contacts != '' ? $inquiryLogResult->contacts : ' - ',
'content' => $inquiryLogResult->content && $inquiryLogResult->content != '' ? $inquiryLogResult->content : ' - ',
'r_time' => $inquiryLogResult->r_time ? date('Y-m-d H:i:s', $inquiryLogResult->r_time) : ' - ',
't_time' => $inquiryLogResult->t_time ? date('Y-m-d H:i:s', $inquiryLogResult->t_time) : ' - '
];
\app\common\utils\email\MailSend::send($recipientInfo->email, '【XYH】新的客户线索', replaceTpl($data));
// var_dump($data);
// var_dump('====== ====== ====== ====== ====== ====== ');
// 更新状态
$record->update(['status' => 1]);
// 每封邮件后延迟
if ($delaySeconds > 0) {
sleep($delaySeconds);
}
// 每发送一定数量后额外休息
if (++$count % $throttleAfter === 0) {
sleep($throttleSeconds);
Log::info("已发送 {$count} 封邮件,休息 {$throttleSeconds} 秒");
}
} catch (\Exception $e) {
Log::error("邮件发送失败 ID:{$record->id} - " . $e->getMessage());
// 更新状态
$record->update(['status' => 3]);
// 失败后也加入延迟,避免快速连续失败
sleep($delaySeconds);
}
}
});
} catch (\Throwable $th) {
flg(dpr($th));
return false;
}
}
}
+2 -2
View File
@@ -80,7 +80,7 @@ class IndexController
// ],
'model' => [
'module' => 'common',
'name' => 'inquiry_channel'
'name' => 'email_parsed_result'
],
// 'service' => [
// 'module' => 'inquiry',
@@ -94,7 +94,7 @@ class IndexController
// 'module' => 'inquiry',
// 'name' => 'country'
// ],
'table' => 'inquiry_channel'
'table' => 'email_parsed_result'
];
$result = (new Generate($options))->doGenerate();
+26
View File
@@ -194,4 +194,30 @@ if (!function_exists('InquiryFormat')) {
$content .= '内容:'.$data['content'];
return $content;
}
}
/**
* 移除字符串中的括号及其内部内容(仅移除第一个匹配项)
*
* @param string $str 要处理的字符串
* @return string 处理后的字符串
*/
if (!function_exists('removeParentheses')) {
function removeParentheses(string $str): string
{
// 匹配模式:半角括号或全角括号及其内部内容
$pattern = '/
(?: # 非捕获分组开始
\( # 匹配半角左括号
[^)]* # 匹配除半角右括号外的任意字符
\) # 匹配半角右括号
)|(?: # 或
\( # 匹配全角左括号
[^)]* # 匹配除全角右括号外的任意字符
\) # 匹配全角右括号
) # 非捕获分组结束
/xu'; // x: 忽略空白 u: Unicode模式
return trim(preg_replace($pattern, '', $str, 1));
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ class InquiryToCrm implements Consumer
$crmCustomer->type = 1;
$crmCustomer->channel_id = $info->channel;
$crmCustomer->country_id = $info->country;
$crmCustomer->magnitude = $info->quality - 1;
$crmCustomer->magnitude = $info->quality;
$crmCustomer->email = $info->email;
$crmCustomer->phone = $info->tel;
$crmCustomer->WhatsApp = $info->whatsapp;