lion 6 days ago
commit 306e29867a

@ -0,0 +1,91 @@
<?php
namespace App\Console\Commands;
use App\Models\Company;
use Illuminate\Console\Command;
class RefreshCompanyMarketStatus extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'company:refresh-market-status {--company_id=} {--chunk=500}';
/**
* The console command description.
*
* @var string
*/
protected $description = '全量重算公司是否上市字段 company_market';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$companyId = $this->option('company_id');
$chunk = (int) $this->option('chunk');
$chunk = $chunk > 0 ? $chunk : 500;
$query = Company::query()->orderBy('id');
if (!empty($companyId)) {
$query->where('id', $companyId);
}
$total = (clone $query)->count();
if ($total == 0) {
return $this->info('没有需要处理的公司');
}
$this->info("开始重算 company_market共 {$total} 家公司");
$bar = $this->output->createProgressBar($total);
$bar->start();
$updatedCount = 0;
$unchangedCount = 0;
$failCount = 0;
$query->chunkById($chunk, function ($companies) use (&$updatedCount, &$unchangedCount, &$failCount, $bar) {
foreach ($companies as $company) {
try {
$updated = Company::updateMarketStatus($company->id);
if ($updated) {
$updatedCount++;
$bar->setMessage($company->company_name . ' 已更新', 'status');
} else {
$unchangedCount++;
$bar->setMessage($company->company_name . ' 无变化', 'status');
}
} catch (\Throwable $e) {
$failCount++;
$bar->setMessage($company->company_name . ' 失败: ' . $e->getMessage(), 'status');
}
$bar->advance();
}
});
$bar->finish();
$this->newLine();
$this->info("处理完成:更新 {$updatedCount} 家,未变化 {$unchangedCount} 家,失败 {$failCount} 家");
return $this->info('company_market 全量重算完成');
}
}

@ -34,7 +34,7 @@ class Kernel extends ConsoleKernel
// 更新课程校友资格
$schedule->command('auto_schoolmate')->everyThirtyMinutes();
// 更新公司信息
$schedule->command('update_company')->everyFiveMinutes();
// $schedule->command('update_company')->everyFiveMinutes();
// 全量同步公司信息(每天凌晨执行,不同步经纬度和地址)
// $schedule->command('sync:company')->dailyAt('02:00');
// 同步老师课程方向

@ -10,6 +10,7 @@ use App\Models\AppointmentConfig;
use App\Models\AppointmentType;
use App\Models\Banner;
use App\Models\Company;
use App\Models\CompanyDetailCache;
use App\Models\Config;
use App\Models\CourseType;
use App\Repositories\YuanheRepository;
@ -132,11 +133,36 @@ class OtherController extends CommonController
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$normalizedCompanyName = Company::normalizeCompanyName($all['company_name']);
$cache = CompanyDetailCache::query()
->where('normalized_company_name', $normalizedCompanyName)
->orWhere('normalized_enterprise_name', $normalizedCompanyName)
->orderByDesc('fetched_at')
->first();
if ($cache && !empty($cache->payload)) {
$cache->last_matched_at = now();
$cache->save();
return $this->success($cache->payload);
}
$YuanheRepository = new YuanheRepository();
$result = $YuanheRepository->companyInfo(['enterpriseName' => $all['company_name']]);
if (empty($result)) {
return $this->fail([ResponseCode::ERROR_PARAMETER, '无数据']);
}
CompanyDetailCache::updateOrCreate(
['normalized_company_name' => $normalizedCompanyName],
[
'query_company_name' => trim($all['company_name']),
'enterprise_name' => $result['enterpriseName'] ?? null,
'normalized_enterprise_name' => Company::normalizeCompanyName($result['enterpriseName'] ?? null),
'credit_code' => $result['creditCode'] ?? null,
'enterprise_id' => $result['enterpriseId'] ?? null,
'payload' => $result,
'fetched_at' => now(),
]
);
return $this->success($result);
}

@ -167,6 +167,7 @@ class UserController extends CommonController
{
$all = \request()->except(['id', 'mobile', 'openid']);
$model = User::find($this->getUserId());
$oldCompanyName = $model->company_name;
if (isset($all['password'])) {
// 判断旧密码是否正确
if (!Hash::check($all['old_password'], $model->password)) {
@ -192,10 +193,11 @@ class UserController extends CommonController
$model->fill($all);
$model->save();
// 如果有公司信息,就更新一下公司
if (isset($all['company_name']) && !empty($all['company_name']) && $model->company_name != $all['company_name']) {
// 设置待更新标记,由定时任务处理
$model->company_id = -1;
if (isset($all['company_name']) && !empty($all['company_name']) && $oldCompanyName != $all['company_name']) {
$model->company_id = null;
$model->save();
Company::updateCompanyFromCache($model);
}
// 判断下,如果用户新加入车牌号,并且有未开始或者进行中的预约,则直接预约车牌号
$appointmentModel = Appointment::where('user_id', $this->getUserId())

@ -502,7 +502,7 @@ function friendly_date2($sTime, $cTime = false, $type = 'mohu', $show_after_or_b
// 获取当前域名
function getDomain()
{
return get_http_type() . $_SERVER['HTTP_HOST'];
return config('app.url');
}
/**

@ -3,6 +3,7 @@
namespace App\Models;
use App\Repositories\YuanheRepository;
use Illuminate\Support\Arr;
class Company extends SoftDeletesModel
{
@ -163,30 +164,46 @@ class Company extends SoftDeletesModel
return ['success' => false, 'message' => '用户已有公司关联', 'company' => null];
}
// 清理公司名称:去除首尾空格、换行符、制表符等异常字符
$cleanedCompanyName = trim($user->company_name);
// 去除换行符、回车符、制表符等空白字符
$cleanedCompanyName = preg_replace('/[\r\n\t]+/', '', $cleanedCompanyName);
// 将多个连续空格替换为单个空格
$cleanedCompanyName = preg_replace('/\s+/', ' ', $cleanedCompanyName);
// 再次去除首尾空格
$cleanedCompanyName = trim($cleanedCompanyName);
$cleanedCompanyName = self::normalizeCompanyName($user->company_name);
// 如果清理后为空,返回错误
if (empty($cleanedCompanyName)) {
return ['success' => false, 'message' => '公司名称无效', 'company' => null];
}
$YuanheRepository = new YuanheRepository();
$result = self::getCompanyDetailByName($cleanedCompanyName);
return self::syncUserCompanyByDetail($user, $result);
}
public static function updateCompanyFromCache($user)
{
if (!$user || empty($user->company_name)) {
return ['success' => false, 'message' => '用户或公司名称为空', 'company' => null];
}
if ($user->company_id && $user->company_id > 0) {
return ['success' => false, 'message' => '用户已有公司关联', 'company' => null];
}
$cleanedCompanyName = self::normalizeCompanyName($user->company_name);
if (empty($cleanedCompanyName)) {
return ['success' => false, 'message' => '公司名称无效', 'company' => null];
}
$result = self::getCachedCompanyDetailByName($cleanedCompanyName);
return self::syncUserCompanyByDetail($user, $result, '公司缓存不存在');
}
// 获取公司详细信息,使用清理后的公司名称
$result = $YuanheRepository->companyInfo(['enterpriseName' => $cleanedCompanyName]);
protected static function syncUserCompanyByDetail($user, $result, $notFoundMessage = '公司不存在')
{
if (!$result) {
// 标识一下未匹配到公司,后续可以根据这个字段筛选出未匹配到公司的用户
$user->company_id = 0;
$user->save();
return ['success' => false, 'message' => '公司不存在', 'company' => null];
return ['success' => false, 'message' => $notFoundMessage, 'company' => null];
}
// 如果$result['enterpriseName']存在数字,跳过
@ -203,40 +220,7 @@ class Company extends SoftDeletesModel
}
$where = ['company_name' => $result['enterpriseName']];
$data = [
'business_scope' => $result['businessScope'],
'company_city' => $result['city'],
'contact_mail' => $result['contactMail'],
'contact_phone' => $result['contactPhone'],
'company_area' => $result['country'],
'credit_code' => $result['creditCode'],
'enterprise_id' => $result['enterpriseId'],
'company_name' => $result['enterpriseName'],
'is_abroad' => $result['isAbroad'],
'company_market' => $result['isOnStock'],
'is_yh_invested' => $result['isYhInvested'],
'logo' => $result['logo'],
'company_legal_representative' => $result['operName'],
'company_province' => $result['province'],
'company_industry' => combineKeyValue($result['qccIndustry']),
'regist_amount' => $result['registAmount'],
'regist_capi_type' => $result['registCapiType'],
'company_date' => $result['startDate'],
'status' => $result['status'],
'stock_date' => $result['stockDate'],
'currency_type' => $result['currencyType'],
'stock_number' => $result['stockNumber'],
'stock_type' => $result['stockType'],
'company_tag' => implode(',', $result['tagList']),
// 更新日期
'update_date' => $result['updatedDate'] ?? null,
// 管理平台
'project_users' => $result['projectUsers'] ?? null,
// 股东信息
'partners' => $result['partners'] ?? null,
// 更新地址
'company_address' => $result['address'],
];
$data = self::buildCompanyDataFromDetail($result, true);
$company = Company::updateOrCreate($where, $data);
@ -264,20 +248,13 @@ class Company extends SoftDeletesModel
return ['success' => false, 'message' => '公司或公司名称为空', 'company' => null];
}
// 清理公司名称
$cleanedCompanyName = trim($company->company_name);
$cleanedCompanyName = preg_replace('/[\r\n\t]+/', '', $cleanedCompanyName);
$cleanedCompanyName = preg_replace('/\s+/', ' ', $cleanedCompanyName);
$cleanedCompanyName = trim($cleanedCompanyName);
$cleanedCompanyName = self::normalizeCompanyName($company->company_name);
if (empty($cleanedCompanyName)) {
return ['success' => false, 'message' => '公司名称无效', 'company' => null];
}
$YuanheRepository = new YuanheRepository();
// 获取公司详细信息
$result = $YuanheRepository->companyInfo(['enterpriseName' => $cleanedCompanyName]);
$result = self::fetchCompanyDetailFromApi($cleanedCompanyName);
if (!$result) {
return ['success' => false, 'message' => '公司不存在', 'company' => null];
@ -293,6 +270,95 @@ class Company extends SoftDeletesModel
}
// 更新公司数据(不包含地址和经纬度)
$data = self::buildCompanyDataFromDetail($result, false);
$company->fill($data);
$company->save();
// 更新上市状态
self::updateMarketStatus($company->id);
return ['success' => true, 'message' => '更新成功', 'company' => $company];
}
public static function normalizeCompanyName($companyName)
{
if ($companyName === null) {
return null;
}
$companyName = trim($companyName);
$companyName = preg_replace('/[\r\n\t]+/', '', $companyName);
$companyName = preg_replace('/\s+/', ' ', $companyName);
return trim($companyName);
}
protected static function getCompanyDetailByName($companyName)
{
$normalizedCompanyName = self::normalizeCompanyName($companyName);
if (empty($normalizedCompanyName)) {
return false;
}
$cachePayload = self::getCachedCompanyDetailByName($normalizedCompanyName);
if ($cachePayload) {
return $cachePayload;
}
return self::fetchCompanyDetailFromApi($normalizedCompanyName);
}
protected static function getCachedCompanyDetailByName($companyName)
{
$normalizedCompanyName = self::normalizeCompanyName($companyName);
if (empty($normalizedCompanyName)) {
return false;
}
$cache = CompanyDetailCache::query()
->where('normalized_company_name', $normalizedCompanyName)
->orWhere('normalized_enterprise_name', $normalizedCompanyName)
->orderByDesc('fetched_at')
->first();
if (!$cache || empty($cache->payload)) {
return false;
}
$cache->last_matched_at = now();
$cache->save();
return $cache->payload;
}
protected static function fetchCompanyDetailFromApi($companyName)
{
$YuanheRepository = new YuanheRepository();
$result = $YuanheRepository->companyInfo(['enterpriseName' => $companyName]);
if (!$result) {
return false;
}
CompanyDetailCache::updateOrCreate(
['normalized_company_name' => self::normalizeCompanyName($companyName)],
[
'query_company_name' => $companyName,
'enterprise_name' => Arr::get($result, 'enterpriseName'),
'normalized_enterprise_name' => self::normalizeCompanyName(Arr::get($result, 'enterpriseName')),
'credit_code' => Arr::get($result, 'creditCode'),
'enterprise_id' => Arr::get($result, 'enterpriseId'),
'payload' => $result,
'fetched_at' => now(),
]
);
return $result;
}
protected static function buildCompanyDataFromDetail(array $result, $includeAddress = false)
{
$data = [
'business_scope' => $result['businessScope'],
'company_city' => $result['city'],
@ -317,19 +383,17 @@ class Company extends SoftDeletesModel
'currency_type' => $result['currencyType'],
'stock_number' => $result['stockNumber'],
'stock_type' => $result['stockType'],
'company_tag' => implode(',', $result['tagList']),
'company_tag' => implode(',', $result['tagList'] ?? []),
'update_date' => $result['updatedDate'] ?? null,
'project_users' => $result['projectUsers'] ?? null,
'partners' => $result['partners'] ?? null,
];
$company->fill($data);
$company->save();
// 更新上市状态
self::updateMarketStatus($company->id);
if ($includeAddress) {
$data['company_address'] = $result['address'] ?? null;
}
return ['success' => true, 'message' => '更新成功', 'company' => $company];
return $data;
}
/**
@ -360,36 +424,29 @@ class Company extends SoftDeletesModel
public static function updateMarketStatus($companyId)
{
$company = Company::find($companyId);
if (!$company || empty($company->company_tag)) {
if (!$company) {
return false;
}
// 上市代码正则:匹配全球各地上市公司股票代码后缀
$stockCodePattern = '/\.(SWR|SW|WR|SS|RS|SB|PK|TO|AX|WS|PR|DB|UN|RT|WT|SH|SZ|BJ|TW|HK|SG|US|DE|FR|JP|KR|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|U|V|W|X|Y|Z)(?![A-Za-z0-9])/i';
if (empty($company->company_tag)) {
$newMarketStatus = 0;
$hasStockCode = preg_match($stockCodePattern, $company->company_tag);
if ($company->company_market != $newMarketStatus) {
$company->company_market = $newMarketStatus;
$company->save();
return true;
}
// 不属于新三板上市公司的关键字(需要排除)
$excludeXinsanbanKeywords = [
'新三板摘牌',
'新三板挂牌审核',
'新三板终止',
'新三板退市',
'新三板撤销',
'新三板注销',
'新三板中止',
];
return false;
}
// 检查是否包含排除关键字
$hasExcludeKeyword = array_reduce($excludeXinsanbanKeywords, function ($carry, $keyword) use ($company) {
return $carry || strpos($company->company_tag, $keyword) !== false;
}, false);
// 上市代码正则:匹配全球各地上市公司股票代码后缀
$stockCodePattern = '/\.(SWR|SW|WR|SS|RS|SB|PK|TO|AX|WS|PR|DB|UN|RT|WT|SH|SZ|BJ|TW|HK|SG|US|DE|FR|JP|KR|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|U|V|W|X|Y|Z)(?![A-Za-z0-9])/i';
// 检查是否包含"新三板",且不包含排除关键字
$hasXinsanban = !$hasExcludeKeyword && strpos($company->company_tag, '新三板') !== false;
$hasStockCode = preg_match($stockCodePattern, $company->company_tag);
// 如果匹配到股票代码或包含"新三板"(且非排除关键字),则标记为上市
$newMarketStatus = ($hasStockCode || $hasXinsanban) ? 1 : 0;
// 仅按股票代码判断上市状态,新三板标签不再计入上市公司
$newMarketStatus = $hasStockCode ? 1 : 0;
// 只有状态变化才更新
if ($company->company_market != $newMarketStatus) {

@ -0,0 +1,13 @@
<?php
namespace App\Models;
class CompanyDetailCache extends SoftDeletesModel
{
protected $casts = [
'payload' => 'array',
'fetched_at' => 'datetime',
'last_matched_at' => 'datetime',
];
}

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('company_detail_caches', function (Blueprint $table) {
$table->id();
$table->string('query_company_name')->comment('前端查询公司名称');
$table->string('normalized_company_name')->unique()->comment('标准化查询公司名称');
$table->string('enterprise_name')->nullable()->comment('第三方返回企业名称');
$table->string('normalized_enterprise_name')->nullable()->index()->comment('标准化企业名称');
$table->string('credit_code')->nullable()->index()->comment('统一社会信用代码');
$table->string('enterprise_id')->nullable()->index()->comment('企业ID');
$table->json('payload')->comment('第三方公司详情暂存数据');
$table->dateTime('fetched_at')->nullable()->comment('抓取时间');
$table->dateTime('last_matched_at')->nullable()->comment('最近匹配使用时间');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('company_detail_caches');
}
};
Loading…
Cancel
Save