登录
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
/runtime
|
||||
/.idea
|
||||
/.vscode
|
||||
/vendor
|
||||
*.log
|
||||
.env
|
||||
/tests/tmp
|
||||
/tests/.phpunit.result.cache
|
||||
/public/uploads
|
||||
/webman
|
||||
/start.php
|
||||
/windows.php
|
||||
/bootstrap.php
|
||||
/composer.lock
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/webman/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,70 @@
|
||||
<div style="padding:18px;max-width: 1024px;margin:0 auto;background-color:#fff;color:#333">
|
||||
<h1>webman</h1>
|
||||
|
||||
基于<a href="https://www.workerman.net" target="__blank">workerman</a>开发的超高性能PHP框架
|
||||
|
||||
|
||||
<h1>学习</h1>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/webman" target="__blank">主页 / Home page</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://webman.workerman.net" target="__blank">文档 / Document</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/doc/webman/install.html" target="__blank">安装 / Install</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/questions" target="__blank">问答 / Questions</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/apps" target="__blank">市场 / Apps</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/sponsor" target="__blank">赞助 / Sponsors</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.workerman.net/doc/webman/thanks.html" target="__blank">致谢 / Thanks</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div style="float:left;padding-bottom:30px;">
|
||||
|
||||
<h1>赞助商</h1>
|
||||
|
||||
<h4>特别赞助</h4>
|
||||
<a href="https://www.crmeb.com/?form=workerman" target="__blank">
|
||||
<img src="https://www.workerman.net/img/sponsors/6429/20230719111500.svg" width="200">
|
||||
</a>
|
||||
|
||||
<h4>铂金赞助</h4>
|
||||
<a href="https://www.fadetask.com/?from=workerman" target="__blank"><img src="https://www.workerman.net/img/sponsors/1/20230719084316.png" width="200"></a>
|
||||
<a href="https://www.yilianyun.net/?from=workerman" target="__blank" style="margin-left:20px;"><img src="https://www.workerman.net/img/sponsors/6218/20230720114049.png" width="200"></a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div style="float:left;padding-bottom:30px;clear:both">
|
||||
|
||||
<h1>请作者喝咖啡</h1>
|
||||
|
||||
<img src="https://www.workerman.net/img/wx_donate.png" width="200">
|
||||
<img src="https://www.workerman.net/img/ali_donate.png" width="200">
|
||||
<br>
|
||||
<b>如果您觉得webman对您有所帮助,欢迎捐赠。</b>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div style="clear: both">
|
||||
<h1>LICENSE</h1>
|
||||
The webman is open-sourced software licensed under the MIT.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace app\bootstrap;
|
||||
|
||||
use Webman\Bootstrap;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use app\common\audit\AuditBuffer;
|
||||
|
||||
class Audit implements Bootstrap
|
||||
{
|
||||
public static function start($worker)
|
||||
{
|
||||
Model::created(function ($model) {
|
||||
AuditBuffer::instance()->pushModelChange($model->getTable(), $model->getKey(), [], $model->getAttributes());
|
||||
});
|
||||
Model::updated(function ($model) {
|
||||
$dirty = $model->getDirty();
|
||||
if (!$dirty) return;
|
||||
$old = []; $new = [];
|
||||
foreach ($dirty as $k=>$v) {
|
||||
$old[$k] = $model->getOriginal($k);
|
||||
$new[$k] = $model->getAttribute($k);
|
||||
}
|
||||
AuditBuffer::instance()->pushModelChange($model->getTable(), $model->getKey(), $old, $new);
|
||||
});
|
||||
Model::deleted(function ($model) {
|
||||
AuditBuffer::instance()->pushModelChange($model->getTable(), $model->getKey(), $model->getAttributes(), []);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\crm\model\CustomerHis;
|
||||
use support\Db;
|
||||
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CrmRotateUserCommand extends SymfonyCommand
|
||||
{
|
||||
protected static $defaultName = 'crm:rotate-user';
|
||||
protected static $defaultDescription = '将满足条件的 crm_customer 的 user_id 轮流改为 1、2、3';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setHelp('查询 user 表中 status=2 且 corp_id=3 的用户,
|
||||
然后查找其关联的 crm_customer 记录,
|
||||
按顺序将这些 crm_customer.user_id 轮流设置为 1、2、3。');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$corpId = 3;
|
||||
$status = 2;
|
||||
$roundRobinUserIds = [64,63,83,93,118,131,132,143,145,150,156,157,158,160];
|
||||
|
||||
$output->writeln('<info>开始执行 crm:rotate-user</info>');
|
||||
|
||||
try {
|
||||
// 1) 找到 user 表中满足条件的用户ID,派卡离职用户
|
||||
$userRows = Db::table('user')
|
||||
->where('corp_id', $corpId)
|
||||
->where('status', $status)
|
||||
->get(['id','name']);
|
||||
|
||||
$userIds = [];
|
||||
$userNames = [];
|
||||
foreach ($userRows as $row) {
|
||||
$userIds[] = (int)$row->id;
|
||||
$userNames[] = $row->name;
|
||||
}
|
||||
|
||||
if (empty($userIds)) {
|
||||
$output->writeln('<comment>未找到满足条件的用户(status=2 且 corp_id=3)。无需处理。</comment>');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$output->writeln('<info>匹配到用户数:' . count($userIds) .' '. implode(',',$userNames) . '</info>');
|
||||
|
||||
// 2) 查找 crm_customer 中属于这些用户的记录
|
||||
$customers = Db::table('crm_customer')
|
||||
->whereIn('user_id', $userIds)
|
||||
->orderBy('id', 'asc')
|
||||
->get(['id','user_id']);
|
||||
|
||||
if (!$customers || count($customers) === 0) {
|
||||
$output->writeln('<comment>未找到相关的 crm_customer 记录。</comment>');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$output->writeln('<info>需要处理的 crm_customer 数量:' . count($customers) . '</info>');
|
||||
|
||||
// 3) 事务中按顺序轮流设置
|
||||
// 初始化 UID 轮询器
|
||||
$uidRotator = new \app\common\utils\RedisUidRotator('email_parsed_result_20250804');
|
||||
$uidRotator->setUids([64, 63, 83, 93, 118, 131, 132, 143, 145, 150, 156, 157, 158, 160], false);
|
||||
|
||||
Db::connection()->transaction(function () use ($customers, $roundRobinUserIds, $output, $uidRotator) {
|
||||
$assignCount = 0;
|
||||
$targetCount = count($roundRobinUserIds);
|
||||
|
||||
foreach ($customers as $index => $customer) {
|
||||
// 获取下一个分配的UID
|
||||
$newUserId = $uidRotator->next();
|
||||
Db::table('crm_customer')
|
||||
->where('id', $customer->id)
|
||||
->update(['user_id' => $newUserId]);
|
||||
|
||||
$his = CustomerHis::create([
|
||||
'customer_id' => $customer->id,
|
||||
'user_his' => $customer->user_id,
|
||||
'user_id' => $newUserId,
|
||||
]);
|
||||
|
||||
|
||||
$assignCount++;
|
||||
|
||||
if ($assignCount % 500 === 0) {
|
||||
$output->writeln('<comment>已处理:' . $assignCount . '</comment>');
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln('<info>处理完成,共更新 crm_customer:' . $assignCount . ' 条</info>');
|
||||
});
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln('<error>执行失败:' . $e->getMessage() . '</error>');
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\crm\model\Customer;
|
||||
use support\Log;
|
||||
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||
use Webman\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CustomerCodeCommand extends SymfonyCommand
|
||||
{
|
||||
protected static $defaultName = 'customer:generate-code';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('为crm_customer表所有记录生成唯一code码,分批处理');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$batchSize = 1000;
|
||||
$total = Customer::withTrashed()->where('code','')->count();
|
||||
$output->writeln("总计需处理:$total 条记录");
|
||||
|
||||
$processed = 0;
|
||||
do {
|
||||
// 查询一批未生成code的记录
|
||||
$customers = Customer::withTrashed()->where('code','')->limit($batchSize)->get();
|
||||
if ($customers->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Eloquent 批量处理
|
||||
foreach ($customers as $customer) {
|
||||
// 生成唯一16位code,CM+14位
|
||||
$unique = strtoupper(substr(md5($customer->id . microtime(true) . mt_rand(1000, 9999)), 0, 14));
|
||||
$code = 'CM' . $unique;
|
||||
|
||||
// 防止极小概率重复
|
||||
while (Customer::where('code', $code)->exists()) {
|
||||
$unique = strtoupper(substr(md5($customer->id . microtime(true) . mt_rand(1000, 9999)), 0, 14));
|
||||
$code = 'CM' . $unique;
|
||||
}
|
||||
|
||||
$created_at = $customer->created_at->getTimestamp();
|
||||
$obtain = $created_at > strtotime('2025-07-29')?$created_at:strtotime('2025-07-29');
|
||||
|
||||
$customer->code = $code;
|
||||
$customer->obtain = $obtain;
|
||||
$customer->save();
|
||||
$processed++;
|
||||
}
|
||||
|
||||
$output->writeln("已处理:$processed / $total");
|
||||
} while (true);
|
||||
|
||||
$output->writeln('<info>全部处理完成!</info>');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\crm\model\Customer;
|
||||
use support\Command;
|
||||
use support\Db;
|
||||
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
// 假设你有对应的模型类
|
||||
// use app\model\CrmCustomer;
|
||||
// use app\model\InquiryLog;
|
||||
|
||||
class CustomerCreatedAtCommand extends SymfonyCommand
|
||||
{
|
||||
protected static $defaultName = 'customer:update-created-at';
|
||||
protected static $defaultDescription = '根据 inquiry_log 记录更新 crm_customer 的 created_at 字段';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setHelp('该命令将查询 crm_customer 表中 sn 不为空的记录,并根据 sn 在 inquiry_log 表中查找对应的 order_no 记录,将 inquiry_log 的 created_at 更新到 crm_customer 的 created_at 字段');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln('<info>开始更新 crm_customer 的 created_at 字段...</info>');
|
||||
|
||||
try {
|
||||
// 使用事务确保数据一致性
|
||||
DB::transaction(function () use ($output) {
|
||||
// 查询所有 sn 字段不为空的客户记录
|
||||
$customers = Db::table('crm_customer')
|
||||
->where('sn', '<>', '')
|
||||
->whereNotNull('sn')
|
||||
->get(['id', 'sn']);
|
||||
$output->writeln('<info>'.count($customers).'</info>');
|
||||
|
||||
$updatedCount = 0;
|
||||
|
||||
foreach ($customers as $customer) {
|
||||
// 查找匹配的 inquiry_log 记录
|
||||
$inquiryLog = Db::table('inquiry_log')
|
||||
->where('order_no', $customer->sn)
|
||||
->first(['r_time','created_at']);
|
||||
|
||||
// 如果找到匹配记录,则更新客户的 created_at 字段
|
||||
if ($inquiryLog && isset($inquiryLog->r_time)) {
|
||||
$created_at = $inquiryLog->r_time?:$inquiryLog->created_at;
|
||||
DB::table('crm_customer')
|
||||
->where('id', $customer->id)
|
||||
->update(['created_at' => $created_at]);
|
||||
|
||||
$updatedCount++;
|
||||
$output->writeln("<comment>已更新客户 ID {$customer->id} 的 created_at 为: {$created_at}</comment>");
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln("<info>更新完成,总共更新了 {$updatedCount} 条记录。</info>");
|
||||
});
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>执行过程中发生错误: ' . $e->getMessage() . '</error>');
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\common\model\InquiryLog;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\Db;
|
||||
|
||||
/**
|
||||
* 主要处理询单详情中的图片地址
|
||||
*/
|
||||
class DomainCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'domain';
|
||||
protected static $defaultDescription = '';
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$log = new InquiryLog();
|
||||
$list = $log->orderBy('id', 'desc')->get();
|
||||
|
||||
foreach ($list as $item) {
|
||||
if (!$item->content) {
|
||||
continue;
|
||||
}
|
||||
$item->content = processRichTextImagesEnhanced($item->content);
|
||||
$item->save();
|
||||
$output->writeln("处理了{$item->id} \n");
|
||||
}
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 增强版 - 处理富文本内容中的图片URL
|
||||
*
|
||||
* @param string $content 富文本HTML内容
|
||||
* @param string $newDomain 新域名(如: https://fdesk.hnxinyouhe.com)
|
||||
* @param string $oldDomain 旧域名(如: http://219.157.255.213:25326)
|
||||
* @return string 处理后的富文本内容
|
||||
*/
|
||||
function processRichTextImagesEnhanced($content, $newDomain = 'https://x-server.hnxinyouhe.com', $oldDomain = 'http://219.157.255.213:25326') {
|
||||
// 标准化域名格式(去除末尾斜杠)
|
||||
$newDomain = rtrim($newDomain, '/');
|
||||
$oldDomain = rtrim($oldDomain, '/');
|
||||
|
||||
// 要处理的属性列表
|
||||
$attributes = ['src', 'data-href', 'href', 'data-src', 'data-original'];
|
||||
|
||||
foreach ($attributes as $attr) {
|
||||
// 情况1:替换旧域名为新域名
|
||||
$content = preg_replace(
|
||||
'/('.$attr.'=["\'])'.preg_quote($oldDomain, '/').'(\/uploads\/)/i',
|
||||
'$1' . $newDomain . '$2',
|
||||
$content
|
||||
);
|
||||
|
||||
// 情况2:给无域名的路径添加新域名
|
||||
$content = preg_replace(
|
||||
'/('.$attr.'=["\'])(\/uploads\/)/i',
|
||||
'$1' . $newDomain . '$2',
|
||||
$content
|
||||
);
|
||||
|
||||
// 情况3:处理可能存在的其他域名(可选)
|
||||
$content = preg_replace(
|
||||
'/('.$attr.'=["\'])(https?:\/\/[^\/]+)(\/uploads\/)/i',
|
||||
'$1' . $newDomain . '$3',
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\Db;
|
||||
|
||||
/**
|
||||
* 拉取邮箱的详情入库(根据emai_uid)
|
||||
*/
|
||||
class EmailDetailCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'email-detail';
|
||||
protected static $defaultDescription = '拉取 detail 入库';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOption(
|
||||
'per-page', 'p',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'每批处理多少条详情',
|
||||
config('imap.per_page', 50)
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
try {
|
||||
$cfg = config('imap');
|
||||
$mailbox = $cfg['mailbox'];
|
||||
$username = $cfg['username'];
|
||||
$password = $cfg['password'];
|
||||
$batch = (int)$input->getOption('per-page');
|
||||
|
||||
// 打开 IMAP
|
||||
$inbox = @imap_open($mailbox, $username, $password);
|
||||
if (!$inbox) {
|
||||
$output->writeln("<error>IMAP 连接失败:". imap_last_error() ."</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// 阶段二:逐批拉取详情并写入 emails 表
|
||||
$output->writeln("依次拉取详情并写入 emails 表 …");
|
||||
while (true) {
|
||||
$pending = Db::table('email_uids')
|
||||
->where('status', 0)
|
||||
// ->where('uid', '>', 13399)
|
||||
->orderBy('uid', 'desc')
|
||||
->limit(2)
|
||||
->get();
|
||||
|
||||
if (count($pending) === 0) {
|
||||
$output->writeln("<info>所有 UID 均已处理完毕。</info>");
|
||||
break;
|
||||
}
|
||||
|
||||
$output->writeln("处理下一批 " . count($pending) . " 条 …");
|
||||
|
||||
foreach ($pending as $row) {
|
||||
$uid = $row->uid;
|
||||
|
||||
// 包裹整行处理在 try/catch 中,任何异常都不会中断循环
|
||||
try {
|
||||
$output->writeln("---- 开始处理 UID={$uid} ----");
|
||||
|
||||
// 1. fetch overview
|
||||
$ov = @imap_fetch_overview($inbox, $uid, FT_UID)[0] ?? null;
|
||||
if (!$ov) {
|
||||
$err = imap_last_error() ?: '未知错误';
|
||||
$output->writeln("<error>UID={$uid} overview 失败:{$err}</error>");
|
||||
$this->logError("UID={$uid} overview 失败:{$err}");
|
||||
continue;
|
||||
}
|
||||
$subject = mb_convert_encoding($ov->subject ?? '', 'UTF-8', 'GBK,UTF-8');
|
||||
$from = $ov->from ?? '';
|
||||
$date = $ov->date ?? '';
|
||||
|
||||
$output->writeln("UID={$uid} overview 成功,subject={$subject}");
|
||||
|
||||
// 2. fetch body
|
||||
$body = $this->getMailBody($inbox, $uid, true);
|
||||
$output->writeln("UID={$uid} body 长度:" . strlen($body));
|
||||
|
||||
// 3. sanitize & insert
|
||||
$body = $this->sanitizeUtf8($body);
|
||||
$now = time();
|
||||
|
||||
try {
|
||||
Db::table('emails')->insert([
|
||||
'uid' => $uid,
|
||||
'subject' => $subject,
|
||||
'from' => $from,
|
||||
'date' => $date,
|
||||
'body' => $body,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$output->writeln("<info>UID={$uid} 写入 emails 成功</info>");
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln("<error>UID={$uid} 写入失败:{$e->getMessage()}</error>");
|
||||
$this->logError("UID={$uid} 写入失败:{$e->getMessage()}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. 标记已处理
|
||||
try {
|
||||
Db::table('email_uids')
|
||||
->where('uid', $uid)
|
||||
->update([
|
||||
'status' => 1,
|
||||
'processed_at' => $now,
|
||||
]);
|
||||
$output->writeln("<info>UID={$uid} 标记为已处理</info>");
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln("<error>UID={$uid} 标记状态失败:{$e->getMessage()}</error>");
|
||||
$this->logError("UID={$uid} 标记状态失败:{$e->getMessage()}");
|
||||
// 即使更新 status 失败,也继续下一条
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 捕获任何非预期错误
|
||||
$output->writeln("<error>UID={$uid} 处理异常:{$e->getMessage()}</error>");
|
||||
$this->logError("UID={$uid} 处理异常:{$e->getMessage()}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 批次结束后稍作休眠,防止服务器过载
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
imap_close($inbox);
|
||||
$output->writeln("\n<info>✅ 全部邮件处理完成。</info>");
|
||||
return Command::SUCCESS;
|
||||
} catch (\Throwable $th) {
|
||||
dpr($th);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将任意字符串清洗为有效 UTF-8,移除或替换无效字节
|
||||
*/
|
||||
protected function sanitizeUtf8(string $text): string
|
||||
{
|
||||
return iconv('UTF-8', 'UTF-8//IGNORE', $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录错误到日志
|
||||
*/
|
||||
protected function logError(string $msg): void
|
||||
{
|
||||
$line = '[' . date('Y-m-d H:i:s') . '] ' . $msg . "\n";
|
||||
file_put_contents(
|
||||
runtime_path('logs/email_errors.log'),
|
||||
$line,
|
||||
FILE_APPEND | LOCK_EX
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按编码解码并保留 "\r\n"
|
||||
*/
|
||||
protected function getMailBody($inbox, int $id, bool $isUid = false): string
|
||||
{
|
||||
$flags = $isUid ? FT_UID : 0;
|
||||
$structure = imap_fetchstructure($inbox, $id, $flags);
|
||||
|
||||
if (!empty($structure->parts)) {
|
||||
// HTML 部分
|
||||
foreach ($structure->parts as $i => $part) {
|
||||
if (isset($part->subtype) && strtolower($part->subtype) === 'html') {
|
||||
$data = imap_fetchbody($inbox, $id, (string)($i+1), $flags);
|
||||
return $this->decodeBody($data, $part->encoding);
|
||||
}
|
||||
}
|
||||
// 文本部分
|
||||
foreach ($structure->parts as $i => $part) {
|
||||
if (isset($part->subtype) && strtolower($part->subtype) === 'plain') {
|
||||
$data = imap_fetchbody($inbox, $id, (string)($i+1), $flags);
|
||||
return $this->decodeBody($data, $part->encoding);
|
||||
}
|
||||
}
|
||||
// 原始 body
|
||||
return imap_body($inbox, $id, $flags);
|
||||
}
|
||||
|
||||
return imap_body($inbox, $id, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按编码解码
|
||||
*/
|
||||
protected function decodeBody(string $body, int $encoding): string
|
||||
{
|
||||
switch ($encoding) {
|
||||
case 3: return imap_base64($body);
|
||||
case 4: return imap_qprint($body);
|
||||
default: return $body;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
namespace app\command;
|
||||
|
||||
use support\Db;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* 将拉取邮箱的详情进行清洗,找出询盘的邮件入库(根据emai_detail)
|
||||
*/
|
||||
class EmailParseCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'email-parse';
|
||||
protected static $defaultDescription = '邮件清洗';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
// 不再接收参数,固定每次 100 条
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$batchSize = 100; // 每次处理 100 条
|
||||
$page = 0;
|
||||
|
||||
// 明确定义 parsed_contacts 表里要插入的所有列(顺序无所谓,但要全部包含)
|
||||
$fields = [
|
||||
'email_id',
|
||||
'date',
|
||||
'email',
|
||||
'name',
|
||||
'tel',
|
||||
'company',
|
||||
'country',
|
||||
'form_url',
|
||||
'message',
|
||||
'ip',
|
||||
'iplocation',
|
||||
'ilang',
|
||||
'mailto',
|
||||
'navigator_language',
|
||||
'navigator_platform',
|
||||
'screen_width',
|
||||
'screen_height',
|
||||
'ucountry',
|
||||
];
|
||||
|
||||
while (true) {
|
||||
$rows = Db::table('emails')
|
||||
->orderBy('id', 'asc')
|
||||
->limit($batchSize)
|
||||
->offset($page * $batchSize)
|
||||
->get();
|
||||
|
||||
if ($rows->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$toInsert = [];
|
||||
foreach ($rows as $row) {
|
||||
$parsed = $this->parseHtml($row->body);
|
||||
if ($parsed === null) {
|
||||
$output->writeln(" ↳ [{$row->id}] skip: not a contact-form HTML");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 严格按 $fields 顺序填充每条记录,缺失的字段补 null
|
||||
$record = [];
|
||||
foreach ($fields as $col) {
|
||||
if ($col === 'email_id') {
|
||||
$record[$col] = $row->id;
|
||||
} else {
|
||||
$record[$col] = $parsed[$col] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
$toInsert[] = $record;
|
||||
}
|
||||
|
||||
if (!empty($toInsert)) {
|
||||
Db::table('email_parsed_result')->insert($toInsert);
|
||||
$output->writeln("Inserted batch #" . ($page + 1) . ", count: " . count($toInsert));
|
||||
}
|
||||
|
||||
$page++;
|
||||
}
|
||||
|
||||
$output->writeln("✔ All done.");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 HTML,找不到 <h2>Preview</h2> 则返回 null,
|
||||
* 否则提取所有 <span>Key:</span> Value 为小写蛇形 key=>value 数组
|
||||
*/
|
||||
protected function parseHtml(string $html): ?array
|
||||
{
|
||||
$doc = new \DOMDocument();
|
||||
libxml_use_internal_errors(true);
|
||||
$doc->loadHTML('<?xml encoding="utf-8"?>' . $html);
|
||||
libxml_clear_errors();
|
||||
|
||||
$xpath = new \DOMXPath($doc);
|
||||
|
||||
// 类型检查
|
||||
if ($xpath->query("//h2[text()='Preview']")->length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nodes = $xpath->query("//div[span]");
|
||||
$data = [];
|
||||
|
||||
/** @var \DOMElement $node */
|
||||
foreach ($nodes as $node) {
|
||||
$span = $xpath->query("span", $node)->item(0);
|
||||
if (! $span) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 原始键名
|
||||
$key = trim(str_replace(':', '', $span->textContent));
|
||||
// 转成小写蛇形
|
||||
$col = strtolower(str_replace(' ', '_', $key));
|
||||
|
||||
// 获取值
|
||||
$a = $xpath->query(".//a", $node)->item(0);
|
||||
if ($a && $a->hasAttribute('href')) {
|
||||
$value = trim($a->getAttribute('href'));
|
||||
} else {
|
||||
$clone = $node->cloneNode(true);
|
||||
$spanClone = $xpath->query(".//span", $clone)->item(0);
|
||||
if ($spanClone) {
|
||||
$spanClone->parentNode->removeChild($spanClone);
|
||||
}
|
||||
$value = trim($clone->textContent);
|
||||
}
|
||||
|
||||
$data[$col] = $value;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\Db;
|
||||
|
||||
/**
|
||||
* 拉取邮箱的UID入库
|
||||
*/
|
||||
class EmailUidCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'email-uid';
|
||||
protected static $defaultDescription = '拉取 UID 入库';
|
||||
|
||||
// protected function configure()
|
||||
// {
|
||||
// $this->addOption(
|
||||
// 'per-page', 'p',
|
||||
// InputOption::VALUE_OPTIONAL,
|
||||
// '每批处理多少条详情',
|
||||
// config('imap.per_page', 50)
|
||||
// );
|
||||
// }
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
try {
|
||||
$cfg = config('imap');
|
||||
$mailbox = $cfg['mailbox'];
|
||||
$username = $cfg['username'];
|
||||
$password = $cfg['password'];
|
||||
// $batch = (int)$input->getOption('per-page');
|
||||
|
||||
$retryTimes = 3;
|
||||
$retryDelay = 10; // 秒
|
||||
|
||||
$inbox = false;
|
||||
$attempt = 0;
|
||||
|
||||
$maxUid = Db::table('email_uids')->max('uid');
|
||||
|
||||
while ($attempt < $retryTimes) {
|
||||
$attempt++;
|
||||
$output->writeln("尝试第 {$attempt} 次连接 IMAP...");
|
||||
$inbox = @imap_open($mailbox, $username, $password);
|
||||
|
||||
if ($inbox) {
|
||||
break;
|
||||
}
|
||||
|
||||
$error = imap_last_error();
|
||||
$output->writeln("<comment>第 {$attempt} 次失败:{$error}</comment>");
|
||||
|
||||
// 若出现 too many login failures 立即暂停一段时间
|
||||
if (stripos($error, 'Too many login failures') !== false) {
|
||||
$output->writeln("<error>检测到登录失败次数过多,暂停 60 秒避免封禁</error>");
|
||||
sleep(60);
|
||||
} else {
|
||||
sleep($retryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$inbox) {
|
||||
$output->writeln("<error>最终仍然连接失败:" . imap_last_error() . "</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// 拉取 UID 并入库
|
||||
$output->writeln("开始拉取 UID 并入库 …");
|
||||
$uids = imap_search($inbox, 'ALL', SE_UID) ?: [];
|
||||
|
||||
$uids = array_filter($uids, function($uid) use ($maxUid) {
|
||||
return $uid > $maxUid;
|
||||
});
|
||||
|
||||
sort($uids);
|
||||
$now = time();
|
||||
$count = 0;
|
||||
|
||||
foreach ($uids as $uid) {
|
||||
try {
|
||||
Db::table('email_uids')->insert([
|
||||
'uid' => $uid,
|
||||
'fetched_at' => $now,
|
||||
'email' => $cfg['username']
|
||||
]);
|
||||
$count++;
|
||||
|
||||
$output->writeln("UID:".$uid." 已写入数据库");
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln("<comment>{$e->getMessage()}</comment>");
|
||||
}
|
||||
}
|
||||
|
||||
imap_close($inbox);
|
||||
$output->writeln(date('Y-m-d H:i:s').":共拉到 {$count} 个 UID 并写入 email_uids 表。");
|
||||
return Command::SUCCESS;
|
||||
} catch (\Throwable $th) {
|
||||
dpr($th);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定格式的日期字符串
|
||||
*
|
||||
* @param string|null $input_date 可选,指定的日期(可以是任何PHP可解析的日期格式)
|
||||
* @return string 返回格式为 'd-M-Y' 的日期字符串(如 '28-Jul-2025')
|
||||
*/
|
||||
function getImapFormattedDate($input_date = null) {
|
||||
// 如果有传入日期参数,尝试解析
|
||||
if ($input_date !== null) {
|
||||
$timestamp = strtotime($input_date);
|
||||
if ($timestamp === false) {
|
||||
// 如果日期解析失败,使用当前日期并记录警告
|
||||
trigger_error("无法解析提供的日期: '$input_date',将使用当前日期", E_USER_WARNING);
|
||||
$timestamp = time();
|
||||
}
|
||||
} else {
|
||||
// 如果没有提供日期,使用当前日期
|
||||
$timestamp = time();
|
||||
}
|
||||
|
||||
// 格式化为 IMAP 需要的日期格式 (如 '28-Jul-2025')
|
||||
return date('d-M-Y', $timestamp);
|
||||
}
|
||||
|
||||
// // 使用示例
|
||||
// echo getImapFormattedDate(); // 输出当天的日期,如 '31-Jul-2025'
|
||||
// echo getImapFormattedDate('2025-07-28'); // 输出 '28-Jul-2025'
|
||||
// echo getImapFormattedDate('28/07/2025'); // 输出 '28-Jul-2025'
|
||||
// echo getImapFormattedDate('next Monday'); // 输出下个周一的日期
|
||||
|
||||
/*
|
||||
|
||||
// imap_search() 条件
|
||||
|
||||
// 1. 基本日期搜索(按接收日期)
|
||||
// 搜索今天收到的邮件
|
||||
$today = date("d-M-Y");
|
||||
$uids = imap_search($inbox, 'SINCE "' . $today . '"', SE_UID);
|
||||
|
||||
// 搜索特定日期之后的邮件
|
||||
$since_date = '15-Jul-2023';
|
||||
$uids = imap_search($inbox, 'SINCE "' . $since_date . '"', SE_UID);
|
||||
|
||||
// 搜索特定日期之前的邮件
|
||||
$before_date = '20-Jul-2023';
|
||||
$uids = imap_search($inbox, 'BEFORE "' . $before_date . '"', SE_UID);
|
||||
|
||||
// 搜索特定日期的邮件
|
||||
$on_date = '18-Jul-2023';
|
||||
$uids = imap_search($inbox, 'ON "' . $on_date . '"', SE_UID);
|
||||
|
||||
// 2. 日期范围搜索
|
||||
// 搜索2023年7月15日到2023年7月20日之间的邮件
|
||||
$start_date = '15-Jul-2023';
|
||||
$end_date = '20-Jul-2023';
|
||||
$uids = imap_search($inbox, 'SINCE "' . $start_date . '" BEFORE "' . $end_date . '"', SE_UID);
|
||||
|
||||
// 3. 结合日期和其他条件
|
||||
// 搜索2023年7月以后未读的邮件
|
||||
$date = '1-Jul-2023';
|
||||
$uids = imap_search($inbox, 'SINCE "' . $date . '" UNSEEN', SE_UID);
|
||||
|
||||
// 搜索2023年7月来自某人的邮件
|
||||
$date = '1-Jul-2023';
|
||||
$from = 'example@domain.com';
|
||||
$uids = imap_search($inbox, 'SINCE "' . $date . '" FROM "' . $from . '"', SE_UID);
|
||||
|
||||
// 4. 日期格式注意事项
|
||||
// - IMAP日期格式通常为 d-M-Y,例如 15-Jul-2023
|
||||
// - 月份使用英文缩写(Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)
|
||||
// - 日期格式必须用双引号括起来
|
||||
|
||||
所有ID
|
||||
imap_search($inbox, 'ALL', SE_UID)
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\Db;
|
||||
use app\common\utils\email\Emails;
|
||||
|
||||
/**
|
||||
* 批量处理用户编号(也用于调试)
|
||||
*/
|
||||
class FinanceRecoCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'finance-reco';
|
||||
protected static $defaultDescription = '';
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
|
||||
// $tmp = (new \app\common\service\OssService)->generatePrivateUrl('fdesk_test/20251104/20251104_财务.xlsx');;
|
||||
|
||||
$service = new \app\common\service\FinanceReconDubaiFilesService();
|
||||
$result = $service->dImport('fdesk_test/20251104/20251104_迪拜.xlsx');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace app\command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\Db;
|
||||
use support\Log;
|
||||
|
||||
/**
|
||||
* 处理询单中的编号重复
|
||||
*/
|
||||
class FixInquiryLogOrderNo extends Command
|
||||
{
|
||||
protected static $defaultName = 'sn';
|
||||
protected static $defaultDescription = 'Fix duplicate order_no values in inquiry_log table';
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// Start transaction for safety
|
||||
Db::beginTransaction();
|
||||
|
||||
try {
|
||||
$output->writeln('Starting duplicate order_no fix process...');
|
||||
|
||||
// Find all duplicate order_no values using Laravel-style query
|
||||
$duplicates = Db::table('inquiry_log')
|
||||
->select('order_no', Db::raw('COUNT(*) as count'))
|
||||
->groupBy('order_no')
|
||||
->having('count', '>', 1)
|
||||
->get();
|
||||
|
||||
if ($duplicates->isEmpty()) {
|
||||
$output->writeln('No duplicate order_no values found.');
|
||||
Db::commit();
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('Found %d order_no values with duplicates.', $duplicates->count()));
|
||||
|
||||
$totalFixed = 0;
|
||||
|
||||
foreach ($duplicates as $dup) {
|
||||
$orderNo = $dup->order_no;
|
||||
|
||||
$output->writeln("Processing duplicates for order_no: {$orderNo}");
|
||||
|
||||
// Get all records with this order_no, ordered by created_at (oldest first)
|
||||
$records = Db::table('inquiry_log')
|
||||
->where('order_no', $orderNo)
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
|
||||
// Keep the first record as is, modify the rest
|
||||
$keepOriginal = true;
|
||||
|
||||
foreach ($records as $record) {
|
||||
if ($keepOriginal) {
|
||||
$output->writeln("Keeping original record ID {$record->id} with order_no {$record->order_no}");
|
||||
$keepOriginal = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate new order_no by appending suffix
|
||||
$newOrderNo = orderNum();
|
||||
$output->writeln("Updating record ID {$record->id} from {$record->order_no} to {$newOrderNo}");
|
||||
|
||||
// Update the record
|
||||
Db::table('inquiry_log')
|
||||
->where('id', $record->id)
|
||||
->update(['order_no' => $newOrderNo]);
|
||||
|
||||
$totalFixed++;
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
$output->writeln("Successfully fixed {$totalFixed} duplicate records.");
|
||||
|
||||
// Verify no duplicates remain
|
||||
$remainingDuplicates = Db::table('inquiry_log')
|
||||
->select('order_no', Db::raw('COUNT(*) as count'))
|
||||
->groupBy('order_no')
|
||||
->having('count', '>', 1)
|
||||
->get();
|
||||
|
||||
if ($remainingDuplicates->isEmpty()) {
|
||||
$output->writeln('Verification: No duplicate order_no values remain.');
|
||||
} else {
|
||||
$output->writeln('Warning: Some duplicates still remain. Please run the command again.');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
$output->writeln("Error: " . $e->getMessage());
|
||||
Log::error("FixInquiryLogOrderNo command failed: " . $e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\crm\model\Country;
|
||||
use app\crm\model\Customer;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class ImportCustomerCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'import:customer';
|
||||
protected static $defaultDescription = '导入Excel文件到crm_customer表';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
// $this->addArgument('file', InputArgument::REQUIRED, 'Excel文件路径')
|
||||
// ->addOption('user-id', 'u', InputOption::VALUE_REQUIRED, '用户ID', 0)
|
||||
// ->addOption('channel-id', 'c', InputOption::VALUE_REQUIRED, '渠道ID', 5)
|
||||
// ->addOption('corp-abbr', 'a', InputOption::VALUE_REQUIRED, '公司缩写', 2);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// $filePath = $input->getArgument('file');
|
||||
// $userId = $input->getOption('user-id');
|
||||
// $channelId = $input->getOption('channel-id');
|
||||
// $corpAbbr = $input->getOption('corp-abbr');
|
||||
$filePath = 'C:\Users\16961\Downloads\中国制造 2023-2025-询盘导出.xls';
|
||||
$userId = 0;
|
||||
$channelId = 5;
|
||||
$corpAbbr = 2;
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!file_exists($filePath)) {
|
||||
$output->writeln("<error>文件不存在: {$filePath}</error>");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
$output->writeln("开始导入文件: {$filePath}");
|
||||
|
||||
// 加载Excel文件
|
||||
$output->writeln("正在加载Excel文件...");
|
||||
$spreadsheet = IOFactory::load($filePath);
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
$output->writeln("Excel文件加载成功");
|
||||
|
||||
// 获取数据范围
|
||||
$highestRow = $worksheet->getHighestRow();
|
||||
$highestColumn = $worksheet->getHighestColumn();
|
||||
|
||||
$output->writeln("文件包含 {$highestRow} 行数据");
|
||||
|
||||
// 获取所有国家数据,用于匹配
|
||||
$output->writeln("正在获取国家数据...");
|
||||
// $countries = Country::select('id', 'cn_addr', 'en_addr')->get()->keyBy(function($item) {
|
||||
// return strtolower(trim($item->cn_addr));
|
||||
// });
|
||||
// $output->writeln("获取到 " . $countries->count() . " 个国家数据");
|
||||
|
||||
$successCount = 0;
|
||||
$errorCount = 0;
|
||||
$errors = [];
|
||||
|
||||
// 从第二行开始读取数据(第一行是标题)
|
||||
$output->writeln("开始读取Excel数据...");
|
||||
for ($row = 2; $row <= $highestRow; $row++) {
|
||||
try {
|
||||
$output->writeln("正在处理第 {$row} 行...");
|
||||
|
||||
// 读取各列数据 - 使用坐标方法
|
||||
try {
|
||||
$countryName = trim($worksheet->getCell('B' . $row)->getValue() ?? '');
|
||||
$contacts = trim($worksheet->getCell('A' . $row)->getValue() ?? '');
|
||||
$email = trim($worksheet->getCell('C' . $row)->getValue() ?? '');
|
||||
$phone = trim($worksheet->getCell('D' . $row)->getValue() ?? '');
|
||||
$inquiryProduct = trim($worksheet->getCell('E' . $row)->getValue() ?? '');
|
||||
} catch (\Exception $cellError) {
|
||||
$output->writeln("<error>读取第{$row}行单元格时出错: " . $cellError->getMessage() . "</error>");
|
||||
continue;
|
||||
}
|
||||
|
||||
$output->writeln("读取数据: 国家={$countryName}, 客户={$contacts}, 产品={$inquiryProduct}");
|
||||
|
||||
// 跳过空行
|
||||
// if (empty($contacts)) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// 查找国家ID
|
||||
$countryId = 0;
|
||||
// if (!empty($countryName)) {
|
||||
// $countryKey = strtolower($countryName);
|
||||
// if ($countries->has($countryKey)) {
|
||||
// $countryId = $countries->get($countryKey)->id;
|
||||
// } else {
|
||||
// // 尝试英文名称匹配
|
||||
// $countryByEn = $countries->first(function($item) use ($countryName) {
|
||||
// return strtolower(trim($item->en_addr)) === strtolower($countryName);
|
||||
// });
|
||||
// if ($countryByEn) {
|
||||
// $countryId = $countryByEn->id;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// 检查是否已存在相同客户名
|
||||
// $existingCustomer = Customer::where('contacts', $customerName)->first();
|
||||
// if ($existingCustomer) {
|
||||
// $output->writeln("跳过已存在的客户: {$customerName}");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// 创建客户记录
|
||||
// $customerData = [
|
||||
// 'user_id' => $userId,
|
||||
// 'corp_abbr' => $corpAbbr,
|
||||
// 'channel_id' => $channelId,
|
||||
// 'country_id' => $countryId,
|
||||
// 'contacts' => $customerName,
|
||||
// 'email' => $email,
|
||||
// 'phone' => $phone,
|
||||
// 'desc_collect' => $inquiryProduct,
|
||||
// 'grade' => 0, // 默认等级
|
||||
// 'magnitude' => 1, // 默认商机
|
||||
// 'type' => 1, // 默认类型
|
||||
// 'sex' => 0, // 默认性别
|
||||
// 'enter_user_id' => $userId,
|
||||
// ];
|
||||
$where = [
|
||||
['id', '<', 28110],
|
||||
['id', '>', 25714],
|
||||
];
|
||||
$customer = Customer::where('contacts',$email)->where($where)->first();
|
||||
$customer->email = $email;
|
||||
$customer->phone = $phone;
|
||||
$customer->contacts = $contacts;
|
||||
$customer->save();
|
||||
$successCount++;
|
||||
|
||||
$output->writeln("成功导入客户: {$contacts} (ID: {$customer->id})");
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$errorCount++;
|
||||
$errorMsg = "第{$row}行导入失败: " . $e->getMessage();
|
||||
$errorMsg .= "\n文件: " . $e->getFile() . " 行: " . $e->getLine();
|
||||
$errorMsg .= "\n堆栈: " . $e->getTraceAsString();
|
||||
$errors[] = $errorMsg;
|
||||
$output->writeln("<error>{$errorMsg}</error>");
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln("\n导入完成!");
|
||||
$output->writeln("成功导入: {$successCount} 条记录");
|
||||
$output->writeln("失败: {$errorCount} 条记录");
|
||||
|
||||
if (!empty($errors)) {
|
||||
$output->writeln("\n错误详情:");
|
||||
foreach ($errors as $error) {
|
||||
$output->writeln("<error>{$error}</error>");
|
||||
}
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("<error>导入过程中发生错误: " . $e->getMessage() . "</error>");
|
||||
$output->writeln("<error>文件: " . $e->getFile() . " 行: " . $e->getLine() . "</error>");
|
||||
$output->writeln("<error>堆栈: " . $e->getTraceAsString() . "</error>");
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\common\model\User;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class InitUserPasswordCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'init:user-password';
|
||||
protected static $defaultDescription = '初始化user表密码,如果密码为空则用sn加密后写入password字段';
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$users = User::whereNull('password')->orWhere('password', '')->get();
|
||||
$count = 0;
|
||||
|
||||
foreach ($users as $user) {
|
||||
if (!empty($user->sn)) {
|
||||
$user->password = password_hash($user->sn, PASSWORD_DEFAULT);
|
||||
$user->save();
|
||||
$output->writeln("用户ID {$user->id} 密码已初始化");
|
||||
$count++;
|
||||
} else {
|
||||
$output->writeln("<error>用户ID {$user->id} 的sn字段为空,无法初始化密码</error>");
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln("共初始化 {$count} 个用户密码");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\Db;
|
||||
use app\common\utils\email\Emails;
|
||||
|
||||
/**
|
||||
* 批量处理用户编号(也用于调试)
|
||||
*/
|
||||
class TbCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'tb-db';
|
||||
protected static $defaultDescription = '';
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// $user = new User();
|
||||
// $list = $user->all();
|
||||
|
||||
// foreach ($list as $item) {
|
||||
// // UserSn::where('user_sn', $item->sn)->update(['user_id' => $item->id]);
|
||||
// }
|
||||
|
||||
$m = new Emails();
|
||||
$m->fenpai();
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use support\Command;
|
||||
use support\Db;
|
||||
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class UpdateInquiryLogFromCrmCustomerCommand extends SymfonyCommand
|
||||
{
|
||||
protected static $defaultName = 'inquiry:update-from-crm-customer';
|
||||
protected static $defaultDescription = '将crm_customer表的部分字段更新到inquiry_log表';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setHelp('该命令将crm_customer表的user_id更新到inquiry_log表的recipient字段,将obtain字段更新到t_time字段。')
|
||||
->addOption('dry-run', null, InputOption::VALUE_NONE, '仅显示将要执行的更新操作,不实际执行')
|
||||
->addOption('limit', 'l', InputOption::VALUE_OPTIONAL, '限制处理的记录数量', 5000);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$isDryRun = $input->getOption('dry-run');
|
||||
$limit = (int) $input->getOption('limit');
|
||||
|
||||
if ($isDryRun) {
|
||||
$output->writeln('<info>DRY RUN 模式 - 不会实际执行更新操作</info>');
|
||||
}
|
||||
|
||||
$output->writeln('<info>开始更新 inquiry_log 表...</info>');
|
||||
$output->writeln("<info>处理限制: {$limit} 条记录</info>");
|
||||
|
||||
try {
|
||||
// 使用事务确保数据一致性
|
||||
DB::transaction(function () use ($output, $isDryRun, $limit) {
|
||||
// 查询需要更新的记录
|
||||
$customers = Db::table('crm_customer')
|
||||
->select('id', 'sn', 'user_id', 'obtain','created_at')
|
||||
->whereNotNull('sn')
|
||||
->where('sn', '<>', '')
|
||||
->offset($limit)
|
||||
->limit(4000)
|
||||
->orderBy('id', 'asc')
|
||||
->get();
|
||||
|
||||
$output->writeln("<info>找到 " . count($customers) . " 条客户记录需要处理</info>");
|
||||
|
||||
$updatedCount = 0;
|
||||
$skippedCount = 0;
|
||||
$defaultTimestamp = strtotime('2025-08-26 00:00:00');
|
||||
|
||||
$output->writeln("<info>找到 " . count($customers) . " 条客户记录需要处理</info>");
|
||||
$step = [];
|
||||
$updateId = [];
|
||||
foreach ($customers as $customer) {
|
||||
$output->writeln("<info>处理客户 ID: {$customer->id}, SN: {$customer->sn}, User ID: {$customer->user_id}, obtain: {$customer->obtain}, created_at: {$customer->created_at}</info>");
|
||||
// 查找匹配的 inquiry_log 记录
|
||||
$inquiryLog = Db::table('inquiry_log')
|
||||
->where('order_no', $customer->sn)
|
||||
->first(['id', 'recipient', 't_time']);
|
||||
|
||||
if (!$inquiryLog) {
|
||||
$step[] = $customer->id;
|
||||
$output->writeln("<comment>跳过客户 ID {$customer->id} (sn: {$customer->sn}) - 未找到对应的 inquiry_log 记录</comment>");
|
||||
$skippedCount++;
|
||||
continue;
|
||||
}
|
||||
$output->writeln("<info>$inquiryLog->id</info>");
|
||||
|
||||
// 准备更新数据
|
||||
$updateData = [];
|
||||
|
||||
// 更新 recipient 字段 (user_id)
|
||||
if ($customer->user_id != $inquiryLog->recipient) {
|
||||
$updateData['recipient'] = $customer->user_id;
|
||||
$output->writeln("<comment>客户 ID {$customer->id}: recipient 将从 {$inquiryLog->recipient} 更新为 {$customer->user_id}</comment>");
|
||||
}
|
||||
|
||||
if(empty($customer->obtain)&& !empty($customer->user_id)){
|
||||
if($customer->user_id == $inquiryLog->recipient){
|
||||
$obtain = $inquiryLog->t_time;
|
||||
}else{
|
||||
$obtain = strtotime('2025-08-01 10:00:00');
|
||||
$obtain = $customer->created_at > $obtain?$customer->created_at:$obtain;
|
||||
}
|
||||
Db::table('crm_customer')
|
||||
->where('id', $customer->id)
|
||||
->update(['obtain' => $obtain]);
|
||||
$output->writeln("<info>更新obtain $obtain</info>");
|
||||
}
|
||||
|
||||
// 更新 t_time 字段 (obtain)
|
||||
$newTTime = $customer->obtain > 0 ? $customer->obtain : $defaultTimestamp;
|
||||
if ($newTTime != $inquiryLog->t_time && !empty($updateData['recipient'])) {
|
||||
$updateData['t_time'] = $newTTime;
|
||||
$timeStr = date('Y-m-d H:i:s', $newTTime);
|
||||
$output->writeln("<comment>客户 ID {$customer->id}: t_time 将从 " . ($inquiryLog->t_time ? date('Y-m-d H:i:s', $inquiryLog->t_time) : 'NULL') . " 更新为 {$timeStr}</comment>");
|
||||
}
|
||||
if($customer->user_id == 0 && $inquiryLog->recipient != 0){
|
||||
$updateData['recipient'] = 0;
|
||||
$updateData['t_time'] = time();
|
||||
$updateData['status'] = 1;
|
||||
$updateData['r_time'] = time();
|
||||
|
||||
}
|
||||
|
||||
// 如果有需要更新的字段,执行更新
|
||||
if (!empty($updateData)) {
|
||||
$updateId[] = $customer->id;
|
||||
if (!$isDryRun) {
|
||||
Db::table('inquiry_log')
|
||||
->where('id', $inquiryLog->id)
|
||||
->update($updateData);
|
||||
}
|
||||
$updatedCount++;
|
||||
$output->writeln("<info>已更新 inquiry_log ID {$inquiryLog->id}</info>");
|
||||
} else {
|
||||
$output->writeln("<comment>客户 ID {$customer->id} - 无需更新</comment>");
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln("<info>处理完成!</info>");
|
||||
$output->writeln("<info>总记录数: " . count($customers) . "</info>");
|
||||
$updateIdstr = implode(",", $updateId);
|
||||
$output->writeln("<info>更新记录数: {$updatedCount},更新id:$updateIdstr</info>");
|
||||
$stepstr = implode(",", $step);
|
||||
$output->writeln("<info>跳过记录数: {$skippedCount},跳过id: {$stepstr}</info>");
|
||||
|
||||
if ($isDryRun) {
|
||||
$output->writeln("<info>DRY RUN 模式 - 未实际执行任何更新操作</info>");
|
||||
}
|
||||
});
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>执行过程中发生错误: ' . $e->getMessage() . '</error>');
|
||||
$output->writeln('<error>错误位置: ' . $e->getFile() . ':' . $e->getLine() . '</error>');
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use support\Db;
|
||||
use app\common\utils\email\Emails;
|
||||
|
||||
/**
|
||||
* 批量处理用户编号(也用于调试)
|
||||
*/
|
||||
class VehMoneyCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'veh-money';
|
||||
protected static $defaultDescription = '';
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
|
||||
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace app\common\audit;
|
||||
|
||||
use Webman\Context;
|
||||
|
||||
class AuditBuffer
|
||||
{
|
||||
const CTX_KEY = '__audit_buffer__';
|
||||
|
||||
public array $changes = []; // 每项:['scene','object_id','field','old','new']
|
||||
public ?string $action = null; // 业务动作(可覆盖)
|
||||
public array $meta = []; // 业务自定义元信息(如需扩展入库,可在消费者处处理)
|
||||
public array $maskFields = ['password','pwd','token','secret','access_token','refresh_token'];
|
||||
|
||||
public static function instance(): self
|
||||
{
|
||||
$buf = Context::get(self::CTX_KEY);
|
||||
if (!$buf) {
|
||||
$buf = new self();
|
||||
Context::set(self::CTX_KEY, $buf);
|
||||
}
|
||||
return $buf;
|
||||
}
|
||||
|
||||
public static function reset(): void
|
||||
{
|
||||
Context::set(self::CTX_KEY, new self());
|
||||
}
|
||||
|
||||
public function setAction(?string $action): void
|
||||
{
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
public function setMeta(array $meta): void
|
||||
{
|
||||
$this->meta = $meta + $this->meta;
|
||||
}
|
||||
|
||||
/** 模型字段级变更推送(由 Trait 调用) */
|
||||
public function pushModelChange(string $scene, $objectId, array $old, array $new): void
|
||||
{
|
||||
$keys = array_unique(array_merge(array_keys($old), array_keys($new)));
|
||||
foreach ($keys as $k) {
|
||||
$ov = $old[$k] ?? null;
|
||||
$nv = $new[$k] ?? null;
|
||||
if ($ov === $nv) continue;
|
||||
|
||||
[$ovMasked, $nvMasked] = $this->maskIfNeeded($k, $ov, $nv);
|
||||
$this->changes[] = [
|
||||
'scene' => $scene,
|
||||
'object_id' => $objectId,
|
||||
'field' => $k,
|
||||
'old' => $ovMasked,
|
||||
'new' => $nvMasked,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function maskIfNeeded(string $field, $old, $new): array
|
||||
{
|
||||
foreach ($this->maskFields as $m) {
|
||||
if (stripos($field, $m) !== false) {
|
||||
return [$old===null?null:'***', $new===null?null:'***'];
|
||||
}
|
||||
}
|
||||
if (preg_match('/(mobile|phone|tel|idcard|id_card)/i', $field)) {
|
||||
$mask = function ($v) {
|
||||
if ($v === null) return null;
|
||||
$s = (string)$v;
|
||||
return mb_substr($s, 0, 3).'****'.mb_substr($s, -4);
|
||||
};
|
||||
return [$mask($old), $mask($new)];
|
||||
}
|
||||
return [$old, $new];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\service\AuthService;
|
||||
use app\common\validate\UserValidate;
|
||||
use support\exception\BusinessException;
|
||||
use support\Request;
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 不需要头信息的方法
|
||||
*/
|
||||
protected $noNeedHeaderData = ['feishuAuth'];
|
||||
|
||||
protected $noNeedLogin = ['feishuScope', 'login', 'register', 'feishuAuth'];
|
||||
|
||||
|
||||
// 调试
|
||||
public function feishuAuth(Request $request)
|
||||
{
|
||||
try {
|
||||
$code = $request->all();
|
||||
return $this->success($code);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function feishuScope(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = (new AuthService())->feishuAuthorize($request->get('app_id'), $request->system_code, $request->is_mob);
|
||||
return $this->success($result);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage().$e->getLine().$e->getFile());
|
||||
}
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $request->all();
|
||||
|
||||
if(empty($data['feishu_code']) || empty($data['app_id'])){
|
||||
throw new BusinessException('参数错误');
|
||||
}
|
||||
|
||||
$result = (new AuthService())->login($data);
|
||||
return $this->success($result, '登录成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage().$e->getLine().$e->getFile());
|
||||
}
|
||||
}
|
||||
|
||||
public function register(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_register');
|
||||
$result = (new AuthService())->register($data);
|
||||
return $this->success($result, '注册成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->requestData($request);
|
||||
if (!$data['refresh_token'] || !$data['system_code']) return $this->error('参数缺失', 422);
|
||||
$result = (new AuthService())->refreshToken($data['refresh_token'], $data['system_code']);
|
||||
return $this->success($result, '刷新成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// public function perm(Request $request)
|
||||
// {
|
||||
// try {
|
||||
// $data = $this->requestData($request);
|
||||
// if (!$data['access_token'] || !$data['system_code']) return $this->error('参数缺失', 422);
|
||||
// $result = (new AuthService())->logout($data['access_token'], $data['system_code']);
|
||||
// return $this->success($result, '已登出');
|
||||
// } catch (\Throwable $e) {}
|
||||
// }
|
||||
|
||||
// public function verify(Request $request)
|
||||
// {
|
||||
// try {
|
||||
// $token = $request->post('access_token') ?: $request->header('authorization');
|
||||
// $systemCode = $request->post('system_code');
|
||||
// if (!$token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
// $payload = (new AuthService())->verifyToken($token, $systemCode);
|
||||
// return $this->success($payload, '验证成功');
|
||||
// } catch (\Throwable $e) {
|
||||
// return $this->error($e->getMessage() ?: '无效或过期的token', 401);
|
||||
// }
|
||||
// }
|
||||
|
||||
// public function logout(Request $request)
|
||||
// {
|
||||
// $token = $request->post('access_token') ?: $request->header('Authorization');
|
||||
// $systemCode = $request->post('system_code') ?: $request->header('system_code');
|
||||
// (new AuthService())->logout($token, $systemCode);
|
||||
// return $this->success([], '已登出');
|
||||
// }
|
||||
|
||||
// public function blockToken(Request $request)
|
||||
// {
|
||||
// $token = $request->post('token');
|
||||
// $systemCode = $request->post('system_code');
|
||||
// if (!$token || !$systemCode) return $this->error('参数缺失', 422);
|
||||
// (new AuthService())->blockToken($token, $systemCode);
|
||||
// return $this->success([], '已强制停用该token');
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use support\exception\BusinessException;
|
||||
use app\common\traits\ResponseTrait;
|
||||
use app\common\traits\DataTransformer;
|
||||
use think\Validate;
|
||||
use support\Request;
|
||||
|
||||
class BaseController
|
||||
{
|
||||
/**
|
||||
* 响应处理、数据格式化处理
|
||||
*/
|
||||
use ResponseTrait, DataTransformer;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
* @var Validate
|
||||
*/
|
||||
protected mixed $validator = null;
|
||||
|
||||
/**
|
||||
* @var Model
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 不登录不鉴权
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* 登录不鉴权
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedAuth = [];
|
||||
|
||||
/**
|
||||
* 不用头部数据
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedHeaderData = [];
|
||||
|
||||
/**
|
||||
* 数据限制
|
||||
* null 不做限制,任何管理员都可以查看该表的所有数据
|
||||
* auth 管理员能看到自己以及自己的子管理员插入的数据
|
||||
* personal 管理员只能看到自己插入的数据
|
||||
* @var string
|
||||
*/
|
||||
protected $dataLimit = null;
|
||||
|
||||
/**
|
||||
* 数据限制字段
|
||||
*/
|
||||
protected $dataLimitField = 'admin_id';
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
*
|
||||
* @param Validate $validator 验证器实例
|
||||
* @param array $data 待验证的数据
|
||||
* @param string $scene 验证场景
|
||||
* @return array 验证后的数据
|
||||
* @throws BusinessException 验证失败时抛出业务异常
|
||||
*/
|
||||
public function validate(Validate $validator, array $data, string $scene = ''): array
|
||||
{
|
||||
// 如果指定了验证场景,并且验证器支持场景方法,则设置验证场景
|
||||
if ($scene && method_exists($validator, 'scene')) {
|
||||
$validator->scene($scene);
|
||||
}
|
||||
// 执行数据验证
|
||||
if (!$validator->check($data)) {
|
||||
// 验证失败,抛出业务异常,异常信息为验证器返回的错误信息
|
||||
throw new BusinessException($validator->getError());
|
||||
}
|
||||
// 验证成功,返回原始数据
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数处理
|
||||
*/
|
||||
public function requestData(Request $request)
|
||||
{
|
||||
return $request->all() + $request->basic_data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\service\OssService;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\admin\service\BrandService;
|
||||
use support\Request;
|
||||
|
||||
class BrandController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new BrandService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (!empty($data['logo']))
|
||||
$data['logo_full'] = (new OssService)->generatePrivateUrl($data['logo']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (empty($data['logo']))
|
||||
$data['logo_full'] = (new OssService)->generatePrivateUrl($data['logo']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 抓取品牌系列
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function crawlSeries(Request $request){
|
||||
try {
|
||||
$result = $this->service->crawlSeries($request);
|
||||
if($result){
|
||||
return $this->success($result,'成功');
|
||||
}else{
|
||||
return $this->error('抓取任务提交失败');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建被抓取且选中的品牌系列
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function crawlSeriesCreate(Request $request){
|
||||
try {
|
||||
$result = $this->service->crawlSeriesCreate($request);
|
||||
if($result){
|
||||
return $this->success([],$result);
|
||||
}else{
|
||||
return $this->error('创建失败');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\admin\service\BrandSeriesService;
|
||||
use support\Request;
|
||||
|
||||
class BrandSeriesController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new BrandSeriesService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 抓取系列车型
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function crawlModels(Request $request){
|
||||
try {
|
||||
$result = $this->service->crawlModel($request);
|
||||
if($result){
|
||||
return $this->success($result,'抓取成功');
|
||||
}else{
|
||||
return $this->error('抓取任务失败');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存抓取的系列车型
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function crawlModelCreate(Request $request){
|
||||
try {
|
||||
$result = $this->service->crawlModelCreate($request);
|
||||
if($result){
|
||||
return $this->success($result,'成功');
|
||||
}else{
|
||||
return $this->error('创建失败');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关键词抓取品牌、车系、车型
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function crawlKeyword(Request $request){
|
||||
try {
|
||||
$result = $this->service->crawlKeyword($request);
|
||||
if($result){
|
||||
return $this->success($result,'抓取成功');
|
||||
}else{
|
||||
return $this->error('抓取任务失败');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存抓取的系列车型
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function crawlKeywordCreate(Request $request){
|
||||
try {
|
||||
$result = $this->service->crawlKeywordCreate($request);
|
||||
if($result){
|
||||
return $this->success($result,'成功');
|
||||
}else{
|
||||
return $this->error('创建失败');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\model\Country;
|
||||
use app\common\model\FinPaymentStock;
|
||||
use app\common\model\StockOrders;
|
||||
use app\common\model\SysAccount;
|
||||
use app\common\model\SysCurrency;
|
||||
use app\common\model\SysDictItem;
|
||||
use app\common\model\User;
|
||||
use app\common\service\OssService;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\FinPaymentApplyService;
|
||||
|
||||
class FinPaymentApplyController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new FinPaymentApplyService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['doc_user_id'])) //内勤
|
||||
$data['doc_user_name'] = User::where('id', $data['doc_user_id'])->value('name')??'';
|
||||
if (isset($data['sales_user_id'])) //业务员
|
||||
$data['sales_user_name'] = User::where('id', $data['doc_user_id'])->value('name')??'';
|
||||
if (isset($data['corp_id'])) //公司(渠道)
|
||||
$data['corp_name'] = SysDictItem::where('id', $data['corp_id'])->value('item_label')??'';
|
||||
if (isset($data['account_id'])){ //收款账号
|
||||
$data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??'';
|
||||
$data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??'';
|
||||
}
|
||||
if (isset($data['country_id'])) //付款来源国家
|
||||
$data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??'';
|
||||
if (isset($data['currency_id'])) //付款币种
|
||||
$data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??'';
|
||||
$OssService = new OssService();
|
||||
if (!empty($data['submit_files'])){
|
||||
$data['submit_files'] = json_decode($data['submit_files'], true);
|
||||
foreach ($data['submit_files'] as $key => $value) {
|
||||
$data['submit_files'][$key] = [
|
||||
'file_src' => $value['file_src'],
|
||||
'file_full' => $OssService->generatePrivateUrl($value['file_src']),
|
||||
'file_name' => $value['file_name'],
|
||||
];
|
||||
}
|
||||
}else{
|
||||
$data['submit_files'] = [];
|
||||
}
|
||||
|
||||
if (!empty($data['voucher_file'])){
|
||||
$data['voucher_file'] = json_decode($data['voucher_file'], true);
|
||||
foreach ($data['voucher_file'] as $key => $value) {
|
||||
$data['voucher_file'][$key] = [
|
||||
'file_src' => $value['file_src'],
|
||||
'file_full' => $OssService->generatePrivateUrl($value['file_src']),
|
||||
'file_name' => $value['file_name'],
|
||||
];
|
||||
}
|
||||
}else{
|
||||
$data['voucher_file'] = [];
|
||||
}
|
||||
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (isset($data['claimed_at'])){
|
||||
$data['enter_file_last'] = date('Y-m-d H:i:s', ($data['claimed_at']+86400)); //入账资料最后提交时间
|
||||
$data['claimed_at'] = date('Y-m-d H:i:s', $data['claimed_at']); //认领时间
|
||||
}
|
||||
if (isset($data['id'])){
|
||||
$finPaymentStock = FinPaymentStock::where('payment_apply_id', $data['id'])->get();
|
||||
$stock = [];
|
||||
$stock_order_no = [];
|
||||
foreach ($finPaymentStock as $v) {
|
||||
$stock[$v['pi_no']]['pi_no'] = $v['pi_no'];
|
||||
$stock[$v['pi_no']]['stock_id'][] = [$v['stock_id']];
|
||||
$stockOrders = StockOrders::where('id', $v['stock_id'])->first();
|
||||
$stock_order_no[] = $stockOrders['order_no'];
|
||||
}
|
||||
$data['stock'] = array_values($stock);
|
||||
$data['stock_order_no'] = $stock_order_no;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function showFormat($data)
|
||||
{
|
||||
if (isset($data['doc_user_id'])) //内勤
|
||||
$data['doc_user_name'] = User::where('id', $data['doc_user_id'])->value('name')??'';
|
||||
if (isset($data['sales_user_id'])) //业务员
|
||||
$data['sales_user_name'] = User::where('id', $data['doc_user_id'])->value('name')??'';
|
||||
if (isset($data['corp_id'])) //公司(渠道)
|
||||
$data['corp_name'] = SysDictItem::where('id', $data['corp_id'])->value('item_label')??'';
|
||||
if (isset($data['account_id'])){ //收款账号
|
||||
$data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??'';
|
||||
$data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??'';
|
||||
}
|
||||
if (isset($data['country_id'])) //付款来源国家
|
||||
$data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??'';
|
||||
if (isset($data['currency_id'])) //付款币种
|
||||
$data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??'';
|
||||
$OssService = new OssService();
|
||||
if (!empty($data['submit_files'])){
|
||||
$data['submit_files'] = json_decode($data['submit_files'], true);
|
||||
foreach ($data['submit_files'] as $key => $value) {
|
||||
$data['submit_files'][$key] = [
|
||||
'file_src' => $value['file_src'],
|
||||
'file_full' => $OssService->generatePrivateUrl($value['file_src']),
|
||||
'file_name' => $value['file_name'],
|
||||
];
|
||||
}
|
||||
}else{
|
||||
$data['submit_files'] = [];
|
||||
}
|
||||
|
||||
if (!empty($data['voucher_file'])){
|
||||
$data['voucher_file'] = json_decode($data['voucher_file'], true);
|
||||
foreach ($data['voucher_file'] as $key => $value) {
|
||||
$data['voucher_file'][$key] = [
|
||||
'file_src' => $value['file_src'],
|
||||
'file_full' => $OssService->generatePrivateUrl($value['file_src']),
|
||||
'file_name' => $value['file_name'],
|
||||
];
|
||||
}
|
||||
}else{
|
||||
$data['voucher_file'] = [];
|
||||
}
|
||||
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
|
||||
$finPaymentStock = FinPaymentStock::where('payment_apply_id', $data['id'])->get();
|
||||
$stock = [];
|
||||
foreach ($finPaymentStock as $v) {
|
||||
$stock[$v['pi_no']]['pi_no'] = $v['pi_no'];
|
||||
$stock[$v['pi_no']]['stock_id'][] = [$v['stock_id']];
|
||||
}
|
||||
$data['stock'] = array_values($stock);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 作废
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function delete(Request $request)
|
||||
{
|
||||
try {
|
||||
$id = $request->input('id');
|
||||
$raw = $this->service->delete($id);
|
||||
return $this->success($raw);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\model\Country;
|
||||
use app\common\model\FinPaymentApply;
|
||||
use app\common\model\FinPaymentStock;
|
||||
use app\common\model\SysAccount;
|
||||
use app\common\model\SysCurrency;
|
||||
use app\common\model\SysDictItem;
|
||||
use app\common\model\User;
|
||||
use app\common\service\OssService;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\FinReceiptService;
|
||||
use support\Redis;
|
||||
use support\Request;
|
||||
|
||||
class FinReceiptController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new FinReceiptService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认入账
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function credit(Request $request)
|
||||
{
|
||||
try {
|
||||
$id = $request->input('id');
|
||||
$exchange_rate = $request->input('exchange_rate');
|
||||
if(empty($exchange_rate) || !is_numeric($exchange_rate)){
|
||||
return $this->error('请输入汇率');
|
||||
}
|
||||
$raw = $this->service->credit($id,$exchange_rate);
|
||||
return $this->success($raw);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function show(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceShow($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'showFormat')
|
||||
? [$this, 'showFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回更新成功的数据或信息
|
||||
*/
|
||||
public function payment(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'update');
|
||||
$res = $this->service->serviceUpdatePayment($request, $data);
|
||||
return $this->success([], '更新成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at'])){
|
||||
$data['claim_timeout'] = (($data['created_at'] < (time()-86400)) && empty($data['payment_apply_id'])) ?1:0;
|
||||
$data['claim_timeout_time'] = date('Y-m-d H:i:s', $data['created_at']+86400);
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
}
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (isset($data['arrived_at']))
|
||||
$data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']);
|
||||
if (isset($data['received_at'])){
|
||||
$data['received_at'] = date('Y-m-d H:i:s', $data['received_at']);
|
||||
$voucher_no_timeout_hour = redis::get('voucher_no_timeout_hour');
|
||||
if(!empty($voucher_no_timeout_hour)){
|
||||
$data['voucher_no_timeout_time'] = date('Y-m-d H:i:s', time()+(60*60*$voucher_no_timeout_hour));
|
||||
}
|
||||
}
|
||||
if (isset($data['account_id'])) { //收款账号
|
||||
$data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??'';
|
||||
$data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??'';
|
||||
}
|
||||
if (isset($data['country_id'])) //付款来源国家
|
||||
$data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??'';
|
||||
if (isset($data['currency_id'])) //付款币种
|
||||
$data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??'';
|
||||
|
||||
$OssService = new OssService();
|
||||
if (!empty($data['fx_receipt_file'])){
|
||||
$data['fx_receipt_file'] = json_decode($data['fx_receipt_file'], true);
|
||||
foreach ($data['fx_receipt_file'] as $key => $value) {
|
||||
$data['fx_receipt_file'][$key] = [
|
||||
'file_src' => $value['file_src'],
|
||||
'file_full' => $OssService->generatePrivateUrl($value['file_src']),
|
||||
'file_name' => $value['file_name'],
|
||||
];
|
||||
}
|
||||
}else{
|
||||
$data['fx_receipt_file'] = [];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (isset($data['arrived_at']))
|
||||
$data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']);
|
||||
if (isset($data['received_at']))
|
||||
$data['received_at'] = date('Y-m-d H:i:s', $data['received_at']);
|
||||
if (isset($data['account_id'])) { //收款账号
|
||||
$data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??'';
|
||||
$data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??'';
|
||||
}
|
||||
if (isset($data['country_id'])) //付款来源国家
|
||||
$data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??'';
|
||||
if (isset($data['currency_id'])) //付款币种
|
||||
$data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??'';
|
||||
|
||||
$OssService = new OssService();
|
||||
if (!empty($data['fx_receipt_file'])){
|
||||
$data['fx_receipt_file'] = json_decode($data['fx_receipt_file'], true);
|
||||
foreach ($data['fx_receipt_file'] as $key => $value) {
|
||||
$data['fx_receipt_file'][$key] = [
|
||||
'file_src' => $value['file_src'],
|
||||
'file_full' => $OssService->generatePrivateUrl($value['file_src']),
|
||||
'file_name' => $value['file_name'],
|
||||
];
|
||||
}
|
||||
}else{
|
||||
$data['fx_receipt_file'] = [];
|
||||
}
|
||||
|
||||
//查询关联的付款记录
|
||||
if (!empty($data['payment_apply_id'])) {
|
||||
$paymentApply = FinPaymentApply::where('id', $data['payment_apply_id'])->first();
|
||||
if(!$paymentApply)
|
||||
return $data;
|
||||
$paymentApply = $paymentApply->toArray();
|
||||
$paymentApply['created_at'] = strtotime($paymentApply['created_at']);
|
||||
$paymentApply['updated_at'] = strtotime($paymentApply['updated_at']);
|
||||
$paymentApply = (new FinPaymentApplyController())->showFormat($paymentApply);
|
||||
$data['payment_apply'] = $paymentApply;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收款记录统计
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function statistics(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->statistics();
|
||||
return $this->success($raw);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\model\Country;
|
||||
use app\common\model\SysAccount;
|
||||
use app\common\model\SysCurrency;
|
||||
use app\common\model\User;
|
||||
use app\common\service\OssService;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\FinReceiptHisService;
|
||||
use support\Request;
|
||||
|
||||
class FinReceiptHisController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new FinReceiptHisService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (isset($data['arrived_at']))
|
||||
$data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']);
|
||||
if (isset($data['received_at']))
|
||||
$data['received_at'] = date('Y-m-d H:i:s', $data['received_at']);
|
||||
if (isset($data['account_id'])) { //收款账号
|
||||
$data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??'';
|
||||
$data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??'';
|
||||
}
|
||||
if (isset($data['country_id'])) //付款来源国家
|
||||
$data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??'';
|
||||
if (isset($data['currency_id'])) //付款币种
|
||||
$data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??'';
|
||||
if (!empty($data['fx_receipt_file'])){
|
||||
$data['fx_receipt_file_full'] = (new OssService)->generatePrivateUrl($data['fx_receipt_file']);
|
||||
}elseif (isset($data['fx_receipt_file'])){
|
||||
$data['fx_receipt_file_full'] = '';
|
||||
}
|
||||
|
||||
if (isset($data['created_by']))
|
||||
$data['created_by'] = User::where('id', $data['created_by'])->value('name')??'';
|
||||
if (isset($data['updated_by']))
|
||||
$data['updated_by'] = User::where('id', $data['updated_by'])->value('name')??'';
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (isset($data['arrived_at']))
|
||||
$data['arrived_at'] = date('Y-m-d H:i:s', $data['arrived_at']);
|
||||
if (isset($data['received_at']))
|
||||
$data['received_at'] = date('Y-m-d H:i:s', $data['received_at']);
|
||||
if (isset($data['account_id'])){ //收款账号
|
||||
$data['account_name'] = SysAccount::where('id', $data['account_id'])->value('account_name')??'';
|
||||
$data['account_short_desc'] = SysAccount::where('id', $data['account_id'])->value('short_desc')??'';
|
||||
}
|
||||
if (isset($data['country_id'])) //付款来源国家
|
||||
$data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr')??'';
|
||||
if (isset($data['currency_id'])) //付款币种
|
||||
$data['currency_name'] = SysCurrency::where('id', $data['currency_id'])->value('currency_name')??'';
|
||||
if (!empty($data['fx_receipt_file'])){
|
||||
$data['fx_receipt_file_full'] = (new OssService)->generatePrivateUrl($data['fx_receipt_file']);
|
||||
}elseif (isset($data['fx_receipt_file'])){
|
||||
$data['fx_receipt_file_full'] = '';
|
||||
}
|
||||
|
||||
if (isset($data['created_by']))
|
||||
$data['created_by'] = User::where('id', $data['created_by'])->value('name')??'';
|
||||
if (isset($data['updated_by']))
|
||||
$data['updated_by'] = User::where('id', $data['updated_by'])->value('name')??'';
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\InvRequisitionApproveService;
|
||||
|
||||
class InvRequisitionApproveController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new InvRequisitionApproveService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\InvRequisitionAttachmentService;
|
||||
|
||||
class InvRequisitionAttachmentController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new InvRequisitionAttachmentService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\InvRequisitionItemService;
|
||||
|
||||
class InvRequisitionItemController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new InvRequisitionItemService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\InvRequisitionItemReturnAttachmentService;
|
||||
|
||||
class InvRequisitionItemReturnAttachmentController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new InvRequisitionItemReturnAttachmentService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\InvRequisitionItemReturnService;
|
||||
|
||||
class InvRequisitionItemReturnController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new InvRequisitionItemReturnService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\MessageNotificationsService;
|
||||
use app\common\model\MessageNotifications;
|
||||
use support\Request;
|
||||
|
||||
class MessageNotificationsController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new MessageNotificationsService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function push(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'store');
|
||||
$res = $this->service->servicePush($request, $data);
|
||||
return $this->success($res, '推送成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\MessageUserStatusService;
|
||||
use support\Request;
|
||||
|
||||
class MessageUserStatusController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new MessageUserStatusService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['read_at']) && $data['read_at'] !== null) {
|
||||
$data['read_at'] = date('Y-m-d H:i:s', $data['read_at']);
|
||||
} else {
|
||||
$data['read_at'] = ' - ';
|
||||
}
|
||||
|
||||
if (isset($data['message']))
|
||||
$data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['read_at']) && $data['read_at'] !== null) {
|
||||
$data['read_at'] = date('Y-m-d H:i:s', $data['read_at']);
|
||||
} else {
|
||||
$data['read_at'] = ' - ';
|
||||
}
|
||||
|
||||
if (isset($data['message']))
|
||||
$data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function unReadFormat($data)
|
||||
{
|
||||
foreach ($data as &$item) {
|
||||
if (isset($item['read_at']) && $item['read_at'] !== null) {
|
||||
$item['read_at'] = date('Y-m-d H:i:s', $item['read_at']);
|
||||
} else {
|
||||
$item['read_at'] = ' - ';
|
||||
}
|
||||
|
||||
if (isset($item['message'])) {
|
||||
$item['message']['created_at'] = date('Y-m-d H:i:s', $item['message']['created_at']);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function read(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'read');
|
||||
$this->service->serviceRead($request, $data);
|
||||
return $this->success([], '操作成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function listUnRead(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceListUnRead($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'unReadFormat')
|
||||
? [$this, 'unReadFormat']
|
||||
: null);
|
||||
$data = [
|
||||
'list' => $data,
|
||||
'counts' => count($data)
|
||||
];
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use app\common\utils\FeishuMessage;
|
||||
use app\inquiry\service\InquiryLogService;
|
||||
use app\common\model\CrmCustomer;
|
||||
use app\common\model\InquiryLog;
|
||||
use app\common\model\User;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class NotifyController
|
||||
{
|
||||
|
||||
/**
|
||||
* 不用头部数据
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedHeaderData = ['feishuCallback', 'feishuEvent'];
|
||||
|
||||
/**
|
||||
* 不登录不鉴权
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['feishuCallback', 'feishuEvent'];
|
||||
|
||||
/**
|
||||
* 飞书回调
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function feishuCallback(Request $request)
|
||||
{
|
||||
try {
|
||||
$param = $request->post();
|
||||
|
||||
// 解析数据
|
||||
// $param = json_decode($param['event'], true);
|
||||
$orderSn = $param['event']['action']['value'];
|
||||
if (strpos($orderSn, '"') !== false) {
|
||||
$orderSn = str_replace('"', '', $orderSn);
|
||||
}
|
||||
|
||||
// 处理询单&客户线索信息
|
||||
$openId = $param['event']['operator']['open_id'];
|
||||
|
||||
// 获取询单
|
||||
$order = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('order_no', $orderSn)->first();
|
||||
if (!$order) {
|
||||
return $this->error('未找到询单记录');
|
||||
}
|
||||
|
||||
// 获取客户xians
|
||||
$customer = CrmCustomer::where('sn', $orderSn)->first();
|
||||
if (!$customer) {
|
||||
return $this->error('未找到客户线索');
|
||||
}
|
||||
|
||||
// 获取用户
|
||||
$uinfo = User::where('open_id', $openId)->first();
|
||||
|
||||
// 更新客户线索
|
||||
$customer->user_id = $uinfo->id;
|
||||
|
||||
// 更新询单数据
|
||||
$order->recipient = $uinfo->id;
|
||||
$order->status = 2;
|
||||
$order->t_time = time();
|
||||
|
||||
// 保存
|
||||
if (!$order->save() || !$customer->save()) {
|
||||
return $this->error('跟踪失败');
|
||||
}
|
||||
|
||||
// 获取飞书消息模板
|
||||
$feishuMessage = new FeishuMessage();
|
||||
$content = $feishuMessage->groupUpdateTemplates(
|
||||
(new InquiryLogService())->qualityName($order->quality),
|
||||
$order->order_no,
|
||||
$order->channelInfo ? $order->channelInfo->name : '',
|
||||
$order->countryInfo ? $order->countryInfo->cn_addr : '',
|
||||
$order->enter ? $order->enter->name : '',
|
||||
$order->r_time ? date('Y-m-d H:i:s', $order->r_time) : '',
|
||||
$order->t_time ? date('Y-m-d H:i:s', $order->t_time) : ''
|
||||
);
|
||||
|
||||
// 组装飞书消息模板
|
||||
$data = '{
|
||||
"toast": {
|
||||
"type": "success",
|
||||
"content": "操作成功"
|
||||
},
|
||||
"card": {
|
||||
"type": "raw",
|
||||
"data": '.$content.'
|
||||
}
|
||||
}';
|
||||
|
||||
// 响应消息
|
||||
return new Response(200, ['Content-Type' => 'application/json'], $data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function error($msg = '')
|
||||
{
|
||||
$data = '{
|
||||
"toast": {
|
||||
"type": "error",
|
||||
"content": "'.$msg.'"
|
||||
}
|
||||
}';
|
||||
return new Response(200, ['Content-Type' => 'application/json'], $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 飞书事件
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function feishuEvent(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $request->post();
|
||||
|
||||
var_dump($data);
|
||||
|
||||
return json($data);
|
||||
} catch (\Throwable $e) {
|
||||
return json($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\admin\service\RoleService;
|
||||
use app\common\service\BrandSeriesService;
|
||||
use app\common\service\BrandService;
|
||||
use app\common\service\SysAccountService;
|
||||
use app\common\service\SysCurrencyService;
|
||||
use app\common\service\SysDictItemService;
|
||||
use app\common\service\SysDictService;
|
||||
use app\common\service\SysPortService;
|
||||
use app\common\service\VehColorService;
|
||||
use app\common\service\VehModificationService;
|
||||
use support\Request;
|
||||
use app\common\service\CorpService;
|
||||
use app\common\service\CorpFeishuAppService;
|
||||
use app\common\service\SystemsService;
|
||||
use app\common\service\UserService;
|
||||
use support\Cache;
|
||||
use app\common\service\PermService;
|
||||
use app\common\service\UserGroupService;
|
||||
|
||||
class PresetController extends BaseController
|
||||
{
|
||||
|
||||
protected $noNeedLogin = ['corps', 'feishuApp', 'systems'];
|
||||
|
||||
protected $noNeedHeaderData = ['corps', 'feishuApp', 'systems'];
|
||||
|
||||
public function corps()
|
||||
{
|
||||
try {
|
||||
$result = (new CorpService())->getList();
|
||||
return $this->success($result, '获取公司列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function feishuApp(Request $request)
|
||||
{
|
||||
try {
|
||||
$corpId = $request->get('corp_id');
|
||||
$systemId = $request->get('system_id');
|
||||
$result = (new CorpFeishuAppService())->getFeishuApp($corpId, $systemId);
|
||||
return $this->success($result, '获取飞书应用信息');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function systems()
|
||||
{
|
||||
try {
|
||||
$result = (new SystemsService())->getList();
|
||||
return $this->success($result, '获取系统列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function users(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = (new UserService())->getList($request);
|
||||
return $this->success($result, '获取用户列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function roles()
|
||||
{
|
||||
try {
|
||||
$result = (new RoleService)->getList();
|
||||
return $this->success($result, '获取角色列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
try {
|
||||
$result = (new SysCurrencyService)->getList();
|
||||
return $this->success($result, '获取货币列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function brand()
|
||||
{
|
||||
try {
|
||||
$result = (new BrandService)->getList();
|
||||
return $this->success($result, '获取品牌列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function series(Request $request)
|
||||
{
|
||||
try {
|
||||
$params = $request->all();
|
||||
$result = (new BrandSeriesService)->getList($params);
|
||||
return $this->success($result, '获取车系列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
try {
|
||||
$result = (new SysAccountService)->getList();
|
||||
return $this->success($result, '获取账号列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function dict()
|
||||
{
|
||||
try {
|
||||
$result = (new SysDictService)->getList();
|
||||
return $this->success($result, '获取字典列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function dictItem(Request $request)
|
||||
{
|
||||
try {
|
||||
$params = $request->all();
|
||||
$result = (new SysDictItemService)->getList($params);
|
||||
return $this->success($result, '获取字典项列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function port(Request $request)
|
||||
{
|
||||
try {
|
||||
$params = $request->all();
|
||||
$result = (new SysPortService)->getList($params);
|
||||
return $this->success($result, '获取港口列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function color(Request $request)
|
||||
{
|
||||
try {
|
||||
$params = $request->all();
|
||||
$result = (new VehColorService)->getList($params);
|
||||
return $this->success($result, '获取颜色列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function modification()
|
||||
{
|
||||
try {
|
||||
$result = (new VehModificationService)->getList();
|
||||
return $this->success($result, '获取货币列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function cacheClear()
|
||||
{
|
||||
try {
|
||||
Cache::clear();
|
||||
return $this->success([], '操作成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function menus(Request $request)
|
||||
{
|
||||
try {
|
||||
$uid = $request->user->uid;
|
||||
$systemCode = $request->system_code;
|
||||
$permService = new PermService();
|
||||
$menuTree = $permService->getMenuTree($uid, $systemCode);
|
||||
$permissionCodes = $permService->getPermissionCodes($uid, $systemCode);
|
||||
$result = [
|
||||
'menu' => $menuTree,
|
||||
'permissions' => $permissionCodes
|
||||
];
|
||||
return $this->success($result, '获取菜单权限');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function userGroup(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = (new UserGroupService)->userGroup($request);
|
||||
return $this->success($result, '获取分组');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function userGroupScene(Request $request)
|
||||
{
|
||||
try {
|
||||
$result = (new UserGroupService)->userGroupScene($request);
|
||||
return $this->success($result, '获取分组场景');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\PurchaseOrderItemsService;
|
||||
|
||||
class PurchaseOrderItemsController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new PurchaseOrderItemsService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\PurchaseOrdersService;
|
||||
use support\Request;
|
||||
|
||||
class PurchaseOrdersController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new PurchaseOrdersService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['records']) && count($data['records']) > 0) {
|
||||
foreach ($data['records'] as &$record) {
|
||||
$contractFile = $record['contract_file'] ? explode(',', $record['contract_file']) : [];
|
||||
$contractFileUrl = [];
|
||||
if (count($contractFile) > 0) {
|
||||
foreach ($contractFile as $file) {
|
||||
$contractFileUrl[] = (new \app\common\service\OssService)->generatePrivateUrl($file);
|
||||
}
|
||||
}
|
||||
$result = [
|
||||
'id' => $record['id'],
|
||||
'order_no' => $record['order_no'],
|
||||
'pi_no' => $record['pi_no'],
|
||||
'buyer_id' => $record['buyer_id'],
|
||||
'buyer_name' => $record['buyer_name'],
|
||||
'contract_file' => $contractFile,
|
||||
'contract_file_url' => $contractFileUrl,
|
||||
'status' => $record['status'],
|
||||
'status_text' => array_flip(\app\common\service\SysDictService::pluckKeyValueByCode('purchase_status'))[$record['status']],
|
||||
'remark' => $record['remark'],
|
||||
'created_at' => date('Y-m-d H:i:s', $record['created_at']),
|
||||
'updated_at' => date('Y-m-d H:i:s', $record['updated_at']),
|
||||
];
|
||||
$record = $result;
|
||||
}
|
||||
unset($record);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function showFormat($data)
|
||||
{
|
||||
$contractFile = $data['contract_file'] ? explode(',', $data['contract_file']) : [];
|
||||
$contractFileUrl = [];
|
||||
if (count($contractFile) > 0) {
|
||||
foreach ($contractFile as $file) {
|
||||
$contractFileUrl[] = (new \app\common\service\OssService)->generatePrivateUrl($file);
|
||||
}
|
||||
}
|
||||
$result = [
|
||||
'id' => $data['id'],
|
||||
'order_no' => $data['order_no'],
|
||||
'pi_no' => $data['pi_no'],
|
||||
'buyer_id' => $data['buyer_id'],
|
||||
'buyer_name' => $data['buyer_name'],
|
||||
'contract_file' => $contractFile,
|
||||
'contract_file_url' => $contractFileUrl,
|
||||
'status' => $data['status'],
|
||||
'remark' => $data['remark']
|
||||
];
|
||||
$temps = [];
|
||||
if (isset($data['items']) && count($data['items']) > 0) {
|
||||
foreach ($data['items'] as $record) {
|
||||
if (!in_array($record['stockOrder']['id'], array_keys($temps))) {
|
||||
$temps[$record['stockOrder']['id']] = [
|
||||
'id' => $record['stockOrder']['id'],
|
||||
'order_no' => $record['stockOrder']['order_no'],
|
||||
'salesperson_snapshot' => $record['stockOrder']['salesperson_snapshot'],
|
||||
'biz_date' => date('Y-m-d', $record['stockOrder']['biz_date'])
|
||||
];
|
||||
}
|
||||
$temps[$record['stockOrder']['id']]['stock_order_items'][] =[
|
||||
'id' => $record['stockOrderItems']['id'],
|
||||
'model_snapshot' => $record['stockOrderItems']['model_snapshot'],
|
||||
'unique_id' => $record['stockOrderItems']['unique_id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
$result['stock_order'] = array_values($temps);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前置查询处理
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function preQueryData(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->service->preQueryData($request->all());
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重提
|
||||
*
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function resubmit(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceResubmit($request, $request->all());
|
||||
return $this->success($raw);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\ResourceService;
|
||||
use support\Request;
|
||||
|
||||
class ResourceController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new ResourceService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function allFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源树形结构
|
||||
*/
|
||||
public function listTree(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceListAll($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'allFormat')
|
||||
? [$this, 'allFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单
|
||||
*/
|
||||
public function menuList(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceMenuList($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'allFormat')
|
||||
? [$this, 'allFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单
|
||||
*/
|
||||
public function menuTreeList(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceMenuTreeList($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'allFormat')
|
||||
? [$this, 'allFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\StockOrdersFilesService;
|
||||
|
||||
class StockOrdersFilesController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new StockOrdersFilesService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\SysOpLogService;
|
||||
|
||||
class SysOpLogController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new SysOpLogService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\model\Country;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\admin\service\SysPortService;
|
||||
|
||||
class SysPortController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new SysPortService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (isset($data['country_id']))
|
||||
if (isset($data['country_id']))
|
||||
$data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr');
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
$data['country_name'] = Country::where('id', $data['country_id'])->value('cn_addr');
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\SysUpdateLogService;
|
||||
|
||||
class SysUpdateLogController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new SysUpdateLogService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['update_time']))
|
||||
$data['update_time'] = date('Y-m-d H:i:s', $data['update_time']);
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\service\OssService;
|
||||
use app\common\service\UploadedFilesService;
|
||||
use app\common\service\UploadService;
|
||||
use app\common\traits\ResponseTrait;
|
||||
use support\Log;
|
||||
use support\Request;
|
||||
use support\exception\BusinessException;
|
||||
use Shopwwi\WebmanFilesystem\Facade\Storage;
|
||||
|
||||
class UploadController
|
||||
{
|
||||
|
||||
use ResponseTrait;
|
||||
|
||||
protected $noNeedLogin = ['proxy'];
|
||||
|
||||
protected $noNeedHeaderData = ['proxy'];
|
||||
|
||||
public function upload(Request $request)
|
||||
{
|
||||
try {
|
||||
$scene = $request->input('scene', 'image');
|
||||
$file = $request->file('file');
|
||||
if ($file && $file->isValid()) {
|
||||
$fileName = md5($file->getUploadName() . time()).'.'.$file->getUploadExtension();
|
||||
$pathUrl = '/uploads/'.$scene.'/'.date('Ymd').'/'.$fileName;
|
||||
$fullPathUrl = public_path() . $pathUrl;
|
||||
$file->move($fullPathUrl);
|
||||
|
||||
$data = [
|
||||
'domain' => getenv('MAIN_DOMAIN'),
|
||||
'file_url' => $pathUrl,
|
||||
'full_url' => getenv('MAIN_DOMAIN') . $pathUrl,
|
||||
'scene' => $scene,
|
||||
];
|
||||
|
||||
// 记录文件信息到数据库
|
||||
try {
|
||||
$uploadedFilesService = new UploadedFilesService();
|
||||
$partition = $scene;
|
||||
$category = $request->input('category', 'general');
|
||||
|
||||
$fileInfo = [
|
||||
'file_name' => basename($fileName),
|
||||
'file_path' => $pathUrl,
|
||||
'file_size' => $file->getSize(),
|
||||
'file_type' => $file->getUploadMimeType(),
|
||||
'original_name' => $file->getUploadName(),
|
||||
];
|
||||
|
||||
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, $request, $partition, $category);
|
||||
$data['file_id'] = $recordResult['id'];
|
||||
$data['partition'] = $partition;
|
||||
$data['category'] = $category;
|
||||
} catch (\Throwable $e) {
|
||||
// 记录失败不影响上传结果,只记录日志
|
||||
Log::error('记录文件信息失败: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return $this->success($data, 'upload success');
|
||||
}
|
||||
return $this->error('file not found');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 上传单文件附件
|
||||
public function uploadFile(Request $request)
|
||||
{
|
||||
try {
|
||||
$scene = $request->input('scene');
|
||||
$file = $request->file('file');
|
||||
|
||||
if (!$file || !$file->isValid()) {
|
||||
return $this->error('上传失败');
|
||||
}
|
||||
if (empty($scene)) {
|
||||
// return $this->error('上传场景不能为空');
|
||||
$sceneUrl = '';
|
||||
}else{
|
||||
$sceneUrl = $scene . '/';
|
||||
}
|
||||
$originalName = $file->getUploadName();
|
||||
|
||||
$ossservice = new OssService();
|
||||
$mimes = $ossservice->getMimeTypes(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar']);
|
||||
|
||||
$result = Storage::adapter('oss')
|
||||
->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd'))
|
||||
->size(1024*1024*5)
|
||||
->extYes($mimes)
|
||||
->upload($file);
|
||||
|
||||
|
||||
$data = [
|
||||
'original_name' => $originalName,
|
||||
'file_name' => $result->file_name,
|
||||
'file_url' => $ossservice->generatePrivateUrl($result->file_name)
|
||||
];
|
||||
|
||||
// 记录文件信息到数据库
|
||||
$category = $request->input('category', 'general');
|
||||
$res = $this->recordFileInfo($result,$request,$file,$scene,$category);
|
||||
$data = array_merge($data, $res);
|
||||
|
||||
return $this->success($data);
|
||||
}catch (\Throwable $e){
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 上传单文件图片
|
||||
public function uploadImage(Request $request)
|
||||
{
|
||||
try {
|
||||
$scene = $request->input('scene');
|
||||
$file = $request->file('image');
|
||||
|
||||
if (!$file || !$file->isValid()) {
|
||||
return $this->error('上传失败');
|
||||
}
|
||||
if (empty($scene)) {
|
||||
// return $this->error('上传场景不能为空');
|
||||
$sceneUrl = '';
|
||||
}else{
|
||||
$sceneUrl = $scene . '/';
|
||||
}
|
||||
|
||||
$result = Storage::adapter('oss')
|
||||
->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd'))
|
||||
->size(1024*1024*5)
|
||||
->extYes(['image/jpeg','image/png'])
|
||||
->extNo(['image/gif'])
|
||||
->upload($file);
|
||||
|
||||
$ossservice = new OssService();
|
||||
|
||||
$data = [
|
||||
'href' => $result->file_name,
|
||||
'alt' => '',
|
||||
'url' => $ossservice->generatePrivateUrl($result->file_name)
|
||||
];
|
||||
|
||||
// 记录文件信息到数据库
|
||||
$category = $request->input('category', 'general');
|
||||
$res = $this->recordFileInfo($result,$request,$file,$scene,$category);
|
||||
$data = array_merge($data, $res);
|
||||
|
||||
return json(['errno' => 0, 'data' => $data]);
|
||||
// return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return json(['errno' => 1, 'message' => $e->getMessage()]);
|
||||
// return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function recordFileInfo($result, $request, $file, $scene, $category)
|
||||
{
|
||||
try {
|
||||
$uploadedFilesService = new UploadedFilesService();
|
||||
$partition = $scene;
|
||||
|
||||
$fileInfo = [
|
||||
'file_name' => basename($result->file_name),
|
||||
'file_path' => $result->file_name, // OSS存储路径
|
||||
'file_size' => $file->getSize(),
|
||||
'file_type' => $file->getUploadMimeType(),
|
||||
'original_name' => $file->getUploadName(),
|
||||
];
|
||||
|
||||
$partition = !empty($partition)?$partition:'other';
|
||||
$category = !empty($category)?$category:'other';
|
||||
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, $request, $partition, $category);
|
||||
$data = [];
|
||||
$data['file_id'] = $recordResult['id'];
|
||||
$data['partition'] = $partition;
|
||||
$data['category'] = $category;
|
||||
return $data;
|
||||
} catch (\Throwable $e) {
|
||||
// 记录失败不影响上传结果,只记录日志
|
||||
Log::error('记录文件信息失败: ' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function getFileInfo(Request $request)
|
||||
{
|
||||
try {
|
||||
$fileId = $request->input('id');
|
||||
|
||||
if (!$fileId) {
|
||||
return $this->error('文件ID不能为空');
|
||||
}
|
||||
|
||||
$uploadedFilesService = new UploadedFilesService();
|
||||
$fileInfo = $uploadedFilesService->getFileInfo($fileId);
|
||||
|
||||
if (!$fileInfo) {
|
||||
return $this->error('文件不存在');
|
||||
}
|
||||
|
||||
return $this->success($fileInfo);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error('获取文件信息失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function deleteFile(Request $request)
|
||||
{
|
||||
try {
|
||||
$fileId = $request->input('id');
|
||||
$path = $request->input('path');
|
||||
|
||||
$uploadedFilesService = new UploadedFilesService();
|
||||
if(!empty($path) && empty($fileId)){
|
||||
$where = ['file_path' => $path];
|
||||
$file = $uploadedFilesService->getFile($where);
|
||||
$fileId = $file->id;
|
||||
}elseif(empty($path) && !empty($fileId)){
|
||||
$where = ['id' => $fileId];
|
||||
$file = $uploadedFilesService->getFile($where);
|
||||
$path = $file->file_path;
|
||||
}
|
||||
if (!$fileId) {
|
||||
return $this->error('文件ID与路径不能为空');
|
||||
}
|
||||
|
||||
// 删除 OSS 上的文件
|
||||
$ossService = new OssService();
|
||||
$ossService->deleteFile($path);
|
||||
|
||||
$result = $uploadedFilesService->deleteFile($fileId);
|
||||
|
||||
if ($result) {
|
||||
return $this->success([], '文件删除成功');
|
||||
} else {
|
||||
return $this->error('文件删除失败');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error('文件删除失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分区配置
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function getPartitionConfig(Request $request)
|
||||
{
|
||||
try {
|
||||
$uploadedFilesService = new UploadedFilesService();
|
||||
$config = $uploadedFilesService->getPartitionConfig();
|
||||
|
||||
return $this->success($config);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error('获取分区配置失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function proxy(Request $request)
|
||||
{
|
||||
$filePath = $request->get('file'); // 原OSS文件路径
|
||||
try {
|
||||
// 生成签名URL
|
||||
$signedUrl = (new \app\common\service\OssService())->generatePrivateUrl($filePath);
|
||||
|
||||
// 获取图片数据并返回
|
||||
$imageData = file_get_contents($signedUrl);
|
||||
|
||||
// 根据文件类型设置Content-Type
|
||||
$mime = mime_content_type('data://application/octet-stream;base64,'.base64_encode($imageData));
|
||||
|
||||
return response($imageData, 200, [
|
||||
'Content-Type' => $mime,
|
||||
'Cache-Control' => 'public, max-age=600' // 客户端缓存10分钟
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response('Image not found '.$e->getMessage(), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\UsedcarCrawlResultsService;
|
||||
|
||||
class UsedcarCrawlResultsController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new UsedcarCrawlResultsService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\UsedcarCrawlResultsImagesService;
|
||||
|
||||
class UsedcarCrawlResultsImagesController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new UsedcarCrawlResultsImagesService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use support\Request;
|
||||
use app\common\service\UserService;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new UserService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组员列表
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回分页数据及总数
|
||||
*/
|
||||
public function team(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceTeam($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'indexFormat')
|
||||
? [$this, 'indexFormat']
|
||||
: null);
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记组员离职
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @return mixed 返回分页数据及总数
|
||||
*/
|
||||
public function resignMember(Request $request)
|
||||
{
|
||||
try {
|
||||
$userId = $request->get('user_id');
|
||||
if(empty($userId)){
|
||||
return $this->error('组员id不能为空');
|
||||
}
|
||||
$user = $this->service->resignMember($request);
|
||||
return $this->success($user, '获取用户信息');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息(登录后调用)
|
||||
*/
|
||||
public function info(Request $request)
|
||||
{
|
||||
try {
|
||||
$uid = $request->user->uid;
|
||||
$systemCode = $request->system_code;
|
||||
$result = (new UserService())->getUserInfo($uid, $systemCode);
|
||||
return $this->success($result, '获取用户信息');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
public function getUser(Request $request)
|
||||
{
|
||||
try {
|
||||
$uid = $request->user->uid;
|
||||
$corp_abbr = $request->basic_data['corp_abbr'];
|
||||
$result = (new UserService())->getUser($uid,$corp_abbr);
|
||||
return $this->success($result, '获取用户信息');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑用户
|
||||
*/
|
||||
public function edit(Request $request)
|
||||
{
|
||||
try {
|
||||
$uid = $request->user->uid;
|
||||
$data = $request->all();
|
||||
$result = (new UserService())->edit($uid,$data);
|
||||
unset($result['password']);
|
||||
unset($result['union_id']);
|
||||
unset($result['open_id']);
|
||||
unset($result['tenant_key']);
|
||||
return $this->success($result, '编辑成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws \Random\RandomException
|
||||
*/
|
||||
public function savePassword(Request $request)
|
||||
{
|
||||
try {
|
||||
$uid = $request->user->uid;
|
||||
$data = $request->all();
|
||||
$result = (new UserService())->savePassword($uid,$data);;
|
||||
return $this->success($result, '编辑成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\admin\service\VehColorService;
|
||||
|
||||
class VehColorController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehColorService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (isset($data['created_by']) && $data['created_by'])
|
||||
$data['created_by_name'] = User::where('id', $data['created_by'])->value('name');
|
||||
if (isset($data['updated_by']) && $data['updated_by'])
|
||||
$data['updated_by_name'] = User::where('id', $data['updated_by'])->value('name');
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
if (isset($data['created_by']) && $data['created_by'])
|
||||
$data['created_by_name'] = User::where('id', $data['created_by'])->value('name');
|
||||
if (isset($data['updated_by']) && $data['updated_by'])
|
||||
$data['updated_by_name'] = User::where('id', $data['updated_by'])->value('name');
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehDictI18nService;
|
||||
|
||||
class VehDictI18nController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehDictI18nService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehDictMappingService;
|
||||
|
||||
class VehDictMappingController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehDictMappingService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\admin\service\VehModificationService;
|
||||
|
||||
class VehModificationController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehModificationService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehMoneyValueService;
|
||||
|
||||
class VehMoneyValueController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehMoneyValueService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehVehicleListingService;
|
||||
|
||||
class VehVehicleListingController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehVehicleListingService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\model\BrandSeries;
|
||||
use app\common\service\VehModificationService;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehVehicleSkuService;
|
||||
use support\exception\BusinessException;
|
||||
use support\Request;
|
||||
|
||||
class VehVehicleSkuController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehVehicleSkuService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
return $this->error('请优先抓取车辆信息,如抓取不到相应车型请联系管理员,暂停手动录入车辆信息');
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'store');
|
||||
$res = $this->service->serviceStore($request, $data);
|
||||
return $this->success($res, '创建成功');
|
||||
} catch (\PDOException $e) {
|
||||
$errorCode = $e->getCode();
|
||||
$errorMessage = $e->getMessage();
|
||||
if ($errorCode == '23000') {
|
||||
if (strpos($errorMessage, "Duplicate entry") !== false && strpos($errorMessage, "uk_fingerprint") !== false) {
|
||||
return $this->error("该车辆指纹已存在,请勿重复提交");
|
||||
}
|
||||
// 其他类型的完整性约束错误
|
||||
throw new BusinessException("数据完整性错误", 0, $e);
|
||||
}
|
||||
// 重新抛出其他类型的异常
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'update');
|
||||
$res = $this->service->serviceUpdate($request, $data);
|
||||
return $this->success([], '更新成功');
|
||||
} catch (\PDOException $e) {
|
||||
$errorCode = $e->getCode();
|
||||
$errorMessage = $e->getMessage();
|
||||
if ($errorCode == '23000') {
|
||||
if (strpos($errorMessage, "Duplicate entry") !== false && strpos($errorMessage, "uk_fingerprint") !== false) {
|
||||
return $this->error("该车辆指纹已存在,请勿重复提交");
|
||||
}
|
||||
// 其他类型的完整性约束错误
|
||||
throw new BusinessException("数据完整性错误", 0, $e);
|
||||
}
|
||||
// 重新抛出其他类型的异常
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function preset(Request $request){
|
||||
|
||||
|
||||
try {
|
||||
$series_id = $request->input('series_id');
|
||||
$series_code = BrandSeries::where('id', $series_id)->value('series_code');
|
||||
$data = [
|
||||
'series_code' => $series_code,
|
||||
];
|
||||
$result = $this->service->getList($data);
|
||||
return $this->success($result, '获取车型列表');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function fields_desc(Request $request)
|
||||
{
|
||||
$data = [
|
||||
'sku_code' , // 'SKU编码,系统生成',
|
||||
'fingerprint' , // '规格指纹(sha256)去重',
|
||||
'brand_code' , // '品牌代码,如 BYD、HYUNDAI、KIA',
|
||||
'brand_name' , // '品牌名称展示,如 比亚迪、现代、起亚',
|
||||
'series_code' , // '车系代码,如 ELANTRA、SONGPLUS',
|
||||
'series_name' , // '车系名称展示',
|
||||
'model_code' , // '车型/配置代码(工厂/内部编码)',
|
||||
'model_name' , // '车型/配置名称(可读,如 伊兰特 1.5L、海狮07)',
|
||||
'model_year' , // '年款(YYYY)',
|
||||
'trim_code' , // '版型代码(TRIM)',
|
||||
'trim_level_code' , // '配置等级代码(TRIM_LEVEL)',
|
||||
'edition_code' , // '特别版代码(EDITION)',
|
||||
'trim' , // '版型/细分配置(如 TOP旗舰版、荣耀版)',
|
||||
'trim_level' , // '配置等级(豪华型/旗舰型等,便于筛选)',
|
||||
'edition_name' , // '特别版/纪念版(如 荣耀版、致敬版等)',
|
||||
'body_type_code' , // '车身类型:SEDAN/SUV/HB/MPV/Pickup',
|
||||
'drive_type_code' , // '驱动:FWD/RWD/AWD/4WD',
|
||||
'motor_layout_code' , // '电驱/电机布置(MOTOR_LAYOUT):FWD/RWD/AWD/DM-i/DM-p 等',
|
||||
'fuel_type_code' , // '能源:GAS/HEV/PHEV/EV/EREV',
|
||||
'engine_disp_l' , // '排量(L),如 1.5;纯电为空',
|
||||
'engine_turbo' , // '0' COMMENT '是否涡轮(0/1)',
|
||||
'engine_power_kw' , // '最大功率(kW)',
|
||||
'battery_kwh' , // '电池容量(kWh)',
|
||||
'transmission_code' , // '变速箱:MT/AT/CVT/DCT/1SPEED',
|
||||
'gears' , // '挡位数(EV多为1)',
|
||||
'range_km' , // '续航里程(km)',
|
||||
'range_std_code' , // '续航标准:CLTC/WLTP/EPA/NEDC',
|
||||
'doors' , // '门数',
|
||||
'seats' , // '座位数',
|
||||
'weight_kg' , // '整备质量(kg)',
|
||||
'dimensions_mm' , // '尺寸(mm):{length,width,height,wheelbase}',
|
||||
'color_exterior_code' , // '外观色代码',
|
||||
'color_exterior_text' , // '外观色名称',
|
||||
'color_interior_code' , // '内饰色代码',
|
||||
'color_interior_text' , // '内饰色名称',
|
||||
'options_official' , // '官方选装(结构化 JSON)',
|
||||
'features' , // '功能/安全/智能配置(结构化 JSON)',
|
||||
'emission_std_code' , // '排放标准:CHN6/EURO6/EPA',
|
||||
'origin_country' , // '原产国 ISO-3166-1 alpha-2',
|
||||
'currency' , // 'CNY' COMMENT '币种 ISO-4217',
|
||||
'msrp' , // '建议零售价/参考价',
|
||||
'is_active' , // '1' COMMENT '启用(1)/禁用(0)',
|
||||
'corp_id' , // '0' COMMENT '租户/公司(全局库可写0)',
|
||||
'remark' , // '备注',
|
||||
'created_by' , // '0',
|
||||
'updated_by' , // '0',
|
||||
];
|
||||
|
||||
|
||||
$data = [
|
||||
'fingerprint' , // '规格指纹(sha256)去重',
|
||||
'brand_name' , // '品牌名称展示,如 比亚迪、现代、起亚',
|
||||
'series_name' , // '车系名称展示',
|
||||
'model_name' , // '车型/配置名称(可读,如 伊兰特 1.5L、海狮07)',
|
||||
'model_year' , // '年款(YYYY)',
|
||||
'trim' , // '版型/细分配置(如 TOP旗舰版、荣耀版)',
|
||||
'trim_level' , // '配置等级(豪华型/旗舰型等,便于筛选)',
|
||||
'edition_name' , // '特别版/纪念版(如 荣耀版、致敬版等)',
|
||||
'engine_disp_l' , // '排量(L),如 1.5;纯电为空',
|
||||
'engine_turbo' , // '0' COMMENT '是否涡轮(0/1)',
|
||||
'engine_power_kw' , // '最大功率(kW)',
|
||||
'battery_kwh' , // '电池容量(kWh)',
|
||||
'gears' , // '挡位数(EV多为1)',
|
||||
'range_km' , // '续航里程(km)',
|
||||
'doors' , // '门数',
|
||||
'seats' , // '座位数',
|
||||
'weight_kg' , // '整备质量(kg)',
|
||||
'dimensions_mm' , // '尺寸(mm):{length,width,height,wheelbase}',
|
||||
'color_exterior_text' , // '外观色名称',
|
||||
'color_interior_text' , // '内饰色名称',
|
||||
'options_official' , // '官方选装(结构化 JSON)',
|
||||
'features' , // '功能/安全/智能配置(结构化 JSON)',
|
||||
'origin_country' , // '原产国 ISO-3166-1 alpha-2',
|
||||
'currency' , // 'CNY' COMMENT '币种 ISO-4217',
|
||||
'msrp' , // '建议零售价/参考价',
|
||||
'is_active' , // '1' COMMENT '启用(1)/禁用(0)',
|
||||
'corp_id' , // '0' COMMENT '租户/公司(全局库可写0)',
|
||||
'remark' , // '备注',
|
||||
'created_by' , // '0',
|
||||
'updated_by' , // '0',
|
||||
|
||||
'body_type', // '车身类型'
|
||||
'drive_type', // '驱动:FWD/RWD/AWD/4WD',
|
||||
'motor_layout', // '电机布置(MOTOR_LAYOUT):FWD/RWD/AWD/DM-i/DM-p 等'
|
||||
'fuel_type', // '能源:GAS/HEV/PHEV/EV/EREV'
|
||||
'transmission', // '变速箱:MT/AT/CVT/DCT/1SPEED'
|
||||
'range_std', // '续航标准:CLTC/WLTP/EPA/NEDC'
|
||||
'emission_std', // '排放标准:CHN6/EURO6/EPA'
|
||||
|
||||
'sku_code' , // 'SKU编码,系统生成',
|
||||
'brand_code' , // '品牌代码,如 BYD、HYUNDAI、KIA',
|
||||
'series_code' , // '车系代码,如 ELANTRA、SONGPLUS',
|
||||
'model_code' , // '车型/配置代码(工厂/内部编码)',
|
||||
'trim_code' , // '版型代码(TRIM)',
|
||||
'trim_level_code' , // '配置等级代码(TRIM_LEVEL)',
|
||||
'edition_code' , // '特别版代码(EDITION)',
|
||||
'color_exterior_code' , // '外观色代码',
|
||||
'color_interior_code' , // '内饰色代码',
|
||||
|
||||
'body_type_code' , // '车身类型:SEDAN/SUV/HB/MPV/Pickup',
|
||||
'drive_type_code' , // '驱动:FWD/RWD/AWD/4WD',
|
||||
'motor_layout_code' , // '电驱/电机布置(MOTOR_LAYOUT):FWD/RWD/AWD/DM-i/DM-p 等',
|
||||
'fuel_type_code' , // '能源:GAS/HEV/PHEV/EV/EREV',
|
||||
'transmission_code' , // '变速箱:MT/AT/CVT/DCT/1SPEED',
|
||||
'range_std_code' , // '续航标准:CLTC/WLTP/EPA/NEDC',
|
||||
'emission_std_code' , // '排放标准:CHN6/EURO6/EPA',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehVehicleUnitService;
|
||||
|
||||
class VehVehicleUnitController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehVehicleUnitService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\ApprovalFlows;
|
||||
|
||||
class ApprovalFlowsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = ApprovalFlows::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\ApprovalInstances;
|
||||
|
||||
class ApprovalInstancesLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = ApprovalInstances::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\ApprovalRecords;
|
||||
|
||||
class ApprovalRecordsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = ApprovalRecords::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\Corp;
|
||||
use app\common\model\CorpFeishuApp;
|
||||
use app\common\model\Resource;
|
||||
use app\common\model\UserTokenLog;
|
||||
use app\common\model\Systems;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserSn;
|
||||
use app\common\utils\Feishu;
|
||||
use app\common\utils\Jwt;
|
||||
use support\Redis;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class AuthLogic
|
||||
{
|
||||
/**
|
||||
* 账号密码登录
|
||||
*/
|
||||
public function uPwdLogin(array $data): array
|
||||
{
|
||||
$user = $this->validateUser($data['account'], $data['password']);
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
|
||||
return $this->generateTokensAndReturn($user, $system, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 飞书登录
|
||||
* 如果用户不存在则新增用户信息
|
||||
*/
|
||||
public function feishuLogin(array $data): array
|
||||
{
|
||||
$feishuUserInfo = $this->getFeishuUserInfo($data);
|
||||
var_dump($feishuUserInfo);
|
||||
return $feishuUserInfo;
|
||||
$user = $this->getOrCreateUser($feishuUserInfo);
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
if ($user->isLogin) {
|
||||
$this->validateSystemPermission($user, $system);
|
||||
}
|
||||
|
||||
return $this->generateTokensAndReturn($user, $system, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public function register(array $data)
|
||||
{
|
||||
try {
|
||||
// 获取可用用户编号
|
||||
$sn = $this->getAvailableUserSn();
|
||||
$data['sn'] = $sn->user_sn;
|
||||
|
||||
// 对密码进行哈希处理
|
||||
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
|
||||
|
||||
// 获取企业ID
|
||||
$corpInfo = $this->validateCorp($data['corp_abbr']);
|
||||
$data['corp_id'] = $corpInfo->id;
|
||||
|
||||
// 处理账户
|
||||
$data = $data + normalizeAccount($data['account']);
|
||||
|
||||
// 创建用户记录
|
||||
$user = $this->createUser($data);
|
||||
|
||||
// 更新用户编号的用户 ID
|
||||
$this->updateUserId($user->id, $sn->user_sn);
|
||||
|
||||
$system = $this->validateSystem($data['system_code']);
|
||||
return $this->generateTokensAndReturn($user, $system, $data);
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找或创建飞书用户
|
||||
*/
|
||||
private function getOrCreateUser(array $feishuUserInfo)
|
||||
{
|
||||
try {
|
||||
$phone = $this->sanitizePhone($feishuUserInfo['mobile'] ?? '');
|
||||
$email = $feishuUserInfo['enterprise_email'];
|
||||
// $user = User::where('corp_id', $feishuUserInfo['corp_id'])
|
||||
// ->where(function($query) use ($phone, $email) {
|
||||
// $query->where('phone', $phone)
|
||||
// ->orWhere('email', $email);
|
||||
// })
|
||||
// ->first();
|
||||
$userByPhone = User::where('corp_id', $feishuUserInfo['corp_id'])
|
||||
->where('phone', $phone)
|
||||
->where('phone', '!=', '')
|
||||
->first();
|
||||
$userByEmail = User::where('corp_id', $feishuUserInfo['corp_id'])
|
||||
->where('email', $email)
|
||||
->where('email', '!=', '')
|
||||
->first();
|
||||
$user = $userByPhone ?: $userByEmail;
|
||||
if (!$user) {
|
||||
// 获取编号
|
||||
$userSn = $this->getAvailableUserSn();
|
||||
// 创建用户
|
||||
$user = User::create([
|
||||
'name' => $feishuUserInfo['name'],
|
||||
'avatar' => $feishuUserInfo['avatar_middle'],
|
||||
'phone' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''),
|
||||
'email' => $feishuUserInfo['enterprise_email'],
|
||||
'union_id' => $feishuUserInfo['union_id'],
|
||||
'user_id' => $feishuUserInfo['user_id'],
|
||||
'open_id' => $feishuUserInfo['open_id'],
|
||||
'tenant_key' => $feishuUserInfo['tenant_key'],
|
||||
'corp_id' => $feishuUserInfo['corp_id'],
|
||||
'password' => password_hash($userSn->user_sn, PASSWORD_DEFAULT)
|
||||
]);
|
||||
// 更新使用
|
||||
$this->updateUserId($user->id, $userSn->user_sn);
|
||||
// 更新编号
|
||||
if (!$user->update(['sn'=>$userSn->user_sn])) {
|
||||
throw new BusinessException('编号绑定失败');
|
||||
}
|
||||
// 是否登陆
|
||||
$user->isLogin = false;
|
||||
|
||||
$options = [
|
||||
'corp_id' => $user->corp_id,
|
||||
'user_id' => $user->id
|
||||
];
|
||||
\Webman\RedisQueue\Redis::send('perm-agt', $options);
|
||||
} else {
|
||||
$updated = [
|
||||
'union_id' => $feishuUserInfo['union_id'],
|
||||
'user_id' => $feishuUserInfo['user_id'],
|
||||
'open_id' => $feishuUserInfo['open_id'],
|
||||
'tenant_key' => $feishuUserInfo['tenant_key']
|
||||
];
|
||||
if ($phone && $phone == $user->phone) {
|
||||
$updated['phone'] = $phone;
|
||||
}
|
||||
if ($phone && $phone == $user->phone) {
|
||||
$updated['email'] = $email;
|
||||
}
|
||||
// 更新飞书信息
|
||||
$user->update($updated);
|
||||
// 是否登陆
|
||||
$user->isLogin = true;
|
||||
}
|
||||
return $user;
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤和格式化手机号
|
||||
*/
|
||||
private function sanitizePhone($mobile)
|
||||
{
|
||||
return isset($mobile) ? str_replace('+86', '', $mobile) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并记录token,返回包含token的响应数据
|
||||
*/
|
||||
private function generateTokensAndReturn(User $user, Systems $system, array $data): array
|
||||
{
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id, $data['system_code']);
|
||||
$homeUrl = $this->getHome($system->id, $payload);
|
||||
|
||||
// 记录 token 到 Redis 和 MySQL
|
||||
$this->recordUserToken($user->id, $token, $data['system_code']);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'system_code' => $data['system_code'],
|
||||
'corp_abbr' => $data['corp_abbr'],
|
||||
'login_type' => $data['login_type'],
|
||||
'home_url' => $homeUrl,
|
||||
// 'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取飞书用户信息
|
||||
*/
|
||||
private function getFeishuUserInfo(array $data)
|
||||
{
|
||||
$app = [
|
||||
'cli_a8033d607cba500d' => 'OZW2UOeJ0g4HJfXKSn6skcymCOyYIdDR',
|
||||
];
|
||||
if(empty($app[$data['app_id']])){
|
||||
throw new BusinessException('app_id非法');
|
||||
}
|
||||
$feishu = new Feishu($data['app_id'], $app[$data['app_id']]);
|
||||
|
||||
//回调地址
|
||||
$redirect_uri = $data['r_url'];
|
||||
|
||||
$userAccessToken = $feishu->userAccessToken($data['feishu_code'], $redirect_uri);
|
||||
$userInfo = $feishu->userInfo($userAccessToken['access_token']);
|
||||
|
||||
if ($userInfo['code'] != 0) {
|
||||
throw new BusinessException('授权失败,请重试');
|
||||
}
|
||||
|
||||
return $userInfo['data'] + $data ;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户编号
|
||||
*/
|
||||
private function updateUserId($userId, $userSn)
|
||||
{
|
||||
$updated = UserSn::where('user_sn', $userSn)
|
||||
->update(['user_id' => $userId]);
|
||||
|
||||
if ($updated === 0) {
|
||||
throw new BusinessException('用户编号不存在或更新失败');
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录用户token到 Redis 和 MySQL
|
||||
*/
|
||||
protected function recordUserToken(int $userId, string $token, string $systemCode)
|
||||
{
|
||||
$request = request();
|
||||
$ip = $request ? $request->getRealIp() : '';
|
||||
$ua = $request ? $request->header('user-agent', '') : '';
|
||||
$now = time();
|
||||
$expireAt = $now + config('jwt.jwt_expiration');
|
||||
|
||||
// Redis Set 用户token集合
|
||||
Redis::sAdd("user_tokens:{$userId}:{$systemCode}", $token);
|
||||
Redis::expire("user_tokens:{$userId}:{$systemCode}", config('jwt.jwt_expiration'));
|
||||
|
||||
// Redis Hash token元信息
|
||||
$meta = [
|
||||
'system_code' => $systemCode,
|
||||
'login_ip' => $ip,
|
||||
'user_agent' => $ua,
|
||||
'login_time' => $now,
|
||||
'expired_at' => $expireAt,
|
||||
];
|
||||
Redis::hMSet("user_token_meta:{$token}", $meta);
|
||||
Redis::expire("user_token_meta:{$token}", config('jwt.jwt_expiration'));
|
||||
|
||||
// 可选:MySQL 持久化
|
||||
try {
|
||||
UserTokenLog::create([
|
||||
'user_id' => $userId,
|
||||
'token' => $token,
|
||||
'system_code'=> $systemCode,
|
||||
'login_ip' => $ip,
|
||||
'user_agent' => $ua,
|
||||
'login_time' => $now,
|
||||
'expired_at' => $expireAt,
|
||||
'is_valid' => 1,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Token log write failed: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验token有效性
|
||||
*/
|
||||
public function verifyToken(string $token, string $systemCode): array
|
||||
{
|
||||
if (!$systemCode) throw new BusinessException('缺少system_code', 422);
|
||||
|
||||
$payload = (new Jwt())->verifyToken($token);
|
||||
|
||||
// 校验黑名单
|
||||
if (Redis::has("token_blacklist:{$token}")) {
|
||||
throw new BusinessException('token已失效', 403);
|
||||
}
|
||||
|
||||
// 强制校验aud(系统隔离)
|
||||
if ($systemCode && $payload['aud'] != $systemCode && $payload['aud'] != ($payload['system_code'] ?? '')) {
|
||||
throw new BusinessException('token不属于当前系统', 403);
|
||||
}
|
||||
|
||||
return (array)$payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取飞书应用信息
|
||||
*/
|
||||
public function feishuAppInfo(string $app_id, string $systemCode)
|
||||
{
|
||||
$fsAppInfo = CorpFeishuApp::where('app_id', $app_id)->first();
|
||||
var_dump($fsAppInfo);
|
||||
return [
|
||||
'app_id' => $fsAppInfo->app_id,
|
||||
'app_secret' => $fsAppInfo->app_secret,
|
||||
'r_url' => $fsAppInfo->r_url,
|
||||
'scope' => $fsAppInfo->scopes,
|
||||
'state' => $fsAppInfo->state,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户信息
|
||||
*/
|
||||
private function validateUser(string $account, string $password)
|
||||
{
|
||||
$user = User::byAccount($account)->first();
|
||||
if (!$user || !password_verify($password, $user->password)) {
|
||||
throw new BusinessException('账号或密码错误', 401);
|
||||
}
|
||||
if ($user->status != 1) {
|
||||
throw new BusinessException('账号已禁用', 403);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证系统信息
|
||||
*/
|
||||
private function validateSystem(string $systemCode)
|
||||
{
|
||||
$system = Systems::where('code', $systemCode)->first();
|
||||
if (!$system) {
|
||||
throw new BusinessException('系统不存在', 403);
|
||||
}
|
||||
return $system;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证公司信息
|
||||
*/
|
||||
private function validateCorp(string $corpAbbr)
|
||||
{
|
||||
$corp = Corp::where('abbr', $corpAbbr)->first();
|
||||
if (!$corp) {
|
||||
throw new BusinessException('公司不存在', 403);
|
||||
}
|
||||
return $corp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证飞书应用信息
|
||||
*/
|
||||
private function validateFeishuApp(int $corpId, int $systemId)
|
||||
{
|
||||
$feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first();
|
||||
if (!$feishuAppInfo) {
|
||||
throw new BusinessException('飞书应用不存在', 403);
|
||||
}
|
||||
return $feishuAppInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户系统权限
|
||||
*/
|
||||
private function validateSystemPermission(User $user, Systems $system)
|
||||
{
|
||||
if ($system->is_role) {
|
||||
$systemIds = $user->systems->pluck('id')->toArray();
|
||||
if (!in_array($system->id, $systemIds)) {
|
||||
throw new BusinessException('无该系统权限', 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的用户编号
|
||||
*
|
||||
* @return \app\common\model\UserSn 用户编号模型实例
|
||||
* @throws BusinessException 当未找到适用编号时抛出
|
||||
*/
|
||||
private function getAvailableUserSn(): \app\common\model\UserSn
|
||||
{
|
||||
$userSnModel = new UserSn();
|
||||
$sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first();
|
||||
if (!$sn) {
|
||||
Log::error('用户注册时未找到适用编号');
|
||||
throw new BusinessException('未找到适用编号');
|
||||
}
|
||||
return $sn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户记录
|
||||
*
|
||||
* @param array $data 包含用户信息的数组
|
||||
* @return \app\common\model\User 用户模型实例
|
||||
*/
|
||||
private function createUser(array $data): \app\common\model\User
|
||||
{
|
||||
try {
|
||||
return User::create($data);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('用户注册时创建用户记录失败', [
|
||||
'data' => $data,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
throw new BusinessException('用户注册失败,请稍后重试', 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT payload 构建
|
||||
* 可扩展 payload 字段
|
||||
*/
|
||||
private function buildJwtPayload(User $user, ?Systems $system = null): array
|
||||
{
|
||||
$now = time();
|
||||
$exp = $now + config('jwt.jwt_expiration');
|
||||
$system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : [];
|
||||
$role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : [];
|
||||
|
||||
return [
|
||||
'sub' => 'user_' . $user->id,
|
||||
'aud' => $system->code,
|
||||
'iat' => $now,
|
||||
'nbf' => $now,
|
||||
'exp' => $exp,
|
||||
'typ' => 'access',
|
||||
'uid' => $user->id,
|
||||
'uname' => $user->name,
|
||||
'role_ids' => $role_ids,
|
||||
'system_ids' => $system_ids
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT签发
|
||||
*/
|
||||
private function generateAccessToken(array $payload)
|
||||
{
|
||||
return (new Jwt())->generateToken($payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌生成
|
||||
*/
|
||||
private function generateRefreshToken(int $userId, string $systemCode)
|
||||
{
|
||||
$refreshToken = bin2hex(random_bytes(32));
|
||||
// user_id+system_code只允许一个refresh_token生效
|
||||
Redis::set("refresh_token:{$userId}:{$systemCode}", $refreshToken, config('jwt.jwt_refresh_token_expiration'));
|
||||
// 建立refresh_token到user_id+system_code映射,便于校验
|
||||
Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration'));
|
||||
return $refreshToken;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
* @param string $refreshToken
|
||||
* @param string|null $systemCode 可选,指定系统
|
||||
* @return array
|
||||
*/
|
||||
public function refreshToken(string $refreshToken, ?string $systemCode = null): array
|
||||
{
|
||||
if (!$systemCode) throw new BusinessException('缺少system_code', 422);
|
||||
|
||||
// 校验refresh_token
|
||||
$userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}");
|
||||
if (!$userId) {
|
||||
throw new BusinessException('refresh_token无效或已过期', 401);
|
||||
}
|
||||
|
||||
$validToken = Redis::get("refresh_token:{$userId}:{$systemCode}");
|
||||
if ($validToken !== $refreshToken) {
|
||||
throw new BusinessException('refresh_token无效或已过期', 401);
|
||||
}
|
||||
|
||||
$user = User::find($userId);
|
||||
if (!$user || $user->status != 1) {
|
||||
throw new BusinessException('用户无效', 403);
|
||||
}
|
||||
|
||||
$system = $this->validateSystem($systemCode);
|
||||
$this->validateSystemPermission($user, $system);
|
||||
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
$token = $this->generateAccessToken($payload);
|
||||
$refreshToken = $this->generateRefreshToken($user->id, $systemCode);
|
||||
$this->recordUserToken($user->id, $token, $systemCode);
|
||||
|
||||
return [
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
// 'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出/失效token
|
||||
*/
|
||||
public function logout(?string $token, ?string $systemCode = null)
|
||||
{
|
||||
if ($token) {
|
||||
Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
||||
$payload = null;
|
||||
try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {}
|
||||
$userId = $payload['uid'] ?? null;
|
||||
$aud = $payload['aud'] ?? null;
|
||||
$sc = $systemCode ?? $aud;
|
||||
if ($userId && $sc) {
|
||||
Redis::sRem("user_tokens:{$userId}:{$sc}", $token);
|
||||
}
|
||||
Redis::del("user_token_meta:{$token}");
|
||||
UserTokenLog::where('token', $token)->update(['is_valid'=>0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制拉黑token
|
||||
*/
|
||||
public function blockToken(string $token, ?string $systemCode = null)
|
||||
{
|
||||
if (!$token) {
|
||||
throw new BusinessException('token不能为空', 422);
|
||||
}
|
||||
Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
|
||||
$payload = null;
|
||||
try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {}
|
||||
$userId = $payload['uid'] ?? null;
|
||||
$aud = $payload['aud'] ?? null;
|
||||
$sc = $systemCode ?? $aud;
|
||||
if ($userId && $sc) {
|
||||
Redis::sRem("user_tokens:{$userId}:{$sc}", $token);
|
||||
}
|
||||
Redis::del("user_token_meta:{$token}");
|
||||
UserTokenLog::where('token', $token)->update(['is_valid'=>0]);
|
||||
}
|
||||
|
||||
|
||||
public function getHome($systemId, $data)
|
||||
{
|
||||
// 2025.10.30 临时处理 ====== start
|
||||
if ($systemId == 1) {
|
||||
if (in_array(1, $data['role_ids'])) {
|
||||
return '/dashboard/console';
|
||||
}
|
||||
if (count($data['role_ids']) == 1 && in_array(5, $data['role_ids'])) {
|
||||
return '/dashboard/console/backoffice';
|
||||
}
|
||||
if (count($data['role_ids']) == 1 && in_array(6, $data['role_ids'])) {
|
||||
return '/dashboard/console/finance';
|
||||
}
|
||||
}
|
||||
// 2025.10.30 临时处理 ====== end
|
||||
|
||||
$info = Resource::where('system_id', $systemId)
|
||||
->where('is_home', 1)
|
||||
->first();
|
||||
|
||||
if (!$info) {
|
||||
return '/dashboard/console';
|
||||
}
|
||||
|
||||
// 递归拼接 URL
|
||||
$url = $this->buildParentUrl($info);
|
||||
|
||||
return $url ?: '/dashboard/console';
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归构建父级 URL
|
||||
* @param Resource $resource
|
||||
* @return string
|
||||
*/
|
||||
protected function buildParentUrl(Resource $resource)
|
||||
{
|
||||
$url = $resource->url ?? '';
|
||||
|
||||
// 如果存在父级,则递归拼接
|
||||
if ($resource->parent_id != 0) {
|
||||
$parent = Resource::find($resource->parent_id);
|
||||
if ($parent) {
|
||||
$parentUrl = $this->buildParentUrl($parent);
|
||||
$url = $parentUrl . ($url ? '/' . ltrim($url, '/') : '');
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\Brand;
|
||||
|
||||
class BrandLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = Brand::class;
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return Brand::where('status', 1)->orderBy('hot','desc')->orderBy('sort','desc')->get(['id', 'brand_code', 'brand_name', 'logo', 'description']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\BrandSeries;
|
||||
|
||||
class BrandSeriesLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = BrandSeries::class;
|
||||
|
||||
public function getList($where)
|
||||
{
|
||||
return BrandSeries::where('status', 1)->where($where)->orderBy('sort','desc')->get(['id', 'series_code', 'series_name', 'description']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\CorpFeishuApp;
|
||||
|
||||
class CorpFeishuAppLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = CorpFeishuApp::class;
|
||||
|
||||
|
||||
public function getFeishuApp($corpId, $systemId)
|
||||
{
|
||||
return CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first(['id', 'corp_id', 'name', 'app_id', 'r_url', 'state']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\Corp;
|
||||
|
||||
class CorpLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = Corp::class;
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return Corp::where('status', 1)->orderBy('sort', 'asc')->get(['id', 'name', 'abbr', 'tenant_key']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\CorpFeishuApp;
|
||||
|
||||
class CountryLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = CorpFeishuApp::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\FinPaymentApply;
|
||||
|
||||
class FinPaymentApplyLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = FinPaymentApply::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\FinReceiptHis;
|
||||
|
||||
class FinReceiptHisLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = FinReceiptHis::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\FinReceipt;
|
||||
|
||||
class FinReceiptLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = FinReceipt::class;
|
||||
|
||||
public function credit($id, $data)
|
||||
{
|
||||
return FinReceipt::where('id', $id)->update($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\FinanceReconDubaiFiles;
|
||||
|
||||
class FinanceReconDubaiFilesLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = FinanceReconDubaiFiles::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
/**
|
||||
* Fingerprint
|
||||
*
|
||||
* 目标:
|
||||
* - 默认“强制所有字段参与计算”,保证相同记录生成同一指纹
|
||||
* - 通过统一规范化策略,避免无意义差异(空白、大小写、JSON键序)导致指纹抖动
|
||||
* - 仅排除技术字段:id、fingerprint(可通过参数自行增删)
|
||||
*
|
||||
* 用法:
|
||||
* $hash = Fingerprint::makeFromArray($payload); // 创建前
|
||||
* $hash = Fingerprint::makeFromModel($skuModel); // 已有模型
|
||||
* $hash = Fingerprint::makeFromArray($payload, ['exclude'=>['msrp']]); // 可选排除
|
||||
*/
|
||||
class Fingerprint
|
||||
{
|
||||
/** 你的 veh_vehicle_sku 所有列(保持与表结构一致,顺序无关但建议完整) */
|
||||
public const COLS = [
|
||||
'id','sku_code','fingerprint',
|
||||
'brand_code','brand_name','series_code','series_name',
|
||||
'model_code','model_name','model_year',
|
||||
'trim_code','trim_level_code','edition_code',
|
||||
'trim','trim_level','edition_name',
|
||||
'body_type_code','drive_type_code','motor_layout_code','fuel_type_code',
|
||||
'engine_disp_l','engine_turbo','engine_power_kw','battery_kwh',
|
||||
'transmission_code','gears','range_km','range_std_code',
|
||||
'doors','seats','weight_kg','dimensions_mm',
|
||||
'color_exterior_code','color_exterior_text',
|
||||
'color_interior_code','color_interior_text',
|
||||
'options_official','features','emission_std_code',
|
||||
'origin_country','currency','msrp',
|
||||
'is_active','corp_id','remark',
|
||||
'created_by','updated_by','created_at','updated_at',
|
||||
];
|
||||
|
||||
/** 默认剔除:技术/循环依赖字段 */
|
||||
public const EXCLUDE_DEFAULT = [
|
||||
'id','sku_code','fingerprint', 'dimensions_mm', 'options_official', 'features',
|
||||
'origin_country','currency','msrp',
|
||||
'is_active','corp_id','remark',
|
||||
'created_by','updated_by','created_at','updated_at'
|
||||
];
|
||||
|
||||
/** 识别为“code类”的字段模式(统一大写) */
|
||||
private const CODE_FIELD_REGEX = '/(_code|_country|^currency)$/i';
|
||||
|
||||
/** 识别为 JSON 字段 */
|
||||
private const JSON_FIELDS = ['dimensions_mm','options_official','features'];
|
||||
|
||||
/**
|
||||
* 从 Eloquent 模型生成
|
||||
*/
|
||||
public static function makeFromModel($model, array $opts = []): string
|
||||
{
|
||||
return self::makeFromArray($model->toArray(), $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数组生成指纹
|
||||
* @param array $row 一般为 $_POST 合并后的业务数据或 $model->toArray()
|
||||
* @param array $opts ['exclude'=>[], 'extra'=>[], 'salt'=>string]
|
||||
* - exclude: 额外排除的字段(默认仅排除 id,fingerprint)
|
||||
* - extra : 额外拼入的键值(例如上下文、租户策略),会参与计算
|
||||
* - salt : 额外加盐字符串
|
||||
*/
|
||||
public static function makeFromArray(array $row, array $opts = []): string
|
||||
{
|
||||
$exclude = array_values(array_unique(array_merge(self::EXCLUDE_DEFAULT, $opts['exclude'] ?? [])));
|
||||
$extra = $opts['extra'] ?? [];
|
||||
$salt = $opts['salt'] ?? '';
|
||||
|
||||
// 1) 统一抽取“表中定义的字段” + extra,缺失字段也要纳入(值按 NULL 处理)
|
||||
$data = [];
|
||||
foreach (self::COLS as $col) {
|
||||
if (in_array($col, $exclude, true)) continue;
|
||||
$data[$col] = array_key_exists($col, $row) ? $row[$col] : null;
|
||||
}
|
||||
// extra 也参与
|
||||
foreach ($extra as $k => $v) {
|
||||
if (!is_string($k) || $k === '') continue;
|
||||
$data['__extra__'.$k] = $v;
|
||||
}
|
||||
|
||||
// 2) 规范化(按字段特征)
|
||||
$canon = [];
|
||||
foreach ($data as $k => $v) {
|
||||
$canon[$k] = self::normalize($k, $v);
|
||||
}
|
||||
|
||||
// 3) 键名排序,确保顺序无关
|
||||
ksort($canon, SORT_STRING);
|
||||
|
||||
// 4) 拼成稳定串: key=value | key=value...
|
||||
$parts = [];
|
||||
foreach ($canon as $k => $v) {
|
||||
// 为空也必须占位,避免 key 消失影响其他键的意义
|
||||
$parts[] = $k.'='.$v;
|
||||
}
|
||||
if ($salt !== '') {
|
||||
$parts[] = '__salt__='.$salt;
|
||||
}
|
||||
$plain = implode('|', $parts);
|
||||
|
||||
// 5) sha256
|
||||
return hash('sha256', $plain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化单个字段
|
||||
* - NULL -> 特殊占位 "∅"(避免与空串混淆)
|
||||
* - 布尔/整数/浮点 -> 统一数字字符串
|
||||
* - code类字段 -> 去首尾空白 & 转大写
|
||||
* - 普通字符串 -> trim & 压缩连续空白为单空格
|
||||
* - JSON字段 -> 解析后做“键排序 + 值规范化 + 索引数组可排序”,再无空白编码
|
||||
*/
|
||||
private static function normalize(string $field, $value): string
|
||||
{
|
||||
if ($value === null) return '∅';
|
||||
|
||||
// 数值类型
|
||||
if (is_int($value)) return (string)$value;
|
||||
if (is_bool($value)) return $value ? '1' : '0';
|
||||
|
||||
if (is_float($value) || (is_string($value) && is_numeric($value) && self::looksLikeNumberField($field))) {
|
||||
// 统一浮点格式,最多 6 位小数并去尾零
|
||||
$num = (float)$value;
|
||||
$s = number_format($num, 6, '.', '');
|
||||
$s = rtrim(rtrim($s, '0'), '.');
|
||||
return $s === '' ? '0' : $s;
|
||||
}
|
||||
|
||||
// JSON 字段
|
||||
if (in_array($field, self::JSON_FIELDS, true)) {
|
||||
return self::normalizeJson($value);
|
||||
}
|
||||
|
||||
// 字符串
|
||||
if (is_string($value)) {
|
||||
$s = trim($value);
|
||||
// code类:统一大写
|
||||
if (preg_match(self::CODE_FIELD_REGEX, $field)) {
|
||||
$s = mb_strtoupper($s, 'UTF-8');
|
||||
} else {
|
||||
// 普通文本:压缩连续空白
|
||||
$s = preg_replace('/\s+/u', ' ', $s);
|
||||
}
|
||||
return $s === '' ? '' : $s;
|
||||
}
|
||||
|
||||
// 数组(极少出现,按 JSON 处理)
|
||||
if (is_array($value)) {
|
||||
return self::normalizeJson($value);
|
||||
}
|
||||
|
||||
// 其他类型(对象等):转 JSON 后处理
|
||||
return self::normalizeJson($value);
|
||||
}
|
||||
|
||||
/** 判断这个字段是否“看起来像数值字段” */
|
||||
private static function looksLikeNumberField(string $field): bool
|
||||
{
|
||||
static $numKeys = [
|
||||
'engine_disp_l','engine_power_kw','battery_kwh','gears',
|
||||
'range_km','doors','seats','weight_kg','msrp','model_year',
|
||||
'created_at','updated_at','corp_id','is_active'
|
||||
];
|
||||
return in_array($field, $numKeys, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 规范化:
|
||||
* - 字符串:若能 json_decode 则按 JSON;否则当普通字符串再规范化
|
||||
* - 对象:键名排序;值递归规范化
|
||||
* - 索引数组:元素递归规范化后,按字符串值排序(视为集合,避免顺序抖动)
|
||||
* - 最终 json_encode(JSON_UNESCAPED_UNICODE|UNESCAPED_SLASHES)
|
||||
*/
|
||||
private static function normalizeJson($val): string
|
||||
{
|
||||
// 已是数组/对象
|
||||
if (is_array($val)) {
|
||||
$normalized = self::canonArray($val);
|
||||
return json_encode($normalized, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
// 字符串尝试解析
|
||||
if (is_string($val)) {
|
||||
$val = trim($val);
|
||||
if ($val === '') return '""';
|
||||
$decoded = json_decode($val, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$normalized = self::canonArray($decoded);
|
||||
return json_encode($normalized, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
// 非 JSON 字符串,按普通字符串规范化
|
||||
$s = preg_replace('/\s+/u', ' ', $val);
|
||||
return json_encode($s, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
// 其他类型:直接编码
|
||||
return json_encode($val, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
/** 递归规范化数组/对象 */
|
||||
private static function canonArray($arr)
|
||||
{
|
||||
if (!is_array($arr)) return $arr;
|
||||
|
||||
// 关联数组(对象)
|
||||
if (self::isAssoc($arr)) {
|
||||
$out = [];
|
||||
ksort($arr, SORT_STRING);
|
||||
foreach ($arr as $k => $v) {
|
||||
$out[(string)$k] = self::canonValue($v);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// 索引数组(当集合处理:元素字符串化后排序)
|
||||
$tmp = [];
|
||||
foreach ($arr as $v) {
|
||||
$tmp[] = self::canonValue($v);
|
||||
}
|
||||
// 将元素转为字符串再排序,确保稳定
|
||||
usort($tmp, function($a, $b) {
|
||||
$sa = is_scalar($a) ? (string)$a : json_encode($a, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
$sb = is_scalar($b) ? (string)$b : json_encode($b, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
return strcmp($sa, $sb);
|
||||
});
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
private static function canonValue($v)
|
||||
{
|
||||
if (is_array($v)) return self::canonArray($v);
|
||||
if (is_bool($v)) return $v ? 1 : 0;
|
||||
if (is_int($v)) return $v;
|
||||
if (is_float($v)) {
|
||||
$s = number_format($v, 6, '.', '');
|
||||
$s = rtrim(rtrim($s, '0'), '.');
|
||||
return $s === '' ? '0' : $s;
|
||||
}
|
||||
if (is_string($v)) {
|
||||
$s = trim($v);
|
||||
$s = preg_replace('/\s+/u', ' ', $s);
|
||||
return $s;
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
private static function isAssoc(array $arr): bool
|
||||
{
|
||||
// 有任意一个键不是 0..n 的递增整数,就视为关联数组
|
||||
$i = 0;
|
||||
foreach ($arr as $k => $_) {
|
||||
if ($k !== $i++) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InquiryTransmitConfig;
|
||||
|
||||
class InquiryTransmitConfigLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InquiryTransmitConfig::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InquiryTransmit;
|
||||
|
||||
class InquiryTransmitLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InquiryTransmit::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InvItem;
|
||||
|
||||
class InvItemLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InvItem::class;
|
||||
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return InvItem::where('status',1)->orderBy('sort','desc')->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InvRequisitionApprove;
|
||||
|
||||
class InvRequisitionApproveLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InvRequisitionApprove::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InvRequisitionAttachment;
|
||||
|
||||
class InvRequisitionAttachmentLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InvRequisitionAttachment::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InvRequisitionItem;
|
||||
|
||||
class InvRequisitionItemLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InvRequisitionItem::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InvRequisitionItemReturnAttachment;
|
||||
|
||||
class InvRequisitionItemReturnAttachmentLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InvRequisitionItemReturnAttachment::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InvRequisitionItemReturn;
|
||||
|
||||
class InvRequisitionItemReturnLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InvRequisitionItemReturn::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\InvRequisition;
|
||||
|
||||
class InvRequisitionLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = InvRequisition::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\MessageNotifications;
|
||||
|
||||
class MessageNotificationsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = MessageNotifications::class;
|
||||
|
||||
|
||||
public function logicDeleteBy($config)
|
||||
{
|
||||
$msg = $this->logicShow($config);
|
||||
if ($msg) {
|
||||
// 删除关联的用户状态记录
|
||||
$msg->userStatuses()->delete();
|
||||
// 删除消息本身
|
||||
$msg->delete();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\MessageUserStatus;
|
||||
|
||||
class MessageUserStatusLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = MessageUserStatus::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace app\common\logic;
|
||||
|
||||
final class ModelNaming
|
||||
{
|
||||
/** 生成稳定的 model_code(只用 *_code 与关键数字) */
|
||||
public static function buildModelCode(array $s): string
|
||||
{
|
||||
$brand = self::tok($s['brand_code'] ?? '');
|
||||
$series = self::tok($s['series_code'] ?? '');
|
||||
$fuel = self::tok($s['fuel_type_code'] ?? 'UNK');
|
||||
|
||||
$parts = [$brand, $series];
|
||||
$key = [];
|
||||
|
||||
switch ($fuel) {
|
||||
case 'EV':
|
||||
$key[] = 'EV';
|
||||
if (!empty($s['range_km'])) {
|
||||
$key[] = (string) (round((int)$s['range_km'] / 10) * 10); // 到十位
|
||||
} elseif (!empty($s['battery_kwh'])) {
|
||||
$key[] = self::numToken($s['battery_kwh']) . 'KWH';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'GAS':
|
||||
if (!empty($s['engine_disp_l'])) $key[] = self::dispToken($s['engine_disp_l']); // 1.5L -> 15L
|
||||
if (!empty($s['engine_turbo']) && (int)$s['engine_turbo'] === 1) $key[] = 'T';
|
||||
if (!empty($s['transmission_code'])) $key[] = self::tok($s['transmission_code']);
|
||||
break;
|
||||
|
||||
case 'HEV':
|
||||
case 'PHEV':
|
||||
case 'EREV':
|
||||
$key[] = $fuel;
|
||||
if (!empty($s['engine_disp_l'])) $key[] = self::dispToken($s['engine_disp_l']);
|
||||
if (!empty($s['motor_layout_code'])) $key[] = self::tok($s['motor_layout_code']); // 如 DM-I/DM-P/AWD
|
||||
break;
|
||||
|
||||
default:
|
||||
$key[] = $fuel;
|
||||
}
|
||||
|
||||
if (!empty($s['drive_type_code'])) $key[] = self::tok($s['drive_type_code']);
|
||||
|
||||
$parts[] = implode('_', array_filter($key));
|
||||
return strtoupper(implode('_', array_filter($parts)));
|
||||
}
|
||||
|
||||
/** 生成中文可读的 model_name(走快照中文,不依赖 i18n) */
|
||||
public static function buildModelName(array $s): string
|
||||
{
|
||||
$seriesName = trim((string)($s['series_name'] ?? $s['series_code'] ?? ''));
|
||||
$yearPart = !empty($s['model_year']) ? ($s['model_year'] . '款 ') : '';
|
||||
|
||||
$fuel = strtoupper($s['fuel_type_code'] ?? '');
|
||||
$tail = '';
|
||||
|
||||
if ($fuel === 'EV') {
|
||||
$range = !empty($s['range_km']) ? (int)$s['range_km'] . 'KM ' : '';
|
||||
$drive = trim((string)($s['drive_type'] ?? $s['drive_type_code'] ?? ''));
|
||||
$tail = "EV {$range}{$drive}";
|
||||
} elseif ($fuel === 'GAS') {
|
||||
$disp = (!empty($s['engine_disp_l']))
|
||||
? rtrim(rtrim(number_format((float)$s['engine_disp_l'], 1, '.', ''), '0'), '.') . 'L '
|
||||
: '';
|
||||
$trans = trim((string)($s['transmission'] ?? $s['transmission_code'] ?? ''));
|
||||
$tail = "{$disp}{$trans}";
|
||||
} else {
|
||||
$tail = trim((string)($s['fuel_type'] ?? $fuel));
|
||||
}
|
||||
|
||||
$extras = [];
|
||||
foreach (['trim','trim_level','edition_name'] as $k) {
|
||||
if (!empty($s[$k])) $extras[] = trim((string)$s[$k]);
|
||||
}
|
||||
$extraStr = $extras ? ' ' . implode(' ', $extras) : '';
|
||||
|
||||
return trim($seriesName . ' ' . $yearPart . $tail . $extraStr);
|
||||
}
|
||||
|
||||
/* ===== Helpers ===== */
|
||||
|
||||
private static function tok($v): string
|
||||
{
|
||||
$v = strtoupper((string)$v);
|
||||
return preg_replace('/[^A-Z0-9_]/', '', $v ?? '');
|
||||
}
|
||||
|
||||
private static function numToken($v): string
|
||||
{
|
||||
return preg_replace('/[^0-9]/', '', (string)$v);
|
||||
}
|
||||
|
||||
/** 1.5L -> 15L;2.0L -> 20L(便于 code 稳定检索) */
|
||||
private static function dispToken($l): string
|
||||
{
|
||||
$x = (float)$l;
|
||||
return (string)round($x * 10) . 'L';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\model\Resource;
|
||||
use app\common\model\Systems;
|
||||
|
||||
class PermLogic
|
||||
{
|
||||
/**
|
||||
* 获取用户所有资源(合并角色+直接分配),并加入公共菜单,排序按sort字段
|
||||
*/
|
||||
public function getUserResources(int $userId, string $systemCode)
|
||||
{
|
||||
$system = Systems::where('code', $systemCode)->first();
|
||||
|
||||
// 获取用户及其关联角色资源和直接分配的资源
|
||||
$user = User::with(['roles.resources', 'resources'])->find($userId);
|
||||
|
||||
if (!$user) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$all = collect();
|
||||
// 遍历用户的所有角色,合并每个角色所拥有的资源
|
||||
foreach ($user->roles as $role) {
|
||||
$all = $all->merge(
|
||||
// 过滤当前角色的资源列表
|
||||
$role->resources->filter(function ($resource) use($system) {
|
||||
// 资源过滤条件:
|
||||
// 1. !$resource->no_auth:资源需要权限认证(排除公共资源)
|
||||
// 2. 系统匹配条件:
|
||||
// - $system->id == $resource->system_id:资源属于当前系统
|
||||
// - $resource->system_id == 0:资源为全局公共资源(所有系统可用)
|
||||
return !$resource->no_auth && ($system->id == $resource->system_id || $resource->system_id == 0);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// 合并用户直接分配的资源,排除不需要权限认证的资源
|
||||
$all = $all->merge(
|
||||
$user->resources->filter(function ($resource) {
|
||||
// 只保留需要权限认证的资源(或公共资源)
|
||||
return !$resource->no_auth;
|
||||
})
|
||||
);
|
||||
|
||||
// 加入公共菜单,公共菜单不管是否授权,所有人都能访问
|
||||
$publicResources = Resource::where('no_auth', 1)
|
||||
->orderBy('sort', 'asc') // 按 sort 字段升序排序
|
||||
->get();
|
||||
|
||||
// 合并所有公共资源
|
||||
$all = $all->merge($publicResources);
|
||||
|
||||
// 返回唯一的资源列表,并按 id 排序
|
||||
// 使用 sortBy('sort') 来确保最终结果根据 'sort' 字段排序
|
||||
return $all->unique('id')->values()->sortBy('sort');
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建目录/菜单树
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $resources 全部资源集合
|
||||
* @param int $parentId 当前父级资源 ID
|
||||
* @param string $parentPath 父级完整路径(用于递归)
|
||||
* @return array
|
||||
*/
|
||||
public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($resources as $node) {
|
||||
if ($node->parent_id !== $parentId) continue;
|
||||
|
||||
// 拼接完整路径
|
||||
$currentPath = $parentPath;
|
||||
if ($currentPath && substr($currentPath, -1) === '/') {
|
||||
$currentPath = rtrim($currentPath, '/');
|
||||
}
|
||||
if (!empty($node->url)) {
|
||||
if (!$currentPath) {
|
||||
$currentPath = $node->url;
|
||||
} else {
|
||||
$url = ltrim($node->url, '/');
|
||||
$currentPath = $currentPath . '/' . $url;
|
||||
}
|
||||
}
|
||||
|
||||
// 子节点递归
|
||||
$children = [];
|
||||
if ($node->type === 'directory') {
|
||||
$children = $this->buildTree($resources, $node->id, $currentPath);
|
||||
}
|
||||
|
||||
// 权限
|
||||
$authList = $resources
|
||||
->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id)
|
||||
->map(fn($r) => [
|
||||
'id' => $r->id,
|
||||
'title' => $r->name,
|
||||
'auth_mark' => $r->code,
|
||||
])->values()->all();
|
||||
|
||||
// 判断是否有子级菜单(非权限)
|
||||
$hasChildrenMenu = collect($resources)->where('parent_id', $node->id)
|
||||
->whereIn('type', ['directory', 'menu'])->isNotEmpty();
|
||||
|
||||
$componentValue = $hasChildrenMenu ? '/index/index' : $currentPath;
|
||||
|
||||
$tree[] = [
|
||||
'id' => $node->id,
|
||||
'name' => $node->name,
|
||||
'path' => $node->url,
|
||||
'component' => $componentValue,
|
||||
'meta' => [
|
||||
'title' => $node->name,
|
||||
'icon' => $node->icon,
|
||||
'keepAlive' => $node->keep_alive == 1 ? true : false,
|
||||
'authList' => $authList,
|
||||
'openType' => $node->open_type,
|
||||
'isHide' => $node->is_hide == 0 ? false : true,
|
||||
'isHideTab' => $node->is_hide_tab == 0 ? false : true
|
||||
],
|
||||
'children' => $children,
|
||||
];
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有权限code
|
||||
*/
|
||||
public function getPermissionCodes(int $userId, string $systemCode): array
|
||||
{
|
||||
$resources = $this->getUserResources($userId, $systemCode);
|
||||
return $resources
|
||||
->filter(fn($res) => $res->type === 'permission')
|
||||
->pluck('code')
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\PurchaseOrderItems;
|
||||
|
||||
class PurchaseOrderItemsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = PurchaseOrderItems::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\PurchaseOrders;
|
||||
use app\common\model\StockOrderItems;
|
||||
use app\common\utils\CrudUtil;
|
||||
use app\common\model\StockOrders;
|
||||
|
||||
class PurchaseOrdersLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = PurchaseOrders::class;
|
||||
|
||||
|
||||
/**
|
||||
* 获取备货单PI号
|
||||
*/
|
||||
public function getPiList()
|
||||
{
|
||||
return (new StockOrders())->where('pi_no', '!=', '')
|
||||
->select('pi_no', 'salesperson_snapshot')
|
||||
->distinct()
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据PI号获取备货单
|
||||
*/
|
||||
public function getOrderNoListByPiNo($piNo)
|
||||
{
|
||||
return (new StockOrders())->where('pi_no', $piNo)
|
||||
->where('approval_status', 2)
|
||||
->select('order_no', 'biz_date', 'salesperson_snapshot', 'id as stock_orders_id', 'salesperson_id')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据备货单号获取备货单明细
|
||||
*/
|
||||
public function getOrderItemsListByOrderNo($stockOrdersId)
|
||||
{
|
||||
return (new StockOrderItems())->where('order_id', $stockOrdersId)
|
||||
->select('model_snapshot', 'color_interior_snapshot', 'color_exterior_snapshot', 'unique_id', 'id as stock_order_items_id')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\Resource;
|
||||
use support\Request;
|
||||
|
||||
class ResourceLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = Resource::class;
|
||||
|
||||
|
||||
public function logicListAllResources(Request $request, array $config)
|
||||
{
|
||||
$data = $this->logicListAll($config);
|
||||
|
||||
if ($request->get('role_id')) {
|
||||
$selected = (new RoleLogic())->getRoleResources($request->get('role_id'));
|
||||
} else if ($request->get('user_id')) {
|
||||
$selected = (new RoleLogic())->getUserResources($request->get('user_id'));
|
||||
} else {
|
||||
$selected = [];
|
||||
}
|
||||
|
||||
foreach ($data as &$item) {
|
||||
$item['selected'] = in_array($item['id'], $selected) ? true : false;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use support\Db;
|
||||
|
||||
class RoleLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* 更新角色资源权限
|
||||
*
|
||||
* @param array $data 包含 role_id 和 resources 的数据
|
||||
* @return bool 操作是否成功
|
||||
*/
|
||||
public function updateRoleResources(array $data): bool
|
||||
{
|
||||
$roleId = $data['role_id'] ?? 0;
|
||||
$resources = $data['resources'] ?? [];
|
||||
|
||||
if (empty($roleId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Db::beginTransaction();
|
||||
try {
|
||||
// 删除原有角色资源关联
|
||||
Db::table('role_resources')->where('role_id', $roleId)->delete();
|
||||
|
||||
// 批量插入新角色资源关联
|
||||
if (!empty($resources)) {
|
||||
$insertData = [];
|
||||
foreach ($resources as $resourceId) {
|
||||
$insertData[] = [
|
||||
'role_id' => $roleId,
|
||||
'resource_id' => $resourceId
|
||||
];
|
||||
}
|
||||
Db::table('role_resources')->insert($insertData);
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
return true;
|
||||
} catch (\Throwable $exception) {
|
||||
Db::rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色资源
|
||||
*
|
||||
* @param int $roleId 角色ID
|
||||
* @return array 资源ID列表
|
||||
*/
|
||||
public function getRoleResources(int $roleId): array
|
||||
{
|
||||
if (empty($roleId)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Db::table('role_resources')
|
||||
->where('role_id', $roleId)
|
||||
->pluck('resource_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户资源
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @return array 资源ID列表
|
||||
*/
|
||||
public function getUserResources(int $userId): array
|
||||
{
|
||||
if (empty($userId)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Db::table('user_resources')
|
||||
->where('user_id', $userId)
|
||||
->pluck('resource_id')
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\VehVehicleSku;
|
||||
|
||||
/**
|
||||
* SKU 生成器
|
||||
* - 输入: 车辆规格数组(以 *_code 为主)
|
||||
* - 输出: 稳定、人读友好的 sku_code (<= 40 默认)
|
||||
*/
|
||||
final class SkuCodeBuilder
|
||||
{
|
||||
/** 压缩映射(可按需扩充) */
|
||||
private const ABBR = [
|
||||
// level
|
||||
'FLAGSHIP' => 'FLG',
|
||||
'PREMIUM' => 'PRM',
|
||||
'STANDARD' => 'STD',
|
||||
'DELUXE' => 'DLX',
|
||||
// edition
|
||||
'HONOR' => 'HNR',
|
||||
'CHAMPION' => 'CHP',
|
||||
'ANNIV' => 'ANV',
|
||||
// trans
|
||||
'1SPEED' => '1SP',
|
||||
// drive/fuel/body 等一般已短,不必压缩
|
||||
];
|
||||
|
||||
/** 入口:生成 sku_code */
|
||||
public static function build(array $s, array $opts = []): string
|
||||
{
|
||||
$maxLen = $opts['max_len'] ?? 40;
|
||||
$ensureUnique = $opts['ensure_unique'] ?? true;
|
||||
|
||||
$tokens = [
|
||||
'brand' => self::tok($s['brand_code'] ?? ''),
|
||||
'series' => self::tok($s['series_code'] ?? ''),
|
||||
'year' => isset($s['model_year']) ? (string)intval($s['model_year']) : '',
|
||||
'fuel' => self::tok($s['fuel_type_code'] ?? ''),
|
||||
'drive' => self::tok($s['drive_type_code'] ?? ''),
|
||||
'spec' => self::buildSpec($s, self::tok($s['fuel_type_code'] ?? '')),
|
||||
'level' => self::abbr(self::tok($s['trim_level_code'] ?? '')),
|
||||
'edition' => self::abbr(self::tok($s['edition_code'] ?? '')),
|
||||
];
|
||||
|
||||
// 过滤空段并按既定顺序拼接
|
||||
$order = ['brand','series','year','fuel','drive','spec','level','edition'];
|
||||
$parts = [];
|
||||
foreach ($order as $k) {
|
||||
if (!empty($tokens[$k])) $parts[$k] = $tokens[$k];
|
||||
}
|
||||
|
||||
$sku = self::normalizeSku(implode('-', $parts));
|
||||
|
||||
if (strlen($sku) > $maxLen) {
|
||||
$sku = self::shrink($parts, $maxLen); // 传入“带键名的 parts”
|
||||
}
|
||||
|
||||
if ($ensureUnique) {
|
||||
$sku = self::ensureUnique($sku, $maxLen);
|
||||
}
|
||||
return $sku;
|
||||
}
|
||||
|
||||
/** 构造规格位(SPEC),按能源类型差异化 */
|
||||
private static function buildSpec(array $s, string $fuel): string
|
||||
{
|
||||
$fuel = strtoupper($fuel);
|
||||
if ($fuel === 'EV') {
|
||||
if (!empty($s['range_km'])) {
|
||||
$r = (int)$s['range_km'];
|
||||
$r10 = (int)round($r / 10) * 10; // 到 10 位
|
||||
return (string)$r10;
|
||||
}
|
||||
if (!empty($s['battery_kwh'])) {
|
||||
return self::numToken($s['battery_kwh']) . 'KWH';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($fuel === 'GAS') {
|
||||
$bits = [];
|
||||
if (!empty($s['engine_disp_l'])) {
|
||||
$bits[] = self::dispToken($s['engine_disp_l']); // 1.5L -> 15L
|
||||
}
|
||||
if (!empty($s['engine_turbo']) && intval($s['engine_turbo']) === 1) {
|
||||
$bits[] = 'T';
|
||||
}
|
||||
if (!empty($s['transmission_code'])) {
|
||||
$bits[] = self::abbr(self::tok($s['transmission_code']));
|
||||
}
|
||||
return implode('', $bits); // 如 15LTCVT
|
||||
}
|
||||
|
||||
// HEV/PHEV/EREV 等
|
||||
$bits = [];
|
||||
if (!empty($s['engine_disp_l'])) {
|
||||
$bits[] = self::dispToken($s['engine_disp_l']); // 可空
|
||||
}
|
||||
if (!empty($s['motor_layout_code'])) {
|
||||
$bits[] = self::tok($s['motor_layout_code']); // 如 AWD / DM-I / DM-P
|
||||
}
|
||||
return implode('', $bits);
|
||||
}
|
||||
|
||||
/** 冲突检测并追加短校验(全局唯一) */
|
||||
private static function ensureUnique(string $sku, int $maxLen): string
|
||||
{
|
||||
if (!VehVehicleSku::where('sku_code', $sku)->exists()) {
|
||||
return $sku;
|
||||
}
|
||||
// 用 crc32 生成 5~6 位 base36 校验(稳定且短)
|
||||
$crc = self::base36Crc($sku);
|
||||
$suffix = '-X' . $crc;
|
||||
|
||||
if (strlen($sku . $suffix) > $maxLen) {
|
||||
$sku = substr($sku, 0, max(0, $maxLen - strlen($suffix)));
|
||||
$sku = rtrim($sku, '-'); // 避免尾部 '-'
|
||||
}
|
||||
$sku .= $suffix;
|
||||
|
||||
// 若极端情况下仍冲突,追加递增计数
|
||||
$i = 2;
|
||||
$candidate = $sku;
|
||||
while (VehVehicleSku::where('sku_code', $candidate)->exists()) {
|
||||
$extra = '-'.$i;
|
||||
$cut = max(0, $maxLen - strlen($extra));
|
||||
$candidate = substr($sku, 0, $cut) . $extra;
|
||||
$i++;
|
||||
}
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
/** 把 token 数组尽量压到 maxLen(逐步压缩策略) */
|
||||
private static function shrink(array $partsByKey, int $maxLen): string
|
||||
{
|
||||
// 1) 先对 series/level/edition 做激进压缩
|
||||
$p = $partsByKey;
|
||||
foreach (['series','level','edition'] as $k) {
|
||||
if (!empty($p[$k])) $p[$k] = self::heavyAbbr($p[$k]);
|
||||
}
|
||||
$sku = self::normalizeSku(implode('-', $p));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
|
||||
// 2) 依次移除不太关键的信息:edition → level(若存在)
|
||||
if (!empty($p['edition'])) { unset($p['edition']); }
|
||||
$sku = self::normalizeSku(implode('-', $p));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
|
||||
if (!empty($p['level'])) { unset($p['level']); }
|
||||
$sku = self::normalizeSku(implode('-', $p));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
|
||||
// 3) 压缩 spec(如去 KM/KWH,仅保留核心)
|
||||
if (!empty($p['spec'])) { $p['spec'] = self::compactSpec($p['spec']); }
|
||||
$sku = self::normalizeSku(implode('-', $p));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
|
||||
// 4) 仍超长:从右向左截断次要段(spec/drive/year),保留前三段(brand/series/fuel)优先
|
||||
$dropOrder = ['spec','drive','year'];
|
||||
$q = $p;
|
||||
foreach ($dropOrder as $k) {
|
||||
if (!empty($q[$k])) { unset($q[$k]); }
|
||||
$sku = self::normalizeSku(implode('-', $q));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
}
|
||||
|
||||
// 5) 最后兜底:硬截到 maxLen,后续 ensureUnique 会追加 -X 校验并避免冲突
|
||||
return substr(self::normalizeSku(implode('-', $p)), 0, $maxLen);
|
||||
}
|
||||
|
||||
/* ===== Helpers ===== */
|
||||
|
||||
private static function tok($v): string
|
||||
{
|
||||
$v = strtoupper((string)$v);
|
||||
return preg_replace('/[^A-Z0-9_]/', '', $v ?? '');
|
||||
}
|
||||
|
||||
private static function abbr(string $t): string
|
||||
{
|
||||
return self::ABBR[$t] ?? $t;
|
||||
}
|
||||
|
||||
private static function heavyAbbr(string $t): string
|
||||
{
|
||||
// 更激进:去除元音,连字符化
|
||||
$t = str_replace(['STANDARD','FLAGSHIP','PREMIUM','DELUXE'], ['STD','FLG','PRM','DLX'], $t);
|
||||
$t = preg_replace('/[AEIOU]/', '', $t);
|
||||
return $t;
|
||||
}
|
||||
|
||||
private static function compactSpec(string $s): string
|
||||
{
|
||||
// 规则:去 KM/KWH 等单位,只保留数字与核心字母
|
||||
$s = strtoupper($s);
|
||||
$s = str_replace(['KM','KWH'], '', $s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
private static function numToken($v): string
|
||||
{
|
||||
return preg_replace('/[^0-9]/', '', (string)$v);
|
||||
}
|
||||
|
||||
/** 1.5L -> 15L;2.0L -> 20L */
|
||||
private static function dispToken($l): string
|
||||
{
|
||||
$x = (float)$l;
|
||||
return (string)round($x * 10) . 'L';
|
||||
}
|
||||
|
||||
private static function base36Crc(string $s): string
|
||||
{
|
||||
$crc = sprintf('%u', crc32($s)); // 无符号
|
||||
$n = (int)$crc;
|
||||
$chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$res = '';
|
||||
do {
|
||||
$res = $chars[$n % 36] . $res;
|
||||
$n = intdiv($n, 36);
|
||||
} while ($n > 0);
|
||||
return substr($res, -6); // 取后6位足够
|
||||
}
|
||||
|
||||
private static function normalizeSku(string $s): string
|
||||
{
|
||||
// 连续的 '-' 合并,首尾修剪
|
||||
$s = preg_replace('/-+/', '-', $s);
|
||||
$s = trim($s, '-');
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\StockOrderAccounts;
|
||||
|
||||
class StockOrderAccountsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = StockOrderAccounts::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\StockOrderItemOptions;
|
||||
|
||||
class StockOrderItemOptionsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = StockOrderItemOptions::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\StockOrderItems;
|
||||
|
||||
class StockOrderItemsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = StockOrderItems::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\StockOrdersFiles;
|
||||
|
||||
class StockOrdersFilesLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = StockOrdersFiles::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\StockOrders;
|
||||
|
||||
class StockOrdersLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = StockOrders::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\SysAccount;
|
||||
|
||||
class SysAccountLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = SysAccount::class;
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return SysAccount::where('status', 1)->orderBy('sort','desc')->get(['id', 'account_name', 'currency_id', 'account_number', 'bank_name', 'bank_branch', 'description', 'short_desc']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\SysConfig;
|
||||
use app\common\model\SysCurrency;
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
|
||||
class SysConfigLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = SysCurrency::class;
|
||||
|
||||
public function findByKey(string $key)
|
||||
{
|
||||
return SysConfig::where('status', 1)->where('config_key', $key)->get(['id', 'config_key', 'config_value']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\SysCurrency;
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
|
||||
class SysCurrencyLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = SysCurrency::class;
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return SysCurrency::where('status', 1)->get(['id', 'country_id', 'currency_code', 'currency_name', 'currency_symbol', 'exchange_rate', 'float_percent_up', 'float_percent_down']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\SysDictItem;
|
||||
|
||||
class SysDictItemLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = SysDictItem::class;
|
||||
|
||||
public function getList($where)
|
||||
{
|
||||
return SysDictItem::where('status', 1)->where($where)->orderBy('sort','desc')->get(['id', 'item_label', 'item_value', 'remark', 'dict_id']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\SysDict;
|
||||
|
||||
class SysDictLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = SysDict::class;
|
||||
|
||||
public function getDictByCode($code)
|
||||
{
|
||||
return SysDict::where('status', 1)->where('code', $code)->orderBy('sort','desc')->first(['id']);
|
||||
}
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return SysDict::where('status', 1)->get(['id', 'dict_name', 'dict_code', 'description']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\SysOpLog;
|
||||
|
||||
class SysOpLogLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = SysOpLog::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\SysPort;
|
||||
|
||||
class SysPortLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = SysPort::class;
|
||||
|
||||
public function getList($where)
|
||||
{
|
||||
return SysPort::where('status', 1)->where($where)->orderBy('sort','desc')->get(['id', 'port_code', 'port_name', 'port_name_en', 'country_id', 'description']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\SysUpdateLog;
|
||||
|
||||
class SysUpdateLogLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = SysUpdateLog::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\Systems;
|
||||
|
||||
class SystemsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = Systems::class;
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return Systems::where('status', 1)->orderBy('sort', 'asc')->get(['id', 'name', 'code', 'type', 'type']);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user