This commit is contained in:
2026-01-05 14:33:21 +08:00
commit ee348b4925
451 changed files with 39954 additions and 0 deletions
+14
View File
@@ -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
+21
View File
@@ -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.
+70
View File
@@ -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>
+31
View File
@@ -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(), []);
});
}
}
+105
View File
@@ -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;
}
}
}
+62
View File
@@ -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;
}
}
+69
View File
@@ -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;
}
}
}
+79
View File
@@ -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;
}
+206
View File
@@ -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;
}
}
}
+144
View File
@@ -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;
}
}
+185
View File
@@ -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)
*/
+32
View File
@@ -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;
}
}
+103
View File
@@ -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;
}
}
}
+178
View File
@@ -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;
}
}
}
+34
View File
@@ -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;
}
}
+36
View File
@@ -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;
}
}
}
+29
View File
@@ -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;
}
}
+77
View File
@@ -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];
}
}
+121
View File
@@ -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');
// }
}
+92
View File
@@ -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;
}
}
+90
View File
@@ -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());
}
}
}
+136
View File
@@ -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());
}
}
}
+233
View File
@@ -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;
}
}
+302
View File
@@ -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;
// }
}
+128
View File
@@ -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;
// }
}
+17
View File
@@ -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;
}
+17
View File
@@ -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;
}
+597
View File
@@ -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;
}
}
+22
View File
@@ -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']);
}
}
+22
View File
@@ -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']);
}
}
+23
View File
@@ -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']);
}
}
+21
View File
@@ -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']);
}
}
+17
View File
@@ -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;
}
+17
View File
@@ -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;
}
+17
View File
@@ -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;
}
+21
View File
@@ -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;
}
+262
View File
@@ -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;
}
+17
View File
@@ -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;
}
+22
View File
@@ -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;
}
+17
View File
@@ -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;
}
+101
View File
@@ -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';
}
}
+144
View File
@@ -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;
}
+52
View File
@@ -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();
}
}
+37
View File
@@ -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;
}
}
+85
View File
@@ -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();
}
}
+233
View File
@@ -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;
}
+17
View File
@@ -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;
}
+17
View File
@@ -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;
}
+22
View File
@@ -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']);
}
}
+22
View File
@@ -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']);
}
}
+21
View File
@@ -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']);
}
}
+22
View File
@@ -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']);
}
}
+27
View File
@@ -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']);
}
}
+17
View File
@@ -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;
}
+22
View File
@@ -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']);
}
}
+17
View File
@@ -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;
}
+21
View File
@@ -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