作者 赵彬吉

inquiry

<?php
namespace App\Console\Commands;
use App\Exceptions\InquiryFilterException;
use App\Services\SyncSubmitTaskService;
use Illuminate\Console\Command;
use App\Models\SyncSubmitTask\SyncSubmitTask as SyncSubmitTaskModel;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;
/**
*
* Class SyncSubmitTask
* @package App\Console\Commands
* @author zbj
* @date 2023/11/28
*/
class SyncSubmitTask extends Command
{
protected $signature = 'sync_submit_task';
protected $description = '询盘、访问异步任务';
public function handle()
{
while (true) {
$task_id = $this->getTaskId();
if (empty($task_id)) {
sleep(5);
continue;
}
$this->output('任务' . $task_id . '开始');
$task_info = SyncSubmitTaskModel::find($task_id);
if (empty($task_info) || $task_info->status) {
$this->output('任务不存在或者已执行');
continue;
}
try {
SyncSubmitTaskService::handler($task_info);
$task_info->status = 1;
$task_info->save();
$this->output('任务完成');
} catch (InquiryFilterException $e){
$task_info->status = 1;
$task_info->is_filtered = 1;
$task_info->remark = $e->getMessage();
$task_info->save();
$this->output('任务完成');
} catch (\Exception $e) {
$task_info->retry = $task_info->retry + 1;
if ($task_info->retry >= 3) {
$task_info->status = 2;
$task_info->remark = Str::substr($e->getMessage(), 0, 200);
} else {
Redis::lpush('sync_submit_task', $task_id);
}
$task_info->save();
$this->output('任务失败:' . $e->getMessage());
}
}
}
public function getTaskId(){
$task_id = Redis::rpop('sync_submit_task');
if (empty($task_id)) {
$ids = SyncSubmitTaskModel::where('status', 0)->pluck('id');
foreach($ids as $id){
Redis::lpush('sync_submit_task', $id);
}
$task_id = Redis::rpop('sync_submit_task');
}
return $task_id;
}
/**
* 输出处理日志
*/
public function output($message): bool
{
echo date('Y-m-d H:i:s') . ' | ' . $message . PHP_EOL;
return true;
}
}
... ...
<?php
namespace App\Exceptions;
use Exception;
/**
* @notes: 询盘过滤
* Class InquiryFilterException
* @package App\Exceptions
*/
class InquiryFilterException extends Exception
{
}
... ...
... ... @@ -124,4 +124,36 @@ class FormGlobalsoApi
}
return $res;
}
/**
* 提交询盘
* @param $ip
* @param $referer
* @param $submit_at
* @param $data
* @return array|false|mixed
* @author zbj
* @date 2024/1/20
*/
public function submitInquiry($ip, $referer, $submit_at, $data)
{
$api_url = $this->url . '/api/external-interface/add/fa043f9cbec6b38f';
$data['ip'] = $ip;
$data['token'] = md5($referer . $data['name'] . $ip . date("Y-m-d"));
$data['refer'] = $referer;
$data['submit_time'] = date('Y-m-d H:i:s', strtotime($submit_at));
$data['source'] = 1; //固定
try {
$res = HttpUtils::post($api_url, $data);
$res = Arr::s2a($res);
} catch (\Exception | GuzzleException $e) {
errorLog('提交询盘信息失败', $data, $e);
return false;
}
return $res;
}
}
... ...
... ... @@ -36,6 +36,7 @@ use App\Models\Task\Task;
use App\Services\ProjectServer;
use Hashids\Hashids;
use App\Models\User\User as UserModel;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Schema;
... ... @@ -337,6 +338,8 @@ class ProjectLogic extends BaseLogic
$model->edit($config,['project_id'=>$config['project_id']]);
}
Cache::forget(InquiryFilterConfig::cacheKey($config['project_id']));
return $this->success();
}
... ...
... ... @@ -44,12 +44,6 @@ class InquiryForm extends Base
return $map;
}
public function getFieldAttribute($value)
{
return json_decode($value, true);
}
/**
* @author zbj
* @date 2023/12/5
... ... @@ -63,4 +57,51 @@ class InquiryForm extends Base
}
return $field;
}
/**
* 根据提交数据的key获取表单id
* @param $data
* @return mixed
* @author zbj
* @date 2023/12/4
*/
public static function getFromId($data){
unset($data['globalso-domain_host_url']);
unset($data['globalso-domain']);
unset($data['globalso-edition']);
unset($data['globalso-date']);
ksort($data);
$field = array_keys($data);
$sign = md5(json_encode($field));
$model = self::where('sign', $sign)->first();
if(!$model){
$model = new self();
$model->sign = $sign;
$model->field = $field;
if(!empty($data['name']) && !empty($data['email']) && !empty($data['message'])){
$has_file = false;
foreach ($data as $v){
if (is_array($v)){
$has_file = true;
break;
}
}
!$has_file && $model->is_default = 1;
}
$model->save();
}
return $model->id;
}
public function setFieldAttribute($value)
{
$this->attributes['field'] = json_encode($value);
}
public function getFieldAttribute($value)
{
return json_decode($value, true);
}
}
... ...
... ... @@ -2,6 +2,7 @@
namespace App\Models\Inquiry;
use App\Helper\FormGlobalsoApi;
use App\Models\Base;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
... ... @@ -23,6 +24,71 @@ class InquiryFormData extends Base
protected $connection = "custom_mysql";
protected $table = 'gl_inquiry_form_data';
/**
* 根据提交数据的key获取表单id
* @param $form_id
* @param $domain
* @param $ip
* @param $country
* @param $referer
* @param $user_agent
* @param $submit_at
* @param $data
* @return mixed
* @author zbj
* @date 2023/12/4
*/
public static function saveData($form_id, $domain, $ip, $country, $referer, $user_agent, $submit_at, $data){
//数据标识
ksort($data);
$sign = md5(json_encode($data));
//5分钟内是否有重复数据
$is_exist = self::where('sign', $sign)->where('created_at', '>', date('Y-m-d H:i:s', strtotime('-5 minute')))->first();
if($is_exist){
return true;
}
$model = new self();
$model->form_id = $form_id;
$model->domain = $domain;
$model->ip = $ip;
$model->country = $country;
$model->referer = $referer;
$model->user_agent = $user_agent;
$model->submit_at = $submit_at;
$model->data = $data;
$model->sign = $sign;
$model->save();
if(!empty($data['name']) && !empty($data['email']) && !empty($data['message'])){
unset($data['globalso-domain_host_url']);
unset($data['globalso-domain']);
unset($data['globalso-edition']);
unset($data['globalso-date']);
//推送邮件发送
$has_file = false;
foreach ($data as $k => $v){
if(is_array($v)){
$has_file = true;
break;
}
//其他字段补充到message里
if(!in_array($k, ['name', 'email', 'message', 'phone', 'ip', 'date', 'cname', 'domain', 'edition', 'domain_host_url'])){
$data['message'].= "<br/>" . $k .': ' . $v;
}
}
!$has_file && (new FormGlobalsoApi())->submitInquiry($ip, $referer, $submit_at, $data);
}
return true;
}
public function setDataAttribute($value)
{
$this->attributes['data'] = json_encode($value);
}
public function getDataAttribute($value)
{
return json_decode($value, true);
... ...
... ... @@ -3,6 +3,7 @@
namespace App\Models\Project;
use App\Models\Base;
use Illuminate\Support\Facades\Cache;
/**
* 询盘过滤配置
... ... @@ -24,4 +25,30 @@ class InquiryFilterConfig extends Base
'filter_mobiles' => 'array',
'filter_names' => 'array',
];
/**
* @param $project_id
* @return string
* @author zbj
* @date 2024/1/20
*/
public static function cacheKey($project_id): string
{
return 'project_inquiry_filter_config_info' . $project_id;
}
/**
* @param $project_id
* @return mixed
* @author zbj
* @date 2024/1/20
*/
public static function getCacheInfoByProjectId($project_id){
$info = Cache::get(self::cacheKey($project_id));
if (!$info) {
$info = self::where('project_id', $project_id)->first();
Cache::put(self::cacheKey($project_id), $info, 2 * 3600);
}
return $info;
}
}
... ...
<?php
namespace App\Models\SyncSubmitTask;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
/**
* @method static where(string $string, mixed $ip)
* @method static create(array $data)
*/
class SyncSubmitTask extends Model
{
const TYPE_INQUIRY = 'inquiry';
const TYPE_VISIT = 'visit';
const TRAFFIC_DEFAULT = 0;
const TRAFFIC_TRUE = 1;
//设置关联表名
/**
* @var mixed
*/
protected $table = 'gl_sync_submit_task';
protected $casts = [
'data' => 'array',
];
}
... ...
... ... @@ -4,6 +4,8 @@ namespace App\Models\Visit;
use App\Models\Base;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
/**
* Class Visit
... ... @@ -19,6 +21,7 @@ class Visit extends Base
//连接数据库
protected $connection = 'custom_mysql';
protected $appends = ['device_text'];
protected $fillable = ['ip','device_port','country','city','url','referrer_url','depth','domain','updated_date', 'created_at'];
const DEVICE_PC = 1;
const DEVICE_MOBILE = 2;
... ... @@ -47,4 +50,52 @@ class Visit extends Base
return self::deviceMap()[$this->device_port] ?? '';
}
/**
* 访问写入
*/
public static function saveData($data)
{
//判断IP当天是否有一条数据
$visit = Visit::where("ip",$data['ip'])->where("created_at",">=",Carbon::now()->today()->startOfDay())
->where("created_at","<=",Carbon::now()->today()->endOfDay())
->first();
DB::connection('custom_mysql')->beginTransaction();
if (!empty($visit) && $visit->count() >= 1){
//当天已存在IP访问记录
try{
$data["customer_visit_id"] = $visit->id;
VisitItem::create($data);
Visit::where("id",$visit->id)->update(['depth' => $visit->depth + 1]);
DB::connection('custom_mysql')->commit();
}catch (\Exception $e){
DB::connection('custom_mysql')->rollBack();
throw new \Exception($e->getMessage());
}
}else{
//第一次访问
try{
$id = Visit::create($data)->id;
$data["customer_visit_id"] = $id;
VisitItem::create($data);
DB::connection('custom_mysql')->commit();
}catch (\Exception $e){
DB::connection('custom_mysql')->rollBack();
throw new \Exception($e->getMessage());
}
}
return true;
}
public static function isInquiry($ip){
$visit = Visit::where("ip",$ip)->where("created_at",">=",Carbon::now()->today()->startOfDay())
->where("created_at","<=",Carbon::now()->today()->endOfDay())
->first();
if($visit){
$visit->is_inquiry = 1;
$visit->save();
}
return true;
}
}
... ...
... ... @@ -16,4 +16,7 @@ class VisitItem extends Base
protected $table = 'gl_customer_visit_item';
//连接数据库
protected $connection = 'custom_mysql';
protected $fillable = ['ip','customer_visit_id','device_port','country','city','url','referrer_url','domain','updated_date','created_at'];
}
... ...
<?php
namespace App\Services;
use App\Exceptions\InquiryFilterException;
use App\Models\Inquiry\InquiryForm;
use App\Models\Inquiry\InquiryFormData;
use App\Models\Project\InquiryFilterConfig;
use App\Models\Project\Project;
use App\Models\SyncSubmitTask\SyncSubmitTask;
use App\Models\Visit\Visit;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;
use function Symfony\Component\String\s;
/**
* Class SyncSubmitTaskService
* @package App\Services
* @author zbj
* @date 2023/12/4
*/
class SyncSubmitTaskService
{
/**
* @param $task
* @return mixed
* @throws InquiryFilterException
* @author zbj
* @date 2023/11/28
*/
public static function handler($task)
{
$data = $task['data'];
$checkIpCountry = self::checkIpCountry($data['domain'], $data['ip'], $task['type']);
$data['ip'] = $checkIpCountry['ip'];
$data['country'] = $checkIpCountry['country'];
$data['submit_at'] = $task['created_at'];
$project = $checkIpCountry['project'];
$data['project_id'] = $project['id'];
//特殊处理
if($project['id'] == 455 && !empty($data['email']) && $data['email'] == 'alb@marketingtu.org'){
return false;
}
ProjectServer::useProject($project['id']);
$action = $task['type'];
$handler = new self();
return $handler->$action($data);
}
/**
* 询盘
* @param $data
* @return bool
* @throws InquiryFilterException
* @author zbj
* @date 2023/12/4
*/
public function inquiry($data)
{
$this->inquiryFilter($data['project_id'], $data);
$form_id = InquiryForm::getFromId($data['data']);
InquiryFormData::saveData($form_id, $data['domain'], $data['ip'], $data['country'], $data['referer'], $data['user_agent'], $data['submit_at'], $data['data']);
//转化询盘
Visit::isInquiry($data['ip']);
return true;
}
/**
* 访问
* @param $data
* @return bool
* @throws \Exception
* @author zbj
* @date 2023/12/4
*/
public function visit($data)
{
$visit_data = $data['data'];
$visit_data['referrer_url'] = $data['data']['referrer_url']??'';
$visit_data['device_port'] = $data['data']['device_port']??'';
$visit_data['url'] = $data['data']['url']??'';
$visit_data['domain'] = $data['domain']??'';
$visit_data['ip'] = $data['ip'];
$visit_data['country'] = $data['country'];
$visit_data['updated_date'] = $data['submit_at']->toDateString();
$visit_data['created_at'] = $data['submit_at'];
Visit::saveData($visit_data);
return true;
}
/**
* 根据ip查地区
* @param $ip
* @return string
* @author zbj
* @date 2023/11/28
*/
public static function getCountryByIp($ip){
if(!$ip){
return '';
}
$res = Http::withoutVerifying()->get('http://ip.globalso.com', ['ip' => $ip]);
if($res->status() == 200){
return $res->body();
}else{
return '';
}
}
/**
* 是否开始测试站或中国内地询盘和访问记录
* @param $domain
* @param $ip
* @param $type
* @return array
* @throws InquiryFilterException
* @author zbj
* @date 2023/11/30
*/
public static function checkIpCountry($domain, $ip, $type){
$domain = 'https://demo.globalso.site/';
$project = Project::getProjectByDomain($domain);
if(empty($project)){
throw new InquiryFilterException('项目不存在');
}
// 测试环境返回信息
if (FALSE !== strpos($domain, 'globalso.site') && !$project->is_record_test_visit) {
throw new InquiryFilterException('测试环境过滤');
}
if($ip == "127.0.0.1"){
throw new InquiryFilterException('127.0.0.1过滤');
}
$country = self::getCountryByIp($ip);
//访问记录才过滤是否国内
if ($country == "中国" && !$project->is_record_china_visit && $type == SyncSubmitTask::TYPE_VISIT){
throw new InquiryFilterException('中国内地过滤');
}
return [
'project' => $project,
'ip' => $ip,
'country' => $country
];
}
/**
* 询盘配置过滤
* @author zbj
* @date 2024/1/20
*/
public static function inquiryFilter($project_id, $data){
$config = InquiryFilterConfig::getCacheInfoByProjectId($project_id);
//是否开启过滤
if($config && $config['status']){
//是否包含全局规则(就是project_id=1的配置)
if($project_id != Project::DEMO_PROJECT_ID && $config['is_global_rule']){
self::inquiryFilter(Project::DEMO_PROJECT_ID, $data);
}
//过滤国家
if($config['filter_countries'] && in_array($data['country'], $config['filter_countries'])){
throw new InquiryFilterException( '过滤国家:' . $data['country']);
}
//过滤ip
if($config['black_ips']){
$black_ips = explode("\r\n", $config['black_ips']);
//后端获取的ip
if(in_array($data['ip'], $black_ips)){
throw new InquiryFilterException( '过滤黑名单IP:' . $data['ip']);
}
//前端获取的ip
if(!empty($data['data']['ip']) && in_array($data['data']['ip'], $black_ips)){
throw new InquiryFilterException( '过滤黑名单IP:' . $data['data']['ip']);
}
}
//过滤内容
if(!empty($data['data']['message'])) {
//过滤内容关键字
if ($config['filter_contents']){
foreach ($config['filter_contents'] as $filter_content) {
if (Str::contains($data['data']['message'], $filter_content)) {
throw new InquiryFilterException('过滤内容:' . $filter_content);
}
}
}
//是否允许包含链接
if(!$config['is_allow_link']){
if (Str::contains($data['data']['message'], ['http://', 'https://', 'www.'])) {
throw new InquiryFilterException('不允许包含链接');
}
}
}
//过滤来源
if($config['filter_referers']){
//只比较path路径
$paths = array_map(function ($v){
return parse_url(Url::to($v), PHP_URL_PATH);
},$config['filter_referers']);
//后端获取的referer
if(in_array(parse_url($data['referer'], PHP_URL_PATH), $paths)){
throw new InquiryFilterException( '过滤来源链接:' . $data['referer']);
}
//前端获取的referer
if(!empty($data['data']['globalso-domain_host_url']) && in_array(parse_url($data['data']['globalso-domain_host_url'], PHP_URL_PATH), $paths)){
throw new InquiryFilterException( '过滤来源链接:' . $data['data']['globalso-domain_host_url']);
}
}
//过滤邮箱
if($config['filter_emails'] && !empty($data['data']['email'])){
foreach ($config['filter_emails'] as $filter_email){
if($data['data']['email'] == $filter_email){
throw new InquiryFilterException( '过滤邮箱:' . $filter_email);
}
}
}
//过滤电话
if($config['filter_mobiles'] && !empty($data['data']['phone'])){
foreach ($config['filter_mobiles'] as $filter_mobile){
if($data['data']['phone'] == $filter_mobile){
throw new InquiryFilterException( '过滤电话:' . $filter_mobile);
}
}
}
//过滤姓名
if($config['filter_names'] && !empty($data['data']['name'])){
foreach ($config['filter_names'] as $filter_name){
if($data['data']['name'] == $filter_name){
throw new InquiryFilterException( '过滤姓名:' . $filter_name);
}
}
}
}
return true;
}
}
... ...