first commit
This commit is contained in:
68
app/Command/CoverCommand.php
Executable file
68
app/Command/CoverCommand.php
Executable file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppArticle;
|
||||
use Hyperf\Collection\Collection;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use function Hyperf\Coroutine\co;
|
||||
|
||||
#[Command]
|
||||
class CoverCommand extends HyperfCommand
|
||||
{
|
||||
|
||||
protected $reids;
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
parent::__construct('demo:cover');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('Hyperf Demo Command');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->line('Hello Hyperf!', 'info');
|
||||
|
||||
Db::table('app_articles')->where('id', '>', 11284)->orderBy('id')->chunk(20, function (Collection $item) {
|
||||
$waitGroup = new \Hyperf\Coroutine\WaitGroup();
|
||||
foreach ($item as $v) {;
|
||||
if (!$v || $v->cover) {
|
||||
if ($v->year) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$waitGroup->add();
|
||||
co(function () use ($waitGroup, $v) {
|
||||
$cover = json_decode($v->images, true);
|
||||
$v->cover = current($cover)['src'];
|
||||
|
||||
$model = AppArticle::find($v->id);
|
||||
if ($model) {
|
||||
$model->cover = current($cover)['src'];
|
||||
preg_match('/([0-9]+)/', $model->title, $y);
|
||||
|
||||
$model->year = $y[1] ?? 0;
|
||||
echo "update {$v->id} year: {$model->year} effect: {$model->update()}" . PHP_EOL;
|
||||
}
|
||||
|
||||
$waitGroup->done();
|
||||
});
|
||||
}
|
||||
|
||||
$waitGroup->wait();
|
||||
});
|
||||
|
||||
|
||||
// var_dump(file_get_contents('https://www.vogue.com/fashion-shows/designer/AFKIR'));
|
||||
}
|
||||
}
|
184
app/Command/FooCommand.php
Executable file
184
app/Command/FooCommand.php
Executable file
@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Command\spider\VogueCommand;
|
||||
use App\FormModel\spider\ReviewModel;
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Helpers\TitleHelper;
|
||||
use App\Model\AppArticle;
|
||||
use App\Model\AppBrand;
|
||||
use App\Model\AppSpiderArticle;
|
||||
use Co\WaitGroup;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use Hyperf\Collection\Collection;
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Laminas\Stdlib\ArrayUtils;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Spatie\Crawler\Crawler;
|
||||
use Swoole\Timer;
|
||||
use function Hyperf\Coroutine\co;
|
||||
|
||||
#[Command]
|
||||
class FooCommand extends HyperfCommand
|
||||
{
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
protected $reids;
|
||||
public function __construct(protected ContainerInterface $container, LoggerFactory $loggerFactory)
|
||||
{
|
||||
parent::__construct('demo:command');
|
||||
$this->logger = $loggerFactory->get('log', 'command');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('Hyperf Demo Command');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->line('Hello Hyperf!', 'info');
|
||||
|
||||
// Crawler::create([
|
||||
// RequestOptions::TIMEOUT => 30.0, // 请求最大持续时间
|
||||
// RequestOptions::CONNECT_TIMEOUT => 10.0, // 连接超时
|
||||
// ])->setCrawlObserver(new TestClass())->startCrawling('https://theimpression.com/milan-street-style-fall-2025-day-6/');
|
||||
|
||||
|
||||
$query = AppArticle::where('id', '>', 1);
|
||||
|
||||
// $model = new ReviewModel();
|
||||
$map = [
|
||||
// 'Fall 1996 Ready-to-Wear' => '/Fall ([0-9]*?) Ready-to-Wear/',
|
||||
'/Fall ([0-9]*?) Ready-to-Wear/' => [
|
||||
'trans' => '秋季成衣',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
'/Spring ([0-9]*?) Ready-to-Wear/' => [
|
||||
'trans' => '春季成衣',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
'/Pre-Fall ([0-9]*?)/' => [
|
||||
'trans' => '早秋',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
'/Australia Resort ([0-9]*?)/' => [
|
||||
'trans' => '时装',
|
||||
'style' => 1,
|
||||
'location' => 1,
|
||||
],
|
||||
'/Fall ([0-9]*?) Menswear/' => [
|
||||
'trans' => '秋季男装',
|
||||
'style' => 0,
|
||||
'location' => 1, // 澳大利亚
|
||||
],
|
||||
'/Ukraine Fall ([0-9]*?)/' => [
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => 2, // 乌克兰
|
||||
],
|
||||
'/Kiev Fall ([0-9]*?)/' => [
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => 3, // 基辅
|
||||
],
|
||||
'/Stockholm Fall ([0-9]*?)/' => [
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => 4, // 斯德哥尔摩
|
||||
],
|
||||
'/Tokyo Fall ([0-9]*?)/' => [
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => 5, // 东京
|
||||
],
|
||||
'/Berlin Fall ([0-9]*?)/' => [
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => 6, // 柏林
|
||||
],
|
||||
'/Copenhagen Fall ([0-9]*?)/' => [
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => 7, // 哥本哈根
|
||||
],
|
||||
'/([0-9]*?) Spring Summer/' => [
|
||||
'trans' => '春夏',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
'/([0-9]*?) Autumn Winter/' => [
|
||||
'trans' => '秋冬',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
//
|
||||
// var_dump(TitleHelper::translate('Pre-Fall 2019'));die;
|
||||
foreach ($query->cursor() as $item) {
|
||||
echo '正在同步' . $item->id . PHP_EOL;
|
||||
if (in_array($item->title, ['春季', ' 春季', '秋季', ' 秋季', '早秋', ' 早秋', '时装', ' 时装']) || stripos($item->title, '|') !== false) {
|
||||
$originTitle = AppSpiderArticle::find($item->spider_article_id)->title;
|
||||
if ($title = TitleHelper::translate($originTitle)) {
|
||||
var_dump($title);
|
||||
$item->title = $title;
|
||||
$item->save();
|
||||
}
|
||||
} else {
|
||||
if ($title = TitleHelper::translate($item->title)) {
|
||||
$item->title = $title;
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
|
||||
// $model = AppArticle::find($item->id);
|
||||
// $model->aid = strtr(uniqid(more_entropy:true), [
|
||||
// '.' => ''
|
||||
// ]);
|
||||
//// $model->description = '1';
|
||||
// $model->save();
|
||||
// foreach ($map as $mapPreg => $mapItem) {
|
||||
//
|
||||
// preg_match_all($mapPreg, $item->title, $matches);
|
||||
//
|
||||
// if (count($matches) > 1 && $matches[1]) {
|
||||
// echo current($matches[1]) . " {$mapItem['trans']}" . PHP_EOL;
|
||||
// $model = AppArticle::find($item->id);
|
||||
// $model->title = current($matches[1]) . " {$mapItem['trans']}";
|
||||
// $model->location = $mapItem['location'];
|
||||
// $model->style = $mapItem['style'];
|
||||
//
|
||||
// echo 'save';
|
||||
// $model->save();
|
||||
// continue;
|
||||
// }
|
||||
}
|
||||
//
|
||||
//// $model->pass($item->id);
|
||||
////
|
||||
////// var_dump($item->created_at->date);
|
||||
////// $model->deleted_at = $item->created_at->timestamp;
|
||||
////// var_dump($model->save());
|
||||
// }
|
||||
|
||||
// Fall 1996 Ready-to-Wear
|
||||
// Spring 2025 Ready-to-Wear
|
||||
// Pre-Fall 2025
|
||||
|
||||
}
|
||||
}
|
42
app/Command/SpiderReviewCommand.php
Executable file
42
app/Command/SpiderReviewCommand.php
Executable file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\FormModel\spider\ReviewModel;
|
||||
use App\Model\AppArticle;
|
||||
use Hyperf\Collection\Collection;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use function Hyperf\Coroutine\co;
|
||||
|
||||
#[Command]
|
||||
class SpiderReviewCommand extends HyperfCommand
|
||||
{
|
||||
|
||||
protected $reids;
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
parent::__construct('spider:review');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('review spider article.');
|
||||
$this->addOption('id', 'i', InputOption::VALUE_REQUIRED, '文章id', false);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$reviewModel = new ReviewModel();
|
||||
$reviewModel->pass($input->getOption('id'));
|
||||
return 0;
|
||||
}
|
||||
}
|
26
app/Command/TestClass.php
Executable file
26
app/Command/TestClass.php
Executable file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Spatie\Crawler\CrawlObservers\CrawlObserver;
|
||||
|
||||
class TestClass extends CrawlObserver
|
||||
{
|
||||
public function willCrawl(UriInterface $url, ?string $linkText): void
|
||||
{
|
||||
echo "即将爬取: {$url}\n";
|
||||
}
|
||||
|
||||
public function crawled(
|
||||
UriInterface $url, ResponseInterface $response, ?UriInterface $foundOnUrl = null, ?string $linkText = null): void {
|
||||
var_dump($response);
|
||||
echo "已成功爬取: {$url} 状态码: " . $response->getStatusCode() . "\n";
|
||||
}
|
||||
|
||||
public function crawlFailed(UriInterface $url, \Throwable $exception, ?UriInterface $foundOnUrl = null, ?string $linkText = null): void
|
||||
{
|
||||
echo "爬取失败: {$url} 错误: {$exception->getMessage()}\n";
|
||||
}
|
||||
}
|
171
app/Command/spider/BaseSpider.php
Executable file
171
app/Command/spider/BaseSpider.php
Executable file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command\spider;
|
||||
|
||||
use App\Model\AppArticle;
|
||||
use App\Model\AppSpiderArticle;
|
||||
use Hyperf\Command\Command;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Laminas\Stdlib\ArrayUtils;
|
||||
use Swoole\Coroutine\Channel;
|
||||
use Swoole\Timer;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use function Hyperf\Coroutine\co;
|
||||
|
||||
class BaseSpider extends Command
|
||||
{
|
||||
/**
|
||||
* 最大协程数量
|
||||
* @var int
|
||||
*/
|
||||
protected int $maxCo = 10;
|
||||
|
||||
protected ?\Swoole\Coroutine\Channel $channel = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $baseUrl = '';
|
||||
|
||||
#[Inject]
|
||||
protected ?StdoutLoggerInterface $logger = null;
|
||||
|
||||
protected array $coroutineList = [];
|
||||
|
||||
protected const PLATFORM = '';
|
||||
|
||||
private bool $isInit = false;
|
||||
|
||||
protected int|bool $timer = false;
|
||||
|
||||
protected array $commandConfigure = [];
|
||||
|
||||
private function init()
|
||||
{
|
||||
// 因为最外层还有个父协程, 所以加一
|
||||
$this->channel = new Channel($this->maxCo + 1);
|
||||
$this->timer = Timer::tick(1000 * 30, function () use (&$coList) {
|
||||
// count(\Swoole\Coroutine::getElapsed());
|
||||
var_dump(count($this->coroutineList));
|
||||
// var_dump($list);
|
||||
});
|
||||
for ($i = 0; $i < $this->maxCo + 1; $i++) {
|
||||
$this->channel->push(1);
|
||||
}
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->addOption('prod', '', InputOption::VALUE_NEGATABLE, '是否关闭devMode.', false);
|
||||
}
|
||||
|
||||
public static function getPlatform(): string
|
||||
{
|
||||
return static::PLATFORM;
|
||||
}
|
||||
|
||||
public function getBaseUrl(): string
|
||||
{
|
||||
return rtrim($this->baseUrl, '/');
|
||||
}
|
||||
|
||||
protected function getArticleModel(array $condition)
|
||||
{
|
||||
return AppSpiderArticle::query()->where($condition)->first() ?: new AppSpiderArticle();
|
||||
}
|
||||
|
||||
protected function request(string $url): array
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, array(
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||
));
|
||||
// curl_setopt($ch, CURLOPT_URL, $url);
|
||||
// curl_setopt($ch, CURLOPT_HEADER, false);
|
||||
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
// curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
$result = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
$httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
|
||||
return [$result, $httpCode];
|
||||
}
|
||||
|
||||
protected function returnPool()
|
||||
{
|
||||
return $this->channel->push(1);
|
||||
}
|
||||
|
||||
protected function getPool(): bool
|
||||
{
|
||||
return $this->channel->pop();
|
||||
}
|
||||
|
||||
protected function createCoroutine(\Closure $func): void
|
||||
{
|
||||
if ($this->isInit === false) {
|
||||
$this->isInit = true;
|
||||
$this->init();
|
||||
}
|
||||
$this->getPool();
|
||||
$cid = co(function () use ($func) {
|
||||
\Co\defer(function() {
|
||||
unset($this->coroutineList[Coroutine::id()]);
|
||||
$this->returnPool();
|
||||
});
|
||||
|
||||
$func();
|
||||
});
|
||||
$this->coroutineList[$cid] = 1;
|
||||
}
|
||||
|
||||
protected function debugPrint(array|string $message = '', $level = 0)
|
||||
{
|
||||
if ($this->getCommandConfigure('prod') === false) {
|
||||
$printTime = date('H:i:s');
|
||||
echo "[spider-debug][$printTime]" . print_r($message, true) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于单元测试
|
||||
* @param string $methodName
|
||||
* @param $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function testMethod(string $methodName, $args = [])
|
||||
{
|
||||
return $this->{$methodName}(...$args);
|
||||
}
|
||||
|
||||
public function setCommandConfigure($options): void
|
||||
{
|
||||
$this->commandConfigure = $options;
|
||||
}
|
||||
|
||||
public function getCommandConfigure($key = null, $defaultValue = null)
|
||||
{
|
||||
if (!$key) {
|
||||
return $this->commandConfigure;
|
||||
}
|
||||
|
||||
return $this->commandConfigure[$key] ?? $defaultValue;
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->setCommandConfigure($input->getOptions());
|
||||
return 0;
|
||||
}
|
||||
}
|
187
app/Command/spider/ElleStreetCommand.php
Executable file
187
app/Command/spider/ElleStreetCommand.php
Executable file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command\spider;
|
||||
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use function Swoole\Coroutine\run;
|
||||
|
||||
#[Command]
|
||||
class ElleStreetCommand extends BaseSpider
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $baseUrl = 'https://www.elle.com';
|
||||
|
||||
protected const PLATFORM = 'elle-street';
|
||||
|
||||
public function __construct(protected ContainerInterface $container, LoggerFactory $loggerFactory)
|
||||
{
|
||||
parent::__construct('spider:elle-street');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('elle.com/street elle街拍模块');
|
||||
$this->addOption('brandId', 'b', InputOption::VALUE_OPTIONAL, '指定的品牌id', false);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
run(function () {
|
||||
$this->spiderStart();
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function _getTask($brand): \Generator
|
||||
{
|
||||
$query = Db::table('app_brands');
|
||||
if ($brand) {
|
||||
$query->where(['id' => $brand]);
|
||||
}
|
||||
|
||||
$query->where('id', '>', 1)->orderBy('id');
|
||||
foreach ($query->cursor() as $row) {
|
||||
yield $row;
|
||||
}
|
||||
}
|
||||
|
||||
private function _getTaskName($name): string
|
||||
{
|
||||
return strtolower(strtr($name, [
|
||||
'.' => '-',
|
||||
' ' => '-'
|
||||
]));
|
||||
}
|
||||
|
||||
public function spiderStart(): void
|
||||
{
|
||||
list($result, $httpCode) = $this->request($this->getBaseUrl() . '/fashion/street-style/');
|
||||
|
||||
preg_match_all('/<script id="json-ld" type="application\/ld\+json">([\s\S]*?)<\/script>/', $result, $matches);
|
||||
|
||||
if (!is_array($matches) && count($matches) < 1) {
|
||||
$this->logger->info(self::getPlatform() . " 数据获取失败。");
|
||||
return;
|
||||
}
|
||||
|
||||
$val = json_decode(($matches[1][0]), true);
|
||||
|
||||
$articles = $val[0]['itemListElement'] ?? [];
|
||||
|
||||
if (!$articles) {
|
||||
$this->logger->info(self::getPlatform() . " 文章数据获取失败。");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$saveImages = [];
|
||||
foreach ($articles as $article) {
|
||||
list($result, $httpCode) = $this->request($article['url']);
|
||||
|
||||
preg_match_all('/<script id="json-ld" type="application\/ld\+json">([\s\S]*?)<\/script>/', $result, $matches);
|
||||
|
||||
if (isset($matches[1][0])) {
|
||||
$val = json_decode($matches[1][0], true);
|
||||
|
||||
$images = $val['about']['itemListElement'];
|
||||
|
||||
foreach ($images as $image) {
|
||||
$saveImages[] = $image['item']['image'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var_dump($saveImages);
|
||||
|
||||
|
||||
return;
|
||||
|
||||
|
||||
$this->createCoroutine(function () use ($task) {
|
||||
|
||||
$brandName = $this->_getTaskName($task->name);
|
||||
$url = $this->getBaseUrl() . '/fashion-shows/designer/' . $brandName;
|
||||
$this->logger->info(sprintf("[Command] brandName: {$this->_getTaskName($task->name)}; spiderUrl: {$url}"));
|
||||
|
||||
// 取发布会列表
|
||||
$showsList = $this->_getShowsList($url);
|
||||
|
||||
foreach ($showsList as $list) {
|
||||
$this->createCoroutine(function () use ($task, $list) {
|
||||
$this->_getDetail($task->id, $list);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function _getShowsList($url)
|
||||
{
|
||||
list($request, $httpCode) = $this->request($url);
|
||||
|
||||
if ($httpCode == 200) {
|
||||
preg_match_all('/window.__PRELOADED_STATE__ = ([\s\S]*?);<\/script>/', $request, $matches);
|
||||
$val = json_decode(current(end($matches)), true);
|
||||
return $val['transformed']['runwayDesignerContent']['designerCollections'] ?? [];
|
||||
} else {
|
||||
$this->logger->info('未找到数据.');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function _getDetail(int $brandId, array $info)
|
||||
{
|
||||
$model = $this->getArticleModel(['brand' => $brandId, 'title' => $info['hed']]);
|
||||
|
||||
$model->title = $info['hed'];
|
||||
$model->images = json_encode([]);
|
||||
$model->platform = self::getPlatform();
|
||||
|
||||
// 获取图片
|
||||
$pageUri = $info['url'];
|
||||
$requestUrl = $this->getBaseUrl() . $pageUri . '/slideshow/collection';
|
||||
$this->logger->info("正在匹配发布会详情 {$requestUrl}");
|
||||
|
||||
$matches = [];
|
||||
list($result, $httpCode) = $this->request($requestUrl);
|
||||
|
||||
if ($httpCode != 200 || !$result) {
|
||||
$this->logger->warning($requestUrl . '请求失败.');
|
||||
return;
|
||||
}
|
||||
|
||||
preg_match_all('/window\.__PRELOADED_STATE__ = (.*?);</s', $result, $matches);
|
||||
|
||||
$saveUrl = [];
|
||||
if (count($matches) > 1) {
|
||||
$val = json_decode(current($matches[1]), true);
|
||||
$images = $val['transformed']['runwayGalleries']['galleries'][0]['items'] ?? false;
|
||||
|
||||
if ($images === false) {
|
||||
$this->logger->warning($requestUrl . '获取图片失败.');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (is_array($images) ? $images : [] as $img) {
|
||||
$saveUrl[] = [
|
||||
'src' => $img['image']['sources']['xxl']['url']
|
||||
];
|
||||
}
|
||||
$model->images = json_encode($saveUrl);
|
||||
}
|
||||
|
||||
$model->save();
|
||||
|
||||
$this->logger->info("end: {$requestUrl}");
|
||||
}
|
||||
}
|
103
app/Command/spider/FashionSnapCommand.php
Executable file
103
app/Command/spider/FashionSnapCommand.php
Executable file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command\spider;
|
||||
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppBrand;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Swoole\ExitException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use function Swoole\Coroutine\run;
|
||||
|
||||
#[Command]
|
||||
class FashionSnapCommand extends BaseSpider
|
||||
{
|
||||
protected const PLATFORM = 'fashionsnap';
|
||||
|
||||
protected string $baseUrl = 'https://www.fashionsnap.com';
|
||||
|
||||
public function __construct(protected ContainerInterface $container, LoggerFactory $loggerFactory)
|
||||
{
|
||||
parent::__construct('spider:fashionsnap');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('自动采集fashionsnap.com');
|
||||
$this->addOption('brandId', 'b', InputOption::VALUE_OPTIONAL, '指定的品牌id', false);
|
||||
}
|
||||
|
||||
private function _getTask($brand): \Generator
|
||||
{
|
||||
$query = Db::table('app_brands');
|
||||
|
||||
if ($brand) {
|
||||
$query->whereIn('id', explode(',', $brand));
|
||||
} else {
|
||||
$query->where('spider_origin', '=', 'fashionsnap')->orderBy('id');
|
||||
}
|
||||
|
||||
foreach ($query->cursor() as $row) {
|
||||
if (!$row) {
|
||||
throw new ExitException('END.');
|
||||
}
|
||||
yield $row;
|
||||
}
|
||||
}
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$brand = $input->getOption('brandId');
|
||||
|
||||
run(function () use ($brand) {
|
||||
foreach ($this->_getTask($brand) as $task) {
|
||||
list($result, $httpCode) = $this->request($this->getBaseUrl() . "/api/algolia/article/?blogIds=4&brandName={$task->name}&limit=50");
|
||||
echo $task->name . '--' . $httpCode . PHP_EOL;
|
||||
if ($httpCode == 200) {
|
||||
$isSuccess = false;
|
||||
$result = json_decode($result, true);
|
||||
if ($result['totalCount'] == 0 || $result['totalCount'] > 200) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($result['articles'] ?? [] as $item) {
|
||||
$model = $this->getArticleModel(['title' => $item['mainCategory']['name'], 'platform' => static::PLATFORM, 'brand' => $task->id]);
|
||||
$model->title = $item['mainCategory']['name'];
|
||||
$model->year = AppHelper::getYear($model->title);
|
||||
$model->brand = $task->id;
|
||||
$model->module = 0;
|
||||
$model->platform = self::getPlatform();
|
||||
|
||||
$saveImages = [];
|
||||
foreach ($item['mainGalleryImages'] as $image) {
|
||||
$saveImages[] = [
|
||||
'src' => 'https://fashionsnap-assets.com/asset/width=4096' . $image
|
||||
];
|
||||
}
|
||||
$model->images = json_encode($saveImages);
|
||||
$model->cover = $saveImages[0]['src'] ?? '';
|
||||
// permalink
|
||||
$model->source_url = 'https://fashionsnap.com' . $item['permalink'];
|
||||
if ($model->cover) {
|
||||
$isSuccess = $model->save();
|
||||
}
|
||||
}
|
||||
|
||||
if ($isSuccess) {
|
||||
$brandModel = AppBrand::find($task->id);
|
||||
$brandModel->spider_origin = self::getPlatform();
|
||||
$brandModel->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
117
app/Command/spider/TheImpressionStreetCommand.php
Executable file
117
app/Command/spider/TheImpressionStreetCommand.php
Executable file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command\spider;
|
||||
|
||||
use App\Enums\ArticleModuleEnum;
|
||||
use App\Helpers\AppHelper;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
#[Command]
|
||||
class TheImpressionStreetCommand extends BaseSpider
|
||||
{
|
||||
protected const PLATFORM = 'theimpression-street';
|
||||
|
||||
public function __construct(protected ContainerInterface $container)
|
||||
{
|
||||
parent::__construct('spider:theimpression-street');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('自动采集 https://theimpression.com/street-style');
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
parent::execute($input, $output);
|
||||
|
||||
$url = 'https://theimpression.com/street-style';
|
||||
[$res, $httpCode] = $this->request($url);
|
||||
|
||||
if ($httpCode != 200) {
|
||||
$this->debugPrint("{$url} 请求失败.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 取banner的图
|
||||
(new Crawler($res))
|
||||
->filter('.parallax .mask-overlay')->each(function ($node) {
|
||||
$href = $node->attr('href');
|
||||
$text = trim($node->attr('aria-label'));
|
||||
$this->debugPrint("标题: {$text}");
|
||||
$this->debugPrint("链接: {$href}");
|
||||
$this->getDetail($href, $text);
|
||||
});
|
||||
|
||||
$articleList = [];
|
||||
// 取前五十页
|
||||
for ($i = 1; $i < 2; $i++) {
|
||||
$url = "https://theimpression.com/wp-json/codetipi-zeen/v1/block?paged={$i}&type=1&data%5Bargs%5D%5Bcat%5D=1";
|
||||
[$res, $httpCode] = $this->request($url);
|
||||
if ($httpCode != 200) {
|
||||
$this->debugPrint("{$url} 请求失败.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$res = json_decode($res, true);
|
||||
(new Crawler($res[1]))
|
||||
->filter('article')->each(function (Crawler $node) use (&$articleList) {
|
||||
|
||||
$href = $node->filter('.mask-img')->attr('href', '');
|
||||
$title = $node->filter('.title-wrap')->text('');
|
||||
if (!$href || !$title) {
|
||||
$this->debugPrint("找不到标题或链接.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->getDetail($href, $title);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
// return 0;
|
||||
}
|
||||
|
||||
|
||||
public function getDetail(string $url, $title)
|
||||
{
|
||||
$model = $this->getArticleModel(['title' => $title, 'platform' => static::getPlatform(), 'brand' => 0]);
|
||||
$model->title = $title;
|
||||
$model->platform = static::getPlatform();
|
||||
$model->module = ArticleModuleEnum::STREET->value;
|
||||
$model->year = AppHelper::getYear($title);
|
||||
|
||||
[$res, $httpCode] = $this->request($url);
|
||||
$model->source_url = $url;
|
||||
if ($httpCode != 200) {
|
||||
$this->debugPrint("{$url} 请求失败.");
|
||||
return 0;
|
||||
}
|
||||
$images = [];
|
||||
|
||||
(new Crawler($res))
|
||||
->filter('figure a img')->each(function ($node) use (&$images) {
|
||||
if ($node->attr('src') && !isset($images[$node->attr('src')])) {
|
||||
$this->debugPrint("采集图片: {$node->attr('src')}");
|
||||
$images[$node->attr('src')] = [
|
||||
'src' => $node->attr('src')
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
if ($images) {
|
||||
$model->cover = current($images)['src'];
|
||||
$model->images = json_encode(array_values($images));
|
||||
$model->save();
|
||||
}
|
||||
}
|
||||
}
|
189
app/Command/spider/VogueCommand.php
Executable file
189
app/Command/spider/VogueCommand.php
Executable file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command\spider;
|
||||
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppBrand;
|
||||
use Hyperf\Command\Annotation\Command;
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use function Swoole\Coroutine\run;
|
||||
|
||||
#[Command]
|
||||
class VogueCommand extends BaseSpider
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $baseUrl = 'https://www.vogue.com';
|
||||
|
||||
protected const PLATFORM = 'vogue';
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('spider:vogue');
|
||||
ini_set('pcre.backtrack_limit', '-1');
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->setDescription('自动采集vogue.com');
|
||||
$this->addOption('brandId', 'b', InputOption::VALUE_OPTIONAL, '指定的品牌id.', false);
|
||||
$this->addOption('forceUpdate', 'f', InputOption::VALUE_NEGATABLE, '是否对已经保存的数据进行强制更新.', false);
|
||||
$this->addOption('onlyPlatform', 'o', InputOption::VALUE_NEGATABLE, '是否只对当前平台品牌更新.', false);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->setCommandConfigure($input->getOptions());
|
||||
|
||||
run(function () {
|
||||
// 最大查询的品牌数量, 防止同时最大协程数都有子数据, 导致无法创建协程的问题。
|
||||
$maxBrandExecuteCount = $this->maxCo / 2;
|
||||
$currentBrandExecute = 0;
|
||||
foreach ($this->_getTask() as $task) {
|
||||
$currentBrandExecute++;
|
||||
|
||||
$this->createCoroutine(function () use ($task, &$currentBrandExecute) {
|
||||
$this->spiderStart($task);
|
||||
$currentBrandExecute--;
|
||||
});
|
||||
|
||||
while (true) {
|
||||
if ($currentBrandExecute > $maxBrandExecuteCount) {
|
||||
Coroutine::sleep(1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Coroutine::sleep(60);
|
||||
exit(0);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function _getTask(): \Generator
|
||||
{
|
||||
$query = AppBrand::query();
|
||||
$brandId = $this->getCommandConfigure('brandId');
|
||||
$onlyPlatform = $this->getCommandConfigure('onlyPlatform');
|
||||
if ($brandId) {
|
||||
$query->where(['id' => $brandId]);
|
||||
} else {
|
||||
$query->where('id', '>', 1)->when($onlyPlatform, fn($q) => $q->where('spider_origin', static::PLATFORM))->orderBy('id');
|
||||
}
|
||||
|
||||
foreach ($query->cursor() as $row) {
|
||||
yield $row;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getTaskName($name): string
|
||||
{
|
||||
return strtolower(strtr($name, [
|
||||
'.' => '-',
|
||||
' ' => '-',
|
||||
'&' => ''
|
||||
]));
|
||||
}
|
||||
|
||||
public function spiderStart($task): void
|
||||
{
|
||||
$brandName = $this->getTaskName($task->name);
|
||||
$url = $this->getBaseUrl() . '/fashion-shows/designer/' . $brandName;
|
||||
$this->logger->info(sprintf("[Command] brandName: {$this->getTaskName($task->name)}; spiderUrl: {$url}"));
|
||||
|
||||
// 取发布会列表
|
||||
$showsList = $this->getShowsList($url);
|
||||
|
||||
foreach ($showsList as $list) {
|
||||
$this->createCoroutine(function () use ($task, $list) {
|
||||
$this->getDetail($task->id, $list);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected function getShowsList($url)
|
||||
{
|
||||
list($request, $httpCode) = $this->request($url);
|
||||
|
||||
if ($httpCode == 200) {
|
||||
preg_match_all('/window.__PRELOADED_STATE__ = ([\s\S]*?);<\/script>/', $request, $matches);
|
||||
$val = json_decode(current(end($matches)), true);
|
||||
return $val['transformed']['runwayDesignerContent']['designerCollections'] ?? [];
|
||||
} else {
|
||||
$this->logger->info('未找到数据.');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function getDetail(int $brandId, array $info)
|
||||
{
|
||||
$model = $this->getArticleModel(['brand' => $brandId, 'title' => $info['hed']]);
|
||||
|
||||
// 如果不是force update
|
||||
// 不更新原来的数据
|
||||
if ($model->id && $this->getCommandConfigure('forceUpdate') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$model->title = $info['hed'];
|
||||
$model->images = json_encode([]);
|
||||
$model->platform = self::PLATFORM;
|
||||
$model->brand = $brandId;
|
||||
$model->module = 0;
|
||||
$model->year = AppHelper::getYear($info['hed']);
|
||||
|
||||
// 获取图片
|
||||
$pageUri = $info['url'];
|
||||
$requestUrl = $this->getBaseUrl() . $pageUri . '/slideshow/collection';
|
||||
$this->logger->info("正在匹配发布会详情 {$requestUrl}");
|
||||
$model->source_url = $requestUrl;
|
||||
$matches = [];
|
||||
list($result, $httpCode) = $this->request($requestUrl);
|
||||
|
||||
if ($httpCode != 200 || !$result) {
|
||||
$this->logger->warning($requestUrl . '请求失败.');
|
||||
return;
|
||||
}
|
||||
|
||||
preg_match_all('/window\.__PRELOADED_STATE__ = (.*?);</s', $result, $matches);
|
||||
|
||||
$saveUrl = $detailUrl = [];
|
||||
if (count($matches) > 1) {
|
||||
$val = json_decode(current($matches[1]), true);
|
||||
$images = $val['transformed']['runwayGalleries']['galleries'][0]['items'] ?? false;
|
||||
|
||||
if ($images === false) {
|
||||
$this->logger->warning($requestUrl . '获取图片失败.');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (is_array($images) ? $images : [] as $img) {
|
||||
$saveUrl[] = [
|
||||
'src' => $img['image']['sources']['xxl']['url']
|
||||
];
|
||||
foreach ($img['details'] ?? [] as $detail) {
|
||||
$detailUrl[] = ['src' => $detail['image']['sources']['xxl']['url']];
|
||||
}
|
||||
}
|
||||
$model->images = json_encode($saveUrl);
|
||||
$model->cover = $saveUrl[0]['src'];
|
||||
}
|
||||
|
||||
$model->save();
|
||||
|
||||
$this->logger->info("end: {$requestUrl}");
|
||||
}
|
||||
}
|
25
app/Constants/ErrorCode.php
Executable file
25
app/Constants/ErrorCode.php
Executable file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Constants;
|
||||
|
||||
use Hyperf\Constants\AbstractConstants;
|
||||
use Hyperf\Constants\Annotation\Constants;
|
||||
|
||||
#[Constants]
|
||||
class ErrorCode extends AbstractConstants
|
||||
{
|
||||
/**
|
||||
* @Message("Server Error!")
|
||||
*/
|
||||
public const SERVER_ERROR = 500;
|
||||
}
|
30
app/Controller/AbstractController.php
Executable file
30
app/Controller/AbstractController.php
Executable file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
#[Inject]
|
||||
protected ContainerInterface $container;
|
||||
|
||||
#[Inject]
|
||||
protected RequestInterface $request;
|
||||
|
||||
#[Inject]
|
||||
protected ResponseInterface $response;
|
||||
}
|
17
app/Controller/Hicontroller.php
Executable file
17
app/Controller/Hicontroller.php
Executable file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Grpc\HiReply;
|
||||
use Grpc\HiUser;
|
||||
|
||||
class Hicontroller extends AbstractController
|
||||
{
|
||||
public function sayHello(HiUser $user)
|
||||
{
|
||||
$message = new HiReply();
|
||||
$message->setMessage("Hello World");
|
||||
$message->setUser($user);
|
||||
return $message;
|
||||
}
|
||||
}
|
46
app/Controller/IndexController.php
Executable file
46
app/Controller/IndexController.php
Executable file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Helpers\ExcelHelper;
|
||||
use App\Model\AppKeywordsMonitor;
|
||||
use App\Model\AppKeywordsMonitorResult;
|
||||
use App\Model\AppKeywordsMonitorTask;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\HttpServer\Annotation\AutoController;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\View\RenderInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
#[AutoController]
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$user = $this->request->input('user', 'Hyperf');
|
||||
$method = $this->request->getMethod();
|
||||
|
||||
return [
|
||||
'method' => $method,
|
||||
'message' => "Hello {$user}.",
|
||||
];
|
||||
}
|
||||
|
||||
public function test(RenderInterface $render)
|
||||
{
|
||||
return $render->render('index', ['name' => 'Hyperf']);
|
||||
}
|
||||
|
||||
}
|
159
app/Controller/UploadController.php
Executable file
159
app/Controller/UploadController.php
Executable file
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Helpers\AppHelper;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Qiniu\Auth;
|
||||
use Qiniu\Storage\UploadManager;
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
#[Controller(prefix: 'upload')]
|
||||
class UploadController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @url common/uploadImage
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
// #[RequestMapping(path: "upload-image", methods: "post")]
|
||||
public function uploadImage(): array
|
||||
{
|
||||
$baseConfigKey = 'plugin.admin.upload.qiniu.';
|
||||
// 需要填写你的 Access Key 和 Secret Key
|
||||
$accessKey = config('upload.qiniu.access_key');
|
||||
$secretKey = config('upload.qiniu.secret_key');
|
||||
|
||||
$bucket = config('upload.qiniu.bucket');
|
||||
$file = $this->request->file('file');
|
||||
// 构建鉴权对象
|
||||
$auth = new Auth($accessKey, $secretKey);
|
||||
$returnBody = '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}';
|
||||
$policy = array(
|
||||
'returnBody' => $returnBody
|
||||
);
|
||||
// 生成上传 Token
|
||||
$token = $auth->uploadToken($bucket, policy: $policy);
|
||||
// 要上传文件的本地路径
|
||||
// 上传到存储后保存的文件名
|
||||
$filePath = $file->getRealPath();
|
||||
$key = date('Y-m') . '/' . $file->getClientFilename();
|
||||
// 初始化 UploadManager 对象并进行文件的上传。
|
||||
$uploadMgr = new UploadManager();
|
||||
// 调用 UploadManager 的 putFile 方法进行文件的上传。
|
||||
list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath, null, 'application/octet-stream', true, null, 'v2');
|
||||
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '上传成功@@',
|
||||
'data' => [
|
||||
'base_url' => AppHelper::getImageBaseUrl(),
|
||||
'base_path' => $ret['key'],
|
||||
'src' => AppHelper::getImageBaseUrl() . $ret['key'],
|
||||
'name' => $ret['key'],
|
||||
'size' => $ret['fsize'],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
#[RequestMapping(path: "image", methods: "post")]
|
||||
public function image()
|
||||
{
|
||||
$file = $file = $this->request->file('file');
|
||||
|
||||
if (!$file || $file->getError() !== UPLOAD_ERR_OK) {
|
||||
return $this->response->json(['code' => 400, 'msg' => '文件上传失败']);
|
||||
}
|
||||
|
||||
// 校验类型
|
||||
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (!in_array($file->getClientMediaType(), $allowedTypes)) {
|
||||
return $this->response->json(['code' => 415, 'msg' => '不支持的图片格式']);
|
||||
}
|
||||
|
||||
// 限制大小 (例如 5MB)
|
||||
$maxSize = 5 * 1024 * 1024;
|
||||
if ($file->getSize() > $maxSize) {
|
||||
return $this->response->json(['code' => 413, 'msg' => '图片大小不能超过 5MB']);
|
||||
}
|
||||
|
||||
// 保存路径
|
||||
$filename = uniqid() . '.' . pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
|
||||
$targetPath = BASE_PATH . '/uploads/' . $filename;
|
||||
|
||||
// 确保目录存在
|
||||
if (!is_dir(dirname($targetPath))) {
|
||||
mkdir(dirname($targetPath), 0777, true);
|
||||
}
|
||||
|
||||
|
||||
// 移动文件
|
||||
$file->moveTo($targetPath);
|
||||
|
||||
// 构造返回 URL
|
||||
$url = '/uploads/' . $filename;
|
||||
|
||||
return $this->response->json([
|
||||
'errno' => 0,
|
||||
'msg' => '上传成功!!',
|
||||
'data' => [
|
||||
'url' => 'http://' . '127.0.0.1:9503' . $url,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传关键词监控页面
|
||||
* @url upload/search-image
|
||||
*/
|
||||
#[RequestMapping(path: 'search-image', methods: 'post')]
|
||||
public function uploadSearchImage()
|
||||
{
|
||||
$file = $file = $this->request->file('file');
|
||||
|
||||
if (!$file || $file->getError() !== UPLOAD_ERR_OK) {
|
||||
return $this->response->json(['code' => 400, 'msg' => '文件上传失败']);
|
||||
}
|
||||
|
||||
// 校验类型
|
||||
// $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
// if (!in_array($file->getClientMediaType(), $allowedTypes)) {
|
||||
// return $this->response->json(['code' => 415, 'msg' => '不支持的图片格式']);
|
||||
// }
|
||||
|
||||
// 限制大小 (例如 5MB)
|
||||
$maxSize = 5 * 1024 * 1024;
|
||||
if ($file->getSize() > $maxSize) {
|
||||
return $this->response->json(['code' => 413, 'msg' => '图片大小不能超过 5MB']);
|
||||
}
|
||||
|
||||
// 保存路径
|
||||
$filename = uniqid() . '.' . pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
|
||||
$date = date('m-d');
|
||||
$targetPath = BASE_PATH . '/uploads/' . $filename;
|
||||
|
||||
// 确保目录存在
|
||||
if (!is_dir(dirname($targetPath))) {
|
||||
mkdir(dirname($targetPath), 0777, true);
|
||||
}
|
||||
|
||||
|
||||
// 移动文件
|
||||
$file->moveTo($targetPath);
|
||||
|
||||
// 构造返回 URL
|
||||
$url = '/uploads/' . $filename;
|
||||
|
||||
return $this->response->json([
|
||||
'errno' => 0,
|
||||
'msg' => '上传成功~~',
|
||||
'data' => [
|
||||
'url' => 'http://' . '127.0.0.1:9503' . $url,
|
||||
'file_name' => $url
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
87
app/Controller/admin/ConfigController.php
Executable file
87
app/Controller/admin/ConfigController.php
Executable file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use Hyperf\HttpServer\Annotation\AutoController;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
|
||||
#[Controller]
|
||||
class ConfigController extends AbstractController
|
||||
{
|
||||
#[RequestMapping(path: '', methods: 'get')]
|
||||
public function index()
|
||||
{
|
||||
return '{
|
||||
"logo": {
|
||||
"title": "舆情管理后台",
|
||||
"image": "/app/admin/admin/images/logo.png"
|
||||
},
|
||||
"menu": {
|
||||
"data": "/admin/menu-config",
|
||||
"method": "GET",
|
||||
"accordion": true,
|
||||
"collapse": false,
|
||||
"control": false,
|
||||
"controlWidth": 2000,
|
||||
"select": "0",
|
||||
"async": true
|
||||
},
|
||||
"tab": {
|
||||
"enable": false,
|
||||
"keepState": true,
|
||||
"session": true,
|
||||
"preload": false,
|
||||
"max": "30",
|
||||
"index": {
|
||||
"id": "0",
|
||||
"href": "\/admin\/dashboard",
|
||||
"title": "仪表盘"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"defaultColor": "2",
|
||||
"defaultMenu": "light-theme",
|
||||
"defaultHeader": "light-theme",
|
||||
"allowCustom": true,
|
||||
"banner": false
|
||||
},
|
||||
"colors": [
|
||||
{
|
||||
"id": "1",
|
||||
"color": "#36b368",
|
||||
"second": "#f0f9eb"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"color": "#2d8cf0",
|
||||
"second": "#ecf5ff"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"color": "#f6ad55",
|
||||
"second": "#fdf6ec"
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"color": "#f56c6c",
|
||||
"second": "#fef0f0"
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"color": "#3963bc",
|
||||
"second": "#ecf5ff"
|
||||
}
|
||||
],
|
||||
"other": {
|
||||
"keepLoad": "500",
|
||||
"autoHead": false,
|
||||
"footer": false
|
||||
},
|
||||
"header": {
|
||||
"message": false
|
||||
}
|
||||
}';
|
||||
}
|
||||
}
|
17
app/Controller/admin/DashboardController.php
Executable file
17
app/Controller/admin/DashboardController.php
Executable file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin;
|
||||
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\View\RenderInterface;
|
||||
|
||||
#[Controller]
|
||||
class DashboardController
|
||||
{
|
||||
#[RequestMapping(path: '', methods: 'get')]
|
||||
public function index(RenderInterface $render)
|
||||
{
|
||||
return $render->render('dashboard');
|
||||
}
|
||||
}
|
42
app/Controller/admin/IndexController.php
Executable file
42
app/Controller/admin/IndexController.php
Executable file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin;
|
||||
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Helpers\TreeHelper;
|
||||
use App\Model\AppAdminMenu;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\View\RenderInterface;
|
||||
|
||||
#[Controller(prefix: 'admin')]
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
#[RequestMapping(path: '', methods: 'get')]
|
||||
public function index(RenderInterface $render)
|
||||
{
|
||||
return $render->render('admin');
|
||||
}
|
||||
|
||||
#[RequestMapping(path: 'menu-config', methods: 'get')]
|
||||
public function menuConfig()
|
||||
{
|
||||
$items = AppAdminMenu::orderBy('weight', 'DESC')->get()->toArray();
|
||||
|
||||
$formatted_items = [];
|
||||
foreach ($items as $item) {
|
||||
$item['pid'] = (int)$item['pid'];
|
||||
$item['name'] = $item['title'];
|
||||
$item['value'] = $item['id'];
|
||||
$item['icon'] = $item['icon'] ? "layui-icon {$item['icon']}" : '';
|
||||
$formatted_items[] = $item;
|
||||
}
|
||||
return [
|
||||
'code' => 0,
|
||||
'data' => TreeHelper::getTree($formatted_items)
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
48
app/Controller/admin/KeywordsController.php
Normal file
48
app/Controller/admin/KeywordsController.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\View\Render;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
#[Controller(prefix: 'admin/keywords')]
|
||||
class KeywordsController extends AbstractController
|
||||
{
|
||||
|
||||
/**
|
||||
* 舆情关键词列表
|
||||
* @url /admin/keywords/monitor
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor', methods: 'get')]
|
||||
public function monitor(Render $render): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $render->render('keywords/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增舆情监控关键词页面
|
||||
* @url /admin/keywords/monitor/insert
|
||||
* @param Render $render
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor/insert', methods: 'get')]
|
||||
public function monitorInsert(Render $render): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $render->render('keywords/insert');
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑舆情监控关键词页面
|
||||
* @url /admin/keywords/monitor/view
|
||||
* @param Render $render
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor/view', methods: 'get')]
|
||||
public function monitorUpdate(Render $render): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $render->render('keywords/view');
|
||||
}
|
||||
}
|
34
app/Controller/admin/MenuController.php
Executable file
34
app/Controller/admin/MenuController.php
Executable file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Helpers\TreeHelper;
|
||||
use App\Model\AppAdminMenu;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\View\RenderInterface;
|
||||
use plugin\admin\app\common\Tree;
|
||||
|
||||
#[Controller(prefix: 'admin/menu')]
|
||||
class MenuController extends AbstractController
|
||||
{
|
||||
|
||||
#[RequestMapping(path: '', methods: 'get')]
|
||||
public function index(RenderInterface $render)
|
||||
{
|
||||
return $render->render('menu/index');
|
||||
}
|
||||
|
||||
#[RequestMapping(path: 'insert', methods: 'get')]
|
||||
public function insert(RenderInterface $render)
|
||||
{
|
||||
return $render->render('menu/insert');
|
||||
}
|
||||
|
||||
#[RequestMapping(path: 'update', methods: 'get')]
|
||||
public function update(RenderInterface $render)
|
||||
{
|
||||
return $render->render('menu/update');
|
||||
}
|
||||
}
|
42
app/Controller/admin/NewsController.php
Executable file
42
app/Controller/admin/NewsController.php
Executable file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Model\AppNews;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\View\RenderInterface;
|
||||
|
||||
#[Controller(prefix: 'admin/news')]
|
||||
class NewsController extends AbstractController
|
||||
{
|
||||
#[RequestMapping(path: '', methods: 'get')]
|
||||
public function index(RenderInterface $render): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $render->render('news/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看新闻详情
|
||||
* @url /admin/news/view
|
||||
*/
|
||||
#[RequestMapping(path: 'view', methods: 'get')]
|
||||
public function view(RenderInterface $render): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$id = $this->request->query('id');
|
||||
$query = AppNews::where('id', $id)->first()->toArray();
|
||||
|
||||
return $render->render('news/view');
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增新闻页面
|
||||
* @url /admin/news/insert
|
||||
*/
|
||||
#[RequestMapping(path: 'insert', methods: 'get')]
|
||||
public function insert(RenderInterface $render): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $render->render('news/insert');
|
||||
}
|
||||
}
|
24
app/Controller/admin/SpiderArticleController.php
Executable file
24
app/Controller/admin/SpiderArticleController.php
Executable file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\View\RenderInterface;
|
||||
|
||||
#[Controller(prefix: 'admin/spider-article')]
|
||||
class SpiderArticleController extends AbstractController
|
||||
{
|
||||
#[RequestMapping(path: '', methods: 'get')]
|
||||
public function index(RenderInterface $render): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $render->render('spider-article/index');
|
||||
}
|
||||
|
||||
#[RequestMapping(path: 'view', methods: 'get')]
|
||||
public function view(RenderInterface $render): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $render->render('spider-article/view');
|
||||
}
|
||||
}
|
113
app/Controller/admin/api/ArticleController.php
Executable file
113
app/Controller/admin/api/ArticleController.php
Executable file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin\api;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Enums\ArticlePublishedStatusEnum;
|
||||
use App\FormModel\admin\articles\ModifyModel;
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppArticle;
|
||||
use App\Model\AppBrand;
|
||||
use App\Model\AppNews;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
|
||||
#[Controller(prefix: 'admin/api')]
|
||||
class ArticleController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* 文章列表
|
||||
* @url /admin/api/article
|
||||
* @return array
|
||||
*/
|
||||
#[RequestMapping(path:'articles', methods: 'get')]
|
||||
public function index(): array
|
||||
{
|
||||
$publishedStatus = $this->request->query('published_status', '');
|
||||
$createdFilter = $this->request->query('created_at', [date('Y-m-d', strtotime('-1 month')), date('Y-m-d', time())]);
|
||||
foreach ($createdFilter as $index => &$item) {
|
||||
if ($index == 0) {
|
||||
$item = strtotime($item . ' 00:00:00');
|
||||
}
|
||||
|
||||
if ($index == 1) {
|
||||
$item = strtotime($item . ' 23:59:59');
|
||||
}
|
||||
}
|
||||
|
||||
$query = AppNews::formatQuery(['module', 'published_status', 'location'])
|
||||
->select(['id'])
|
||||
->when($publishedStatus !== '', fn($q) => $q->where('published_status', $publishedStatus))
|
||||
->whereBetween('created_at', $createdFilter)
|
||||
->orderBy('created_at', 'desc');
|
||||
$pagination = $query->paginate($this->request->input('limit', 10), page: $this->request->input('page'));
|
||||
foreach ($pagination->items() as $item) {
|
||||
$ids[] = $item->id;
|
||||
}
|
||||
|
||||
$value = AppNews::find($ids, ['aid', 'title', 'module', 'published_status', 'location'])->toArray();
|
||||
return ['code' => 0, 'msg' => 'ok', 'count' => $pagination->total(), 'data' => $value];
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 新增/编辑文章
|
||||
// * @url /api/v1/articles/save
|
||||
// * @return array
|
||||
// */
|
||||
// #[RequestMapping(path:'save', methods: 'post')]
|
||||
// public function save(): array
|
||||
// {
|
||||
// $id = $this->request->input('id');
|
||||
// $model = new ArticlesModel();
|
||||
// if ($id) {
|
||||
// $model->edit();
|
||||
// } else {
|
||||
// $model->create();
|
||||
// }
|
||||
//
|
||||
// return [
|
||||
// 'code' => 0,
|
||||
// 'message' => 'ok'
|
||||
// ];
|
||||
// }
|
||||
|
||||
#[RequestMapping(path:'brand-search', methods: 'get')]
|
||||
public function brandSearch(): array
|
||||
{
|
||||
$input = $this->request->input('brand');
|
||||
$query = AppBrand::query()->select(['name', 'cn_name', 'id'])->where('name', 'like', "%$input%")
|
||||
->orWhere('cn_name', 'like', "%$input%")
|
||||
->get()->toArray();
|
||||
|
||||
return ['code' => 0, 'msg' => 'ok', 'data' => $query];
|
||||
}
|
||||
|
||||
#[RequestMapping(path:'view', methods: 'get')]
|
||||
public function view(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$query = AppNews::formatQuery(['images', 'brand_name', 'translate_title'])
|
||||
->select(['brand as brand_name', 'brand', 'images', 'id', 'title', 'title as translate_title', 'cover', 'aid', 'location'])
|
||||
->where('aid', $this->request->query('id'));
|
||||
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok', 'data' => $query->first()]);
|
||||
}
|
||||
|
||||
#[RequestMapping(path:'publish', methods: 'post')]
|
||||
public function publish()
|
||||
{
|
||||
$query = AppArticle::where('aid', $this->request->post('aid'))->first();
|
||||
$query->published_status = ArticlePublishedStatusEnum::TRUE->value;
|
||||
$query->published_at = time();
|
||||
$query->save();
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
#[RequestMapping(path:'update', methods: 'post')]
|
||||
public function update()
|
||||
{
|
||||
$model = new ModifyModel();
|
||||
$model->setAttributes($this->request->post());
|
||||
$model->update();
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
188
app/Controller/admin/api/KeywordsController.php
Normal file
188
app/Controller/admin/api/KeywordsController.php
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin\api;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Helpers\ExcelHelper;
|
||||
use App\Model\AppKeywordsMonitor;
|
||||
use App\Model\AppKeywordsMonitorResult;
|
||||
use App\Model\AppKeywordsMonitorTask;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Hyperf\View\RenderInterface;
|
||||
use Laminas\Stdlib\ArrayUtils;
|
||||
|
||||
#[Controller(prefix: 'admin/api/keywords')]
|
||||
class KeywordsController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* 列表数据
|
||||
* @url /admin/api/keywords/monitor
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor', methods: 'get')]
|
||||
public function monitor(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$ids = [];
|
||||
$query = AppKeywordsMonitor::query()
|
||||
->select(['id'])
|
||||
->where('is_delete', 0)
|
||||
->orderBy('id', 'desc');
|
||||
$pagination = $query->paginate($this->request->input('limit', 10), page: $this->request->input('page'));
|
||||
foreach ($pagination->items() as $item) {
|
||||
$ids[] = $item->id;
|
||||
}
|
||||
|
||||
$value = AppKeywordsMonitor::query()->whereIn('id', $ids)->orderBy('id', 'desc')->get()->toArray();
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok', 'count' => $pagination->total(), 'data' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增关键词
|
||||
* @url /admin/api/keywords/monitor/insert
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor/insert', methods: 'post')]
|
||||
public function monitorInsert(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$keyword = $this->request->post('keyword');
|
||||
|
||||
$query = AppKeywordsMonitor::query()->where([
|
||||
['keyword', $keyword],
|
||||
['is_delete', 0]
|
||||
])->get()->toArray();
|
||||
if ($query) {
|
||||
return $this->response->json(['code' => 400, 'msg' => '已重复添加']);
|
||||
}
|
||||
|
||||
$query = new AppKeywordsMonitor();
|
||||
|
||||
$query->keyword = $keyword;
|
||||
|
||||
$query->save();
|
||||
|
||||
foreach ($this->request->post('platform', []) as $platform) {
|
||||
$task = new AppKeywordsMonitorTask();
|
||||
$task->keyword = $keyword;
|
||||
$task->aid = $query->id;
|
||||
$task->platform = $platform;
|
||||
$task->save();
|
||||
}
|
||||
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看关键词
|
||||
* @url /admin/api/keywords/monitor/view
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor/view', methods: 'get')]
|
||||
public function monitorView(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$id = $this->request->input('id');
|
||||
|
||||
$query = AppKeywordsMonitor::query()->where(['id' => $id])->first()->toArray();
|
||||
if (!$query) {
|
||||
return $this->response->json(['code' => 400, 'msg' => 'id 有误']);
|
||||
}
|
||||
|
||||
$query['platform'] = AppKeywordsMonitorTask::query()->select(['platform'])
|
||||
->where([
|
||||
['aid', $query['id']],
|
||||
['is_delete', 0],
|
||||
])
|
||||
->get()?->pluck('platform');
|
||||
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok', 'data' => $query]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑关键词
|
||||
* @url /admin/api/keywords/monitor/save
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor/save', methods: 'post')]
|
||||
public function monitorSave(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$id = $this->request->post('id');
|
||||
$keyword = $this->request->post('keyword');
|
||||
$platform = $this->request->post('platform', []);
|
||||
|
||||
$query = AppKeywordsMonitor::find($id);
|
||||
if (!$query) {
|
||||
return $this->response->json(['code' => 400, 'msg' => 'id 有误']);
|
||||
}
|
||||
|
||||
$query->keyword = $keyword;
|
||||
$query->save();
|
||||
|
||||
// 先全部删掉
|
||||
AppKeywordsMonitorTask::query()->where(['aid' => $id])->update(['is_delete' => 1]);
|
||||
foreach ($platform as $platformItem) {
|
||||
$query = AppKeywordsMonitorTask::query()->where('aid', $id)->first();
|
||||
if ($query) {
|
||||
$query->is_delete = 0;
|
||||
$query->save();
|
||||
} else {
|
||||
$query = new AppKeywordsMonitorTask();
|
||||
$query->platform = $platformItem;
|
||||
$query->aid = $id;
|
||||
$query->keyword = $keyword;
|
||||
$query->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok', 'data' => $query]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除关键词
|
||||
* @url /admin/api/keywords/monitor/delete
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor/delete', methods: 'post')]
|
||||
public function monitorDelete(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$id = $this->request->post('id');
|
||||
|
||||
$query = AppKeywordsMonitor::find($id);
|
||||
if (!$query) {
|
||||
return $this->response->json(['code' => 400, 'msg' => 'id 有误']);
|
||||
}
|
||||
|
||||
$query->is_delete = 1;
|
||||
$query->save();
|
||||
|
||||
AppKeywordsMonitorTask::query()->where(['aid' => $id])->update(['is_delete' => 1]);
|
||||
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok', 'data' => $query]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出关键词报表
|
||||
* @url /admin/api/keywords/monitor/export-all
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'monitor/export-all', methods: 'get')]
|
||||
public function monitorExportAll(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$res = AppKeywordsMonitorResult::query()->orderBy('aid', 'desc')->get()->toArray();
|
||||
|
||||
foreach ($res as &$v) {
|
||||
$v['keyword'] = AppKeywordsMonitorTask::find($v['aid'])->keyword;
|
||||
$v['screen_path'] = 'http://127..0.0.1:9503' . $v['screen_path'];
|
||||
}
|
||||
$fileName = date('Y-m-d') . '关键词监控结果';
|
||||
return ExcelHelper::exportData($this->response, list: $res, header: [
|
||||
['关键词', 'keyword', 'text'],
|
||||
['标题', 'title', 'text'],
|
||||
['排名', 'order', 'text'],
|
||||
['链接地址', 'url', 'text'],
|
||||
['ip归属地', 'ip_source', 'text'],
|
||||
['截图地址', 'screen_path', 'text'],
|
||||
], filename:$fileName);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
101
app/Controller/admin/api/MenuController.php
Executable file
101
app/Controller/admin/api/MenuController.php
Executable file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin\api;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Helpers\TreeHelper;
|
||||
use App\Model\AppAdminMenu;
|
||||
use App\Model\AppArticle;
|
||||
use App\Model\AppBrand;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
#[Controller(prefix: 'admin/api/menu')]
|
||||
class MenuController extends AbstractController
|
||||
{
|
||||
#[RequestMapping(path:'', methods: 'get')]
|
||||
public function index(): array
|
||||
{
|
||||
$request = $this->request;
|
||||
$query = AppAdminMenu::query()->orderBy('id');
|
||||
$pagination = $query->paginate($request->input('limit', 10), page: $request->input('page'));
|
||||
$data = $query->get()->toArray();
|
||||
// $data['cover'] = AppHelper::setImageUrl($query['cover']);
|
||||
|
||||
// foreach ($data as &$v) {
|
||||
// $v['cover'] = AppHelper::setImageUrl($v['cover']);
|
||||
// }
|
||||
return ['code' => 0, 'msg' => 'ok', 'count' => $pagination->total(), 'data' => $data];
|
||||
}
|
||||
|
||||
#[RequestMapping(path:'list', methods: 'get')]
|
||||
public function list(): array
|
||||
{
|
||||
$items = AppAdminMenu::query()->whereIn('type', [0, 1])->orderBy('weight', 'DESC')->get()->toArray();
|
||||
|
||||
$formatted_items = [];
|
||||
foreach ($items as $item) {
|
||||
$item['pid'] = (int)$item['pid'];
|
||||
$item['name'] = $item['title'];
|
||||
$item['value'] = $item['id'];
|
||||
$item['icon'] = $item['icon'] ? "layui-icon {$item['icon']}" : '';
|
||||
$formatted_items[] = $item;
|
||||
}
|
||||
return [
|
||||
'code' => 0,
|
||||
'data' => TreeHelper::getTree($formatted_items)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增菜单
|
||||
* @url /admin/api/menu/insert
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'insert', methods: 'post')]
|
||||
public function insert(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$model = new AppAdminMenu();
|
||||
$model->setRawAttributes($this->request->post());
|
||||
$model->pid = $model->pid ?: 0;
|
||||
$model->save();
|
||||
return $this->response->json([
|
||||
'code' => 0,
|
||||
'msg' => 'ok'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑菜单数据
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'update', methods: 'post')]
|
||||
public function update(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$model = AppAdminMenu::find($this->request->post('id'));
|
||||
$model->setRawAttributes($this->request->post());
|
||||
$model->save();
|
||||
return $this->response->json([
|
||||
'code' => 0,
|
||||
'msg' => 'ok'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览菜单数据
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path: 'view', methods: 'get')]
|
||||
public function view(): ResponseInterface
|
||||
{
|
||||
$id = $this->request->input('id');
|
||||
$model = AppAdminMenu::find($id)->toArray();
|
||||
return $this->response->json([
|
||||
'code' => 0,
|
||||
'data' => $model,
|
||||
'msg' => 'ok'
|
||||
]);
|
||||
}
|
||||
}
|
91
app/Controller/admin/api/NewsController.php
Executable file
91
app/Controller/admin/api/NewsController.php
Executable file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin\api;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Enums\ArticlePublishedStatusEnum;
|
||||
use App\FormModel\admin\articles\ModifyModel;
|
||||
use App\FormModel\admin\news\NewsFormModel;
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppArticle;
|
||||
use App\Model\AppBrand;
|
||||
use App\Model\AppNews;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
|
||||
#[Controller(prefix: 'admin/api/news')]
|
||||
class NewsController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* 文章列表
|
||||
* @url /admin/api/news
|
||||
* @return array
|
||||
*/
|
||||
#[RequestMapping(path:'', methods: 'get')]
|
||||
public function index(): array
|
||||
{
|
||||
$createdFilter = $this->request->query('created_at', [date('Y-m-d', strtotime('-1 month')), date('Y-m-d', time())]);
|
||||
foreach ($createdFilter as $index => &$item) {
|
||||
if ($index == 0) {
|
||||
$item = strtotime($item . ' 00:00:00');
|
||||
}
|
||||
|
||||
if ($index == 1) {
|
||||
$item = strtotime($item . ' 23:59:59');
|
||||
}
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
$query = AppNews::query()
|
||||
->select(['id'])
|
||||
->whereBetween('created_at', $createdFilter)
|
||||
->orderBy('id', 'desc');
|
||||
$pagination = $query->paginate($this->request->input('limit', 10), page: $this->request->input('page'));
|
||||
foreach ($pagination->items() as $item) {
|
||||
$ids[] = $item->id;
|
||||
}
|
||||
|
||||
$value = AppNews::query()->whereIn('id', $ids)->orderBy('id', 'desc')->get()->toArray();
|
||||
// $value = AppNews::find($ids, ['title', 'is_record'])->toArray();
|
||||
return ['code' => 0, 'msg' => 'ok', 'count' => $pagination->total(), 'data' => $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看文章详情信息
|
||||
* @url /admin/api/news/view
|
||||
*/
|
||||
#[RequestMapping(path:'view', methods: 'get')]
|
||||
public function view(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$query = AppNews::query()
|
||||
->where('id', $this->request->query('id'));
|
||||
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok', 'data' => $query->first()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新文章内容
|
||||
* @url /admin/api/news/update
|
||||
*/
|
||||
#[RequestMapping(path:'update', methods: 'post')]
|
||||
public function update()
|
||||
{
|
||||
$model = new NewsFormModel();
|
||||
$model->setAttributes($this->request->post());
|
||||
$model->update();
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增新新闻接口
|
||||
* @url /admin/api/news/insert
|
||||
*/
|
||||
#[RequestMapping(path:'insert', methods: 'post')]
|
||||
public function insert()
|
||||
{
|
||||
$model = new NewsFormModel();
|
||||
$model->setAttributes($this->request->post(), ['title', 'keywords', 'description', 'cover', 'content']);
|
||||
$model->insert();
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
18
app/Controller/admin/api/PermissionController.php
Executable file
18
app/Controller/admin/api/PermissionController.php
Executable file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin\api;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Model\AppAdminMenu;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
|
||||
#[Controller(prefix: 'admin/api/permission')]
|
||||
class PermissionController extends AbstractController
|
||||
{
|
||||
#[RequestMapping(path:'', methods: 'get')]
|
||||
public function index(): array
|
||||
{
|
||||
return ['code' => 0, 'msg' => 'ok', 'data' => ['*']];
|
||||
}
|
||||
}
|
59
app/Controller/admin/api/SpiderArticleController.php
Executable file
59
app/Controller/admin/api/SpiderArticleController.php
Executable file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\admin\api;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Enums\SpiderArticlePublishedStatusEnum;
|
||||
use App\FormModel\spiderArticle\ReviewModel;
|
||||
use App\Model\AppSpiderArticle;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
|
||||
|
||||
#[Controller(prefix: 'admin/api/spider-article')]
|
||||
class SpiderArticleController extends AbstractController
|
||||
{
|
||||
#[RequestMapping(path: '', methods: 'get')]
|
||||
public function index(): array
|
||||
{
|
||||
$createdFilter = $this->request->query('created_at', [date('Y-m-d', strtotime('-1 month')), date('Y-m-d', time())]);
|
||||
foreach ($createdFilter as $index => &$item) {
|
||||
if ($index == 0) {
|
||||
$item = strtotime($item . ' 00:00:00');
|
||||
}
|
||||
|
||||
if ($index == 1) {
|
||||
$item = strtotime($item . ' 23:59:59');
|
||||
}
|
||||
}
|
||||
$titleFilter = $this->request->query('title');
|
||||
$publishedStatus = $this->request->query('published_status', SpiderArticlePublishedStatusEnum::FALSE);
|
||||
$request = $this->request;
|
||||
$query = AppSpiderArticle::formatQuery(['created_at', 'module', 'published_status'])
|
||||
->select(['id', 'title', 'created_at', 'module', 'source_url', 'published_status'])
|
||||
->when($publishedStatus !== null, fn($q) => $q->where('published_status', $publishedStatus))
|
||||
->when($titleFilter, fn($q) => $q->where('title', 'like', "%{$titleFilter}%"))
|
||||
->when($this->request->query('module', '') !== '', fn($q) => $q->where('module', $this->request->query('module')))
|
||||
->whereBetween('created_at', $createdFilter)
|
||||
->orderBy('created_at', 'desc');
|
||||
$pagination = $query->paginate($request->input('limit', 10), page: $request->input('page'));
|
||||
|
||||
return ['code' => 0, 'msg' => 'ok', 'count' => $pagination->total(), 'data' => $pagination->items()];
|
||||
}
|
||||
|
||||
#[RequestMapping(path: 'view', methods: 'get')]
|
||||
public function view(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$query = AppSpiderArticle::formatQuery(['images', 'module', 'brand'])->find($this->request->query('id'));
|
||||
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok', 'data' => $query->toArray()]);
|
||||
}
|
||||
|
||||
#[RequestMapping(path: 'pre-publish', methods: 'post')]
|
||||
public function prePublish(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$prePublishModel = new ReviewModel();
|
||||
$prePublishModel->prePublish($this->request->post('id'));
|
||||
return $this->response->json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
}
|
118
app/Controller/api/expose/v1/ToolsController.php
Normal file
118
app/Controller/api/expose/v1/ToolsController.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\api\expose\v1;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Model\AppKeywordsMonitor;
|
||||
use App\Model\AppKeywordsMonitorResult;
|
||||
use App\Model\AppKeywordsMonitorTask;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
|
||||
#[Controller(prefix: "/api/expose/v1/tools")]
|
||||
class ToolsController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* 获取关键词监控的关键词
|
||||
* @url /api/expose/v1/tools/get-query-keyword
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path:'get-query-keyword', methods:'get')]
|
||||
public function getQueryKeyword(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$keyword = AppKeywordsMonitorTask::formatQuery(['created_at'])
|
||||
->where('queried_at', '<=', strtotime('-2 hours'))
|
||||
->where('is_delete', 0);
|
||||
|
||||
if ($keyword) {
|
||||
$query = $keyword->first()->toArray();
|
||||
return $this->response->json([
|
||||
'code' => 0,
|
||||
'data' => $query
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->json([
|
||||
'code' => 0,
|
||||
'data' => ''
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交监控数据
|
||||
* @url /api/expose/v1/tools/submit-query-keyword
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
#[RequestMapping(path:'submit-query-keyword', methods:'post')]
|
||||
public function submitQueryKeyword(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$requestData = $this->request->post();
|
||||
|
||||
if (!isset($requestData['length']) || !isset($requestData['keyword_id']) || !$requestData['keyword_id'] || !$requestData['image_name'] || !$requestData['ip_info']) {
|
||||
return $this->response->json([
|
||||
'code' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
// 被查的关键词id
|
||||
$aid = $requestData['keyword_id'];
|
||||
|
||||
foreach ($requestData['items'] ?? [] as $key => $value) {
|
||||
// 非负面不存储
|
||||
if (!$value['matched'] || !$value['mu']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$query = AppKeywordsMonitorResult::query()->where([
|
||||
['aid', $aid],
|
||||
['url', $value['mu']],
|
||||
['is_delete', 0],
|
||||
])->first()?->toArray();
|
||||
|
||||
// 如果这个负面存过了就不管了
|
||||
if ($query) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($title, $desc) = call_user_func(function () use ($value) {
|
||||
$content = $value['content'];
|
||||
|
||||
$ex = explode(PHP_EOL . PHP_EOL, $content);
|
||||
if (count($ex) > 1) {
|
||||
return [$ex[0], $ex[1]];
|
||||
}
|
||||
|
||||
return ['', ''];
|
||||
});
|
||||
|
||||
if (!$title || !$desc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$query = new AppKeywordsMonitorResult();
|
||||
|
||||
$query->aid = $aid;
|
||||
$query->title = $title;
|
||||
$query->description = $desc;
|
||||
$query->url = $value['mu'];
|
||||
$query->order = $value['id'];
|
||||
$ipInfo = json_decode($requestData['ip_info'], true);
|
||||
$query->ip_address = $ipInfo['ip'];
|
||||
$query->ip_source = "{$ipInfo['country']}{$ipInfo['province']}{$ipInfo['city']}";
|
||||
$query->screen_path = $requestData['image_name'];
|
||||
$query->save();
|
||||
}
|
||||
|
||||
// 更新关键词最后查询时间
|
||||
$query = AppKeywordsMonitorTask::find($aid);
|
||||
$query->queried_at = time();
|
||||
$query->save();
|
||||
|
||||
// 更新关键词最后查询时间
|
||||
$query = AppKeywordsMonitor::query()->where('id', $query->aid)->first();
|
||||
$query->queried_at = time();
|
||||
$query->save();
|
||||
|
||||
return $this->response->json([]);
|
||||
}
|
||||
}
|
70
app/Controller/api/v1/ArticlesController.php
Executable file
70
app/Controller/api/v1/ArticlesController.php
Executable file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\api\v1;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\FormModel\api\v1\ArticlesModel;
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppArticle;
|
||||
use App\Model\AppBrand;
|
||||
use Hyperf\Collection\Collection;
|
||||
use Hyperf\HttpServer\Annotation\AutoController;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
|
||||
#[Controller]
|
||||
class ArticlesController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* 文章列表
|
||||
* @url /api/v1/articles
|
||||
* @return array
|
||||
*/
|
||||
#[RequestMapping(path:'', methods: 'get')]
|
||||
public function index(): array
|
||||
{
|
||||
$request = $this->request;
|
||||
$query = Model::query()->orderBy('id');
|
||||
$pagination = $query->paginate($request->input('limit', 10), page: $request->input('page'));
|
||||
$data = $query->get()->toArray();
|
||||
// $data['cover'] = AppHelper::setImageUrl($query['cover']);
|
||||
|
||||
foreach ($data as &$v) {
|
||||
$v['cover'] = AppHelper::setImageUrl($v['cover']);
|
||||
}
|
||||
return ['code' => 0, 'msg' => 'ok', 'count' => $pagination->total(), 'data' => $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增/编辑文章
|
||||
* @url /api/v1/articles/save
|
||||
* @return array
|
||||
*/
|
||||
#[RequestMapping(path:'save', methods: 'post')]
|
||||
public function save(): array
|
||||
{
|
||||
$id = $this->request->input('id');
|
||||
$model = new ArticlesModel();
|
||||
if ($id) {
|
||||
$model->edit();
|
||||
} else {
|
||||
$model->create();
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => 0,
|
||||
'message' => 'ok'
|
||||
];
|
||||
}
|
||||
|
||||
#[RequestMapping(path:'brand-search', methods: 'get')]
|
||||
public function brandSearch(): array
|
||||
{
|
||||
$input = $this->request->input('brand');
|
||||
$query = Model::query()->select(['name', 'cn_name', 'id'])->where('name', 'like', "%$input%")
|
||||
->orWhere('cn_name', 'like', "%$input%")
|
||||
->get()->toArray();
|
||||
|
||||
return ['code' => 0, 'msg' => 'ok', 'data' => $query];
|
||||
}
|
||||
}
|
33
app/Controller/api/v1/UserController.php
Executable file
33
app/Controller/api/v1/UserController.php
Executable file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\api\v1;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\FormModel\api\v1\UserModel;
|
||||
use App\Request\Test;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
|
||||
#[Controller]
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
|
||||
#[RequestMapping(path: '/api/v1/user/register', methods: 'post')]
|
||||
public function register(Test $validator)
|
||||
{
|
||||
$validator = $validator->validated();
|
||||
$model = new UserModel();
|
||||
$model->register();
|
||||
return [];
|
||||
}
|
||||
|
||||
public function login()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function logout()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
16
app/Enums/ArticleModuleEnum.php
Executable file
16
app/Enums/ArticleModuleEnum.php
Executable file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ArticleModuleEnum: int
|
||||
{
|
||||
case SHOW = 0;
|
||||
case STREET = 1;
|
||||
|
||||
public function toString(): string {
|
||||
return match($this) {
|
||||
ArticleModuleEnum::SHOW => '秀场',
|
||||
ArticleModuleEnum::STREET => '街拍',
|
||||
};
|
||||
}
|
||||
}
|
20
app/Enums/ArticlePublishedStatusEnum.php
Executable file
20
app/Enums/ArticlePublishedStatusEnum.php
Executable file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ArticlePublishedStatusEnum: int
|
||||
{
|
||||
case FALSE = 0;
|
||||
|
||||
case TRUE = 1;
|
||||
|
||||
case DELETE = 2;
|
||||
|
||||
public function toString(): string {
|
||||
return match($this) {
|
||||
ArticlePublishedStatusEnum::TRUE => '已发布',
|
||||
ArticlePublishedStatusEnum::FALSE => '未发布',
|
||||
ArticlePublishedStatusEnum::DELETE => '已删除',
|
||||
};
|
||||
}
|
||||
}
|
12
app/Enums/ArticleStyleEnum.php
Executable file
12
app/Enums/ArticleStyleEnum.php
Executable file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ArticleStyleEnum: int
|
||||
{
|
||||
case NULL = 0;
|
||||
|
||||
case RESORT = 1;
|
||||
|
||||
case BRIDAL = 2;
|
||||
}
|
69
app/Enums/LocationEnum.php
Executable file
69
app/Enums/LocationEnum.php
Executable file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum LocationEnum: int
|
||||
{
|
||||
case NULL = 0;
|
||||
|
||||
case AUSTRALIA = 1;
|
||||
|
||||
case UKRAINE = 2;
|
||||
|
||||
case KIEV = 3;
|
||||
|
||||
case STOCKHOLM = 4;
|
||||
|
||||
case TOKYO = 5;
|
||||
|
||||
case BERLIN = 6;
|
||||
|
||||
case COPENHAGEN = 7;
|
||||
|
||||
case SHANGHAI = 8;
|
||||
|
||||
case SAO_PAULO = 9;
|
||||
|
||||
case TBILISI = 10;
|
||||
|
||||
case MEXICO = 11;
|
||||
|
||||
case SEOUL = 12;
|
||||
|
||||
case RUSSIA = 13;
|
||||
|
||||
|
||||
case MADRID = 14;
|
||||
case SPAIN= 15;
|
||||
|
||||
case ISTANBUL = 16;
|
||||
|
||||
case LAGOS = 17;
|
||||
|
||||
case PARIS = 18;
|
||||
|
||||
public function toString(): string {
|
||||
return match($this) {
|
||||
LocationEnum::NULL => '',
|
||||
LocationEnum::AUSTRALIA => '澳洲',
|
||||
LocationEnum::UKRAINE => '乌克兰',
|
||||
LocationEnum::KIEV => '基辅',
|
||||
LocationEnum::STOCKHOLM => '斯德哥尔摩',
|
||||
LocationEnum::TOKYO => '东京',
|
||||
LocationEnum::BERLIN => '柏林',
|
||||
LocationEnum::COPENHAGEN => '哥本哈根',
|
||||
LocationEnum::SHANGHAI => '上海',
|
||||
LocationEnum::SAO_PAULO => '圣保罗',
|
||||
LocationEnum::TBILISI => '首都',
|
||||
LocationEnum::MEXICO => '墨西哥',
|
||||
LocationEnum::SEOUL => '首尔',
|
||||
LocationEnum::RUSSIA => '俄罗斯',
|
||||
LocationEnum::MADRID => '马德里',
|
||||
LocationEnum::SPAIN => '西班牙',
|
||||
LocationEnum::ISTANBUL => '伊斯坦布尔',
|
||||
LocationEnum::LAGOS => '拉各斯',
|
||||
LocationEnum::PARIS => '巴黎',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
20
app/Enums/SpiderArticlePublishedStatusEnum.php
Executable file
20
app/Enums/SpiderArticlePublishedStatusEnum.php
Executable file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum SpiderArticlePublishedStatusEnum: int
|
||||
{
|
||||
case FALSE = 0;
|
||||
|
||||
case TRUE = 1;
|
||||
|
||||
case DELETE = 2;
|
||||
|
||||
public function toString(): string {
|
||||
return match($this) {
|
||||
SpiderArticlePublishedStatusEnum::TRUE => '已同步',
|
||||
SpiderArticlePublishedStatusEnum::FALSE => '未同步',
|
||||
SpiderArticlePublishedStatusEnum::DELETE => '已删除',
|
||||
};
|
||||
}
|
||||
}
|
29
app/Exception/BusinessException.php
Executable file
29
app/Exception/BusinessException.php
Executable file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Exception;
|
||||
|
||||
use App\Constants\ErrorCode;
|
||||
use Hyperf\Server\Exception\ServerException;
|
||||
use Throwable;
|
||||
|
||||
class BusinessException extends ServerException
|
||||
{
|
||||
public function __construct(int $code = 0, string $message = null, Throwable $previous = null)
|
||||
{
|
||||
if (is_null($message)) {
|
||||
$message = ErrorCode::getMessage($code);
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
38
app/Exception/Handler/AppExceptionHandler.php
Executable file
38
app/Exception/Handler/AppExceptionHandler.php
Executable file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Exception\Handler;
|
||||
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Throwable;
|
||||
|
||||
class AppExceptionHandler extends ExceptionHandler
|
||||
{
|
||||
public function __construct(protected StdoutLoggerInterface $logger)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(Throwable $throwable, ResponseInterface $response)
|
||||
{
|
||||
$this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
|
||||
$this->logger->error($throwable->getTraceAsString());
|
||||
return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
|
||||
}
|
||||
|
||||
public function isValid(Throwable $throwable): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
11
app/FormModel/admin/articles/ArticlesModel.php
Executable file
11
app/FormModel/admin/articles/ArticlesModel.php
Executable file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\admin\articles;
|
||||
|
||||
class ArticlesModel
|
||||
{
|
||||
public function edit()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
15
app/FormModel/admin/articles/BaseModify.php
Executable file
15
app/FormModel/admin/articles/BaseModify.php
Executable file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\admin\articles;
|
||||
|
||||
class BaseModify
|
||||
{
|
||||
|
||||
protected array $attributes = [];
|
||||
|
||||
public function setAttributes(array $modifyData): static
|
||||
{
|
||||
$this->attributes = $modifyData;
|
||||
return $this;
|
||||
}
|
||||
}
|
11
app/FormModel/admin/articles/ModifyModel.php
Executable file
11
app/FormModel/admin/articles/ModifyModel.php
Executable file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\admin\articles;
|
||||
|
||||
|
||||
use App\FormModel\admin\articles\trait\ModifyForUpdate;
|
||||
|
||||
class ModifyModel extends BaseModify
|
||||
{
|
||||
use ModifyForUpdate;
|
||||
}
|
12
app/FormModel/admin/articles/module/BaseModule.php
Executable file
12
app/FormModel/admin/articles/module/BaseModule.php
Executable file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\admin\articles\module;
|
||||
|
||||
class BaseModule
|
||||
{
|
||||
protected array $attributes = [];
|
||||
public function setAttributes()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
8
app/FormModel/admin/articles/module/ShowsModel.php
Executable file
8
app/FormModel/admin/articles/module/ShowsModel.php
Executable file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\admin\articles\module;
|
||||
|
||||
class ShowsModel
|
||||
{
|
||||
|
||||
}
|
8
app/FormModel/admin/articles/module/StreetModel.php
Executable file
8
app/FormModel/admin/articles/module/StreetModel.php
Executable file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\admin\articles\module;
|
||||
|
||||
class StreetModel extends BaseModule
|
||||
{
|
||||
|
||||
}
|
23
app/FormModel/admin/articles/trait/ModifyForUpdate.php
Executable file
23
app/FormModel/admin/articles/trait/ModifyForUpdate.php
Executable file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\admin\articles\trait;
|
||||
|
||||
use App\Model\AppArticle;
|
||||
|
||||
trait ModifyForUpdate
|
||||
{
|
||||
protected array $canUpdateFields = ['title', 'cover', 'images', 'brand', 'location'];
|
||||
|
||||
public function update()
|
||||
{
|
||||
$query = AppArticle::where(['aid' => $this->attributes['aid']])->first();
|
||||
foreach ($this->canUpdateFields as $field) {
|
||||
$val = $this->attributes[$field] ?? null;
|
||||
if ($val !== null) {
|
||||
$query->{$field} = $this->attributes[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$query->save();
|
||||
}
|
||||
}
|
43
app/FormModel/admin/news/NewsFormModel.php
Normal file
43
app/FormModel/admin/news/NewsFormModel.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\admin\news;
|
||||
|
||||
use App\Model\AppNews;
|
||||
|
||||
class NewsFormModel
|
||||
{
|
||||
|
||||
private array $attributes = [];
|
||||
|
||||
public function setAttributes(array $attr, $allowProp = [])
|
||||
{
|
||||
if (!$allowProp) {
|
||||
$this->attributes = $attr;
|
||||
}
|
||||
|
||||
foreach($allowProp as $prop) {
|
||||
if (isset($attr[$prop])) {
|
||||
$this->attributes[$prop] = $attr[$prop];
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function insert()
|
||||
{
|
||||
$model = new AppNews();
|
||||
$model->setRawAttributes($this->attributes);
|
||||
$model->save();
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$model = AppNews::find($this->attributes['id']);
|
||||
$model->title = $this->attributes['title'];
|
||||
$model->keywords = $this->attributes['keywords'];
|
||||
$model->description = $this->attributes['description'];
|
||||
$model->cover = $this->attributes['cover'];
|
||||
$model->content = $this->attributes['content'];
|
||||
$model->save();
|
||||
}
|
||||
}
|
12
app/FormModel/api/BaseModel.php
Executable file
12
app/FormModel/api/BaseModel.php
Executable file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\api;
|
||||
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
|
||||
class BaseModel
|
||||
{
|
||||
#[Inject]
|
||||
protected RequestInterface $_request;
|
||||
}
|
50
app/FormModel/api/v1/ArticlesModel.php
Executable file
50
app/FormModel/api/v1/ArticlesModel.php
Executable file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\api\v1;
|
||||
|
||||
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppArticle;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
|
||||
class ArticlesModel
|
||||
{
|
||||
#[Inject]
|
||||
private readonly RequestInterface $_request;
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$query = AppArticle::find($this->_request->post('id'));
|
||||
$query->cover = AppHelper::extractImagePath($this->_request->post('cover'));
|
||||
|
||||
$query->title = $this->_request->post('title');
|
||||
$query->brand = $this->_request->post('brand');
|
||||
|
||||
$images = array_values($this->_request->post('images'));
|
||||
foreach ($images as &$image) {
|
||||
$image['src'] = AppHelper::extractImagePath($image['src']);
|
||||
}
|
||||
$query->images = json_encode($images);
|
||||
|
||||
$query->save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$query = new AppArticle();
|
||||
$query->cover = AppHelper::extractImagePath($this->_request->post('cover'));
|
||||
$query->title = $this->_request->post('title');
|
||||
$query->brand = $this->_request->post('brand');
|
||||
|
||||
$images = array_values($this->_request->post('images') ?: []);
|
||||
foreach ($images as &$image) {
|
||||
$image['src'] = AppHelper::extractImagePath($image['src']);
|
||||
}
|
||||
$query->images = json_encode($images);
|
||||
|
||||
$query->save();
|
||||
return true;
|
||||
}
|
||||
}
|
20
app/FormModel/api/v1/UserModel.php
Executable file
20
app/FormModel/api/v1/UserModel.php
Executable file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\api\v1;
|
||||
|
||||
use App\FormModel\api\BaseModel;
|
||||
use App\Model\AppUser;
|
||||
|
||||
class UserModel extends BaseModel
|
||||
{
|
||||
|
||||
public function register()
|
||||
{
|
||||
$query = new AppUser();
|
||||
$query->name = $this->_request->post('name');
|
||||
$query->password = md5($this->_request->post('password'));
|
||||
$query->email = $this->_request->post('email');
|
||||
$query->unique_id = uniqid("", true);
|
||||
$query->save();
|
||||
}
|
||||
}
|
11
app/FormModel/rpc/BaseModel.php
Executable file
11
app/FormModel/rpc/BaseModel.php
Executable file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\rpc;
|
||||
|
||||
class BaseModel
|
||||
{
|
||||
protected function getResponse(): BaseResponse
|
||||
{
|
||||
return new BaseResponse();
|
||||
}
|
||||
}
|
43
app/FormModel/rpc/v1/RpcIndexModel.php
Executable file
43
app/FormModel/rpc/v1/RpcIndexModel.php
Executable file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\rpc\v1;
|
||||
|
||||
use App\Listener\DbQueryExecutedListener;
|
||||
use App\Model\AppArticle;
|
||||
use Hyperf\Database\Query\Builder;
|
||||
use Hyperf\DbConnection\Db;
|
||||
|
||||
class RpcIndexModel
|
||||
{
|
||||
public function index(): array
|
||||
{
|
||||
$res = [];
|
||||
Db::beforeExecuting(function ($q) {
|
||||
var_dump($q);
|
||||
});
|
||||
|
||||
$minPublishedTime = strtotime('-1 month');
|
||||
|
||||
// $res['shows'] = [];
|
||||
$res['shows'] = AppArticle::formatQuery(['brand_name'])
|
||||
->select(['cover', 'aid', 'title', 'brand as brand_name', 'images_count', 'view_count'])
|
||||
->where([
|
||||
['module', 0],
|
||||
['published_status', 1],
|
||||
['year', '>=', 2022],
|
||||
['published_at', '>=', $minPublishedTime],
|
||||
])->orderBy('year', 'DESC')->orderBy('published_at', 'DESC')->limit(25)->get()->toArray();
|
||||
|
||||
$res['street'] = AppArticle::formatQuery(['location'])
|
||||
->select(['cover', 'aid', 'title', 'brand as brand_name', 'images_count', 'view_count', 'location'])
|
||||
->where([
|
||||
['module', 1],
|
||||
['published_status', 1],
|
||||
['year', '>=', 2022],
|
||||
['published_at', '>=', $minPublishedTime],
|
||||
])->orderBy('year', 'DESC')->orderBy('published_at', 'DESC')->limit(25)->get()->toArray();
|
||||
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
32
app/FormModel/spider/ReviewModel.php
Executable file
32
app/FormModel/spider/ReviewModel.php
Executable file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\spider;
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppArticle;
|
||||
use App\Model\AppSpiderArticle;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
|
||||
class ReviewModel
|
||||
{
|
||||
#[Inject]
|
||||
private readonly RequestInterface $_request;
|
||||
|
||||
public function pass($spiderArticleId)
|
||||
{
|
||||
$query = AppSpiderArticle::find($spiderArticleId);
|
||||
$model = new AppArticle();
|
||||
$model->title = $query->title;
|
||||
$model->aid = AppHelper::generateAid();
|
||||
$model->cover = $query->cover;
|
||||
$model->images = $query->images;
|
||||
$model->year = $query->year;
|
||||
$model->module = $query->module;
|
||||
$model->brand = $query->brand;
|
||||
$model->spider_article_id = $spiderArticleId;
|
||||
$model->save();
|
||||
|
||||
$query->deleted_at = time();
|
||||
$query->save();
|
||||
}
|
||||
}
|
69
app/FormModel/spiderArticle/ReviewModel.php
Executable file
69
app/FormModel/spiderArticle/ReviewModel.php
Executable file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\FormModel\spiderArticle;
|
||||
|
||||
use App\Enums\SpiderArticlePublishedStatusEnum;
|
||||
use App\Helpers\AppHelper;
|
||||
use App\Model\AppArticle;
|
||||
use App\Model\AppSpiderArticle;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
|
||||
class ReviewModel
|
||||
{
|
||||
#[Inject]
|
||||
private readonly RequestInterface $_request;
|
||||
|
||||
/**
|
||||
* 爬虫文章预发布
|
||||
* @param $spiderArticleId
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function prePublish($spiderArticleId)
|
||||
{
|
||||
try {
|
||||
Db::beginTransaction();
|
||||
|
||||
$query = AppSpiderArticle::find($spiderArticleId);
|
||||
|
||||
$model = new AppArticle();
|
||||
$model->title = $query->title;
|
||||
$model->aid = AppHelper::generateAid();
|
||||
$model->cover = $query->cover;
|
||||
$model->images = $query->images;
|
||||
$model->year = $query->year;
|
||||
$model->module = $query->module;
|
||||
$model->brand = $query->brand;
|
||||
$model->spider_article_id = $spiderArticleId;
|
||||
$model->published_status = 0;
|
||||
$model->save();
|
||||
|
||||
$query->published_status = SpiderArticlePublishedStatusEnum::TRUE->value;
|
||||
$query->published_at = time();
|
||||
$query->save();
|
||||
|
||||
Db::commit();
|
||||
} catch (\Throwable $throwable) {
|
||||
Db::rollBack();
|
||||
throw new \Exception($throwable->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($spiderArticleId)
|
||||
{
|
||||
try {
|
||||
Db::beginTransaction();
|
||||
|
||||
$query = AppSpiderArticle::find($spiderArticleId);
|
||||
$query->deleted_at = SpiderArticlePublishedStatusEnum::DELETE;
|
||||
$query->deleted_at = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
} catch (\Throwable $throwable) {
|
||||
Db::rollBack();
|
||||
throw new \Exception($throwable->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
45
app/Helpers/AppHelper.php
Executable file
45
app/Helpers/AppHelper.php
Executable file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
class AppHelper
|
||||
{
|
||||
public static function getImageBaseUrl(): string
|
||||
{
|
||||
return config('upload.qiniu.base_url');
|
||||
}
|
||||
|
||||
public static function setImageUrl(string $imageUrl)
|
||||
{
|
||||
$imageUrl = self::extractImagePath($imageUrl);
|
||||
return self::getImageBaseUrl() . $imageUrl;
|
||||
}
|
||||
|
||||
public static function extractImagePath(string $url): string
|
||||
{
|
||||
// 解析URL
|
||||
$parsedUrl = parse_url($url);
|
||||
|
||||
// 获取路径部分
|
||||
$path = $parsedUrl['path'];
|
||||
|
||||
// 去除路径前的 "/"
|
||||
return ltrim($path, '/');
|
||||
}
|
||||
|
||||
public static function generateAid(): string
|
||||
{
|
||||
return strtr(uniqid(more_entropy: true), [
|
||||
'.' => ''
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getYear(string $title)
|
||||
{
|
||||
preg_match('/([0-9]+)/', $title, $y);
|
||||
|
||||
return $y[1] ?? date('Y');
|
||||
}
|
||||
}
|
352
app/Helpers/ExcelHelper.php
Normal file
352
app/Helpers/ExcelHelper.php
Normal file
@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Exception;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Html;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xls;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Csv;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
|
||||
class ExcelHelper
|
||||
{
|
||||
#[Inject]
|
||||
protected ResponseInterface $response;
|
||||
|
||||
/**
|
||||
* 导出Excel
|
||||
*
|
||||
* @param array $list
|
||||
* @param array $header
|
||||
* @param string $filename
|
||||
* @param string $suffix
|
||||
* @param string $path 输出绝对路径
|
||||
* @return bool
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
|
||||
*/
|
||||
public static function exportData(ResponseInterface $response, $list = [], $header = [], $filename = '', $suffix = 'xlsx', $path = '')
|
||||
{
|
||||
if (!is_array($list) || !is_array($header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
!$filename && $filename = time();
|
||||
|
||||
// 初始化
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
// 写入头部
|
||||
$hk = 1;
|
||||
foreach ($header as $k => $v) {
|
||||
$sheet->setCellValue(Coordinate::stringFromColumnIndex($hk) . '1', $v[0]);
|
||||
$sheet->getStyle(Coordinate::stringFromColumnIndex($hk) . '1')->getFont()->setBold(true);
|
||||
$sheet->getColumnDimension(Coordinate::stringFromColumnIndex($hk))->setAutoSize(true);
|
||||
$hk += 1;
|
||||
}
|
||||
|
||||
// 开始写入内容
|
||||
$column = 2;
|
||||
$size = ceil(count($list) / 500);
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$buffer = array_slice($list, $i * 500, 500);
|
||||
|
||||
foreach ($buffer as $k => $row) {
|
||||
$span = 1;
|
||||
|
||||
foreach ($header as $key => $value) {
|
||||
// 解析字段
|
||||
$realData = self::formatting($header[$key], trim(self::formattingField($row, $value[1])), $row);
|
||||
// 写入excel
|
||||
$sheet->setCellValueExplicit(Coordinate::stringFromColumnIndex($span) . $column, $realData, DataType::TYPE_STRING);
|
||||
// $sheet->setCellValue(Coordinate::stringFromColumnIndex($span) . $column, $realData);
|
||||
$span++;
|
||||
}
|
||||
|
||||
$column++;
|
||||
unset($buffer[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
// 清除之前的错误输出
|
||||
// ob_end_clean();
|
||||
ob_start();
|
||||
|
||||
// 直接输出下载
|
||||
switch ($suffix) {
|
||||
case 'xlsx' :
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
if (!empty($path)) {
|
||||
$writer->save($path);
|
||||
} else {
|
||||
// 2. 保存到内存(使用 php://memory)
|
||||
$tempFile = 'php://memory';
|
||||
$stream = fopen($tempFile, 'w+');
|
||||
$writer->save($stream);
|
||||
rewind($stream);
|
||||
$excelOutput = stream_get_contents($stream);
|
||||
fclose($stream);
|
||||
|
||||
// 3. 返回给客户端下载
|
||||
return $response->withHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
->withHeader('Content-Disposition', 'attachment; filename=' . $filename . '.xlsx')
|
||||
->withHeader('Cache-Control', 'max-age=0')
|
||||
->raw($excelOutput);
|
||||
}
|
||||
break;
|
||||
case 'xls' :
|
||||
$writer = new Xls($spreadsheet);
|
||||
if (!empty($path)) {
|
||||
$writer->save($path);
|
||||
} else {
|
||||
header("Content-Type:application/vnd.ms-excel;charset=utf-8;");
|
||||
header("Content-Disposition:inline;filename=\"{$filename}.xls\"");
|
||||
header('Cache-Control: max-age=0');
|
||||
$writer->save('php://output');
|
||||
}
|
||||
break;
|
||||
case 'csv' :
|
||||
$writer = new Csv($spreadsheet);
|
||||
if (!empty($path)) {
|
||||
$writer->save($path);
|
||||
} else {
|
||||
header("Content-type:text/csv;charset=utf-8;");
|
||||
header("Content-Disposition:attachment; filename={$filename}.csv");
|
||||
header('Cache-Control: max-age=0');
|
||||
$writer->save('php://output');
|
||||
}
|
||||
break;
|
||||
case 'html' :
|
||||
$writer = new Html($spreadsheet);
|
||||
if (!empty($path)) {
|
||||
$writer->save($path);
|
||||
} else {
|
||||
header("Content-Type:text/html;charset=utf-8;");
|
||||
header("Content-Disposition:attachment;filename=\"{$filename}.{$suffix}\"");
|
||||
header('Cache-Control: max-age=0');
|
||||
$writer->save('php://output');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* 释放内存 */
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
unset($spreadsheet);
|
||||
$content = ob_end_flush();
|
||||
// 创建一个临时流,并将内容写入其中
|
||||
$tempStream = fopen('php://temp', 'r+');
|
||||
|
||||
if ($tempStream === false) {
|
||||
throw new \RuntimeException('Unable to open temporary stream');
|
||||
}
|
||||
|
||||
fwrite($tempStream, $content);
|
||||
rewind($tempStream);
|
||||
|
||||
// $response = new Response();
|
||||
$contentType = 'text/csv';
|
||||
return $response->withHeader('content-description', 'File Transfer')
|
||||
->withHeader('content-type', $contentType)
|
||||
->withHeader('content-disposition', "attachment; filename=text.xlsx")
|
||||
->withHeader('content-transfer-encoding', 'binary')
|
||||
->withHeader('pragma', 'public')
|
||||
->withBody(new SwooleStream($content));
|
||||
return $response->withHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
->withHeader('Content-Disposition', 'attachment; filename="exported_file.xlsx"')
|
||||
->withBody(new \GuzzleHttp\Psr7\Stream($tempStream));
|
||||
// exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出的另外一种形式(不建议使用)
|
||||
*
|
||||
* @param array $list
|
||||
* @param array $header
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public static function exportCsvData($list = [], $header = [], $filename = '')
|
||||
{
|
||||
if (!is_array($list) || !is_array($header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 清除之前的错误输出
|
||||
ob_end_clean();
|
||||
ob_start();
|
||||
|
||||
!$filename && $filename = time();
|
||||
|
||||
$html = "\xEF\xBB\xBF";
|
||||
foreach ($header as $k => $v) {
|
||||
$html .= $v[0] . "\t ,";
|
||||
}
|
||||
|
||||
$html .= "\n";
|
||||
|
||||
if (!empty($list)) {
|
||||
$info = [];
|
||||
$size = ceil(count($list) / 500);
|
||||
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$buffer = array_slice($list, $i * 500, 500);
|
||||
|
||||
foreach ($buffer as $k => $row) {
|
||||
$data = [];
|
||||
|
||||
foreach ($header as $key => $value) {
|
||||
// 解析字段
|
||||
$realData = self::formatting($header[$key], trim(self::formattingField($row, $value[1])), $row);
|
||||
$data[] = '"' . $realData . '"';
|
||||
}
|
||||
|
||||
$info[] = implode("\t ,", $data) . "\t ,";
|
||||
unset($data, $buffer[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
$html .= implode("\n", $info);
|
||||
}
|
||||
|
||||
header("Content-type:text/csv");
|
||||
header("Content-Disposition:attachment; filename={$filename}.csv");
|
||||
echo $html;
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入
|
||||
*
|
||||
* @param $filePath
|
||||
* @param int $startRow
|
||||
* @return array|mixed
|
||||
* @throws Exception
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
|
||||
*/
|
||||
public static function import($filePath, $startRow = 1)
|
||||
{
|
||||
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
|
||||
$reader->setReadDataOnly(true);
|
||||
if (!$reader->canRead($filePath)) {
|
||||
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();
|
||||
// setReadDataOnly Set read data only 只读单元格的数据,不格式化 e.g. 读时间会变成一个数据等
|
||||
$reader->setReadDataOnly(true);
|
||||
|
||||
if (!$reader->canRead($filePath)) {
|
||||
throw new Exception('不能读取Excel');
|
||||
}
|
||||
}
|
||||
|
||||
$spreadsheet = $reader->load($filePath);
|
||||
$sheetCount = $spreadsheet->getSheetCount();// 获取sheet的数量
|
||||
|
||||
// 获取所有的sheet表格数据
|
||||
$excleDatas = [];
|
||||
$emptyRowNum = 0;
|
||||
for ($i = 0; $i < $sheetCount; $i++) {
|
||||
$currentSheet = $spreadsheet->getSheet($i); // 读取excel文件中的第一个工作表
|
||||
$allColumn = $currentSheet->getHighestColumn(); // 取得最大的列号
|
||||
$allColumn = Coordinate::columnIndexFromString($allColumn); // 由列名转为列数('AB'->28)
|
||||
$allRow = $currentSheet->getHighestRow(); // 取得一共有多少行
|
||||
|
||||
$arr = [];
|
||||
for ($currentRow = $startRow; $currentRow <= $allRow; $currentRow++) {
|
||||
// 从第1列开始输出
|
||||
for ($currentColumn = 1; $currentColumn <= $allColumn; $currentColumn++) {
|
||||
$val = $currentSheet->getCellByColumnAndRow($currentColumn, $currentRow)->getValue();
|
||||
$arr[$currentRow][] = trim($val);
|
||||
}
|
||||
|
||||
// $arr[$currentRow] = array_filter($arr[$currentRow]);
|
||||
// 统计连续空行
|
||||
if (empty($arr[$currentRow]) && $emptyRowNum <= 50) {
|
||||
$emptyRowNum++;
|
||||
} else {
|
||||
$emptyRowNum = 0;
|
||||
}
|
||||
// 防止坑队友的同事在excel里面弄出很多的空行,陷入很漫长的循环中,设置如果连续超过50个空行就退出循环,返回结果
|
||||
// 连续50行数据为空,不再读取后面行的数据,防止读满内存
|
||||
if ($emptyRowNum > 50) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$excleDatas[$i] = $arr; // 多个sheet的数组的集合
|
||||
}
|
||||
|
||||
// 这里我只需要用到第一个sheet的数据,所以只返回了第一个sheet的数据
|
||||
$returnData = $excleDatas ? array_shift($excleDatas) : [];
|
||||
|
||||
// 第一行数据就是空的,为了保留其原始数据,第一行数据就不做array_fiter操作;
|
||||
$returnData = $returnData && isset($returnData[$startRow]) && !empty($returnData[$startRow]) ? array_filter($returnData) : $returnData;
|
||||
return $returnData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化内容
|
||||
*
|
||||
* @param array $array 头部规则
|
||||
* @return false|mixed|null|string 内容值
|
||||
*/
|
||||
protected static function formatting(array $array, $value, $row)
|
||||
{
|
||||
!isset($array[2]) && $array[2] = 'text';
|
||||
|
||||
switch ($array[2]) {
|
||||
// 文本
|
||||
case 'text' :
|
||||
return $value;
|
||||
break;
|
||||
// 日期
|
||||
case 'date' :
|
||||
return !empty($value) ? date($array[3], $value) : null;
|
||||
break;
|
||||
// 选择框
|
||||
case 'selectd' :
|
||||
return $array[3][$value] ?? null;
|
||||
break;
|
||||
// 匿名函数
|
||||
case 'function' :
|
||||
return isset($array[3]) ? call_user_func($array[3], $row) : null;
|
||||
break;
|
||||
// 默认
|
||||
default :
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析字段
|
||||
*
|
||||
* @param $row
|
||||
* @param $field
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function formattingField($row, $field)
|
||||
{
|
||||
$newField = explode('.', $field);
|
||||
if (count($newField) == 1) {
|
||||
return $row[$field] ?? '';
|
||||
}
|
||||
|
||||
foreach ($newField as $item) {
|
||||
if (isset($row[$item])) {
|
||||
$row = $row[$item];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return is_array($row) ? false : $row;
|
||||
}
|
||||
}
|
355
app/Helpers/TitleHelper.php
Executable file
355
app/Helpers/TitleHelper.php
Executable file
@ -0,0 +1,355 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Enums\ArticleStyleEnum;
|
||||
use App\Enums\LocationEnum;
|
||||
|
||||
class TitleHelper
|
||||
{
|
||||
public static function translate(string $englishTitle)
|
||||
{
|
||||
$map = [
|
||||
'Madrid' => [
|
||||
[
|
||||
'preg' => '/Madrid Spring ([0-9]*?)/',
|
||||
'trans' => '春季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::MADRID->value,
|
||||
],[
|
||||
'preg' => '/Madrid Fall ([0-9]*?)/',
|
||||
'trans' => '秋季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::MADRID->value,
|
||||
],
|
||||
],
|
||||
'Spain' => [
|
||||
[
|
||||
'preg' => '/Spain Spring ([0-9]*?)/',
|
||||
'trans' => '春季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::SPAIN->value,
|
||||
],[
|
||||
'preg' => '/Spain Fall ([0-9]*?)/',
|
||||
'trans' => '秋季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::SPAIN->value,
|
||||
],
|
||||
],
|
||||
'Istanbul' => [
|
||||
[
|
||||
'preg' => '/Istanbul Spring ([0-9]*?)/',
|
||||
'trans' => '春季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::ISTANBUL->value,
|
||||
],[
|
||||
'preg' => '/Istanbul Fall ([0-9]*?)/',
|
||||
'trans' => '秋季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::ISTANBUL->value,
|
||||
],
|
||||
],
|
||||
'Lagos' => [
|
||||
[
|
||||
'preg' => '/Lagos Spring ([0-9]*?)/',
|
||||
'trans' => '春季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::LAGOS->value,
|
||||
],[
|
||||
'preg' => '/Lagos Fall ([0-9]*?)/',
|
||||
'trans' => '秋季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::LAGOS->value,
|
||||
],
|
||||
],
|
||||
'Russia' => [
|
||||
[
|
||||
'preg' => '/Russia Spring ([0-9]*?)/',
|
||||
'trans' => '春季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::RUSSIA->value,
|
||||
],[
|
||||
'preg' => '/Russia Fall ([0-9]*?)/',
|
||||
'trans' => '秋季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::RUSSIA->value,
|
||||
],
|
||||
],
|
||||
'Shanghai' => [
|
||||
[
|
||||
'preg' => '/Shanghai Spring ([0-9]*?)/',
|
||||
'trans' => '春季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::SHANGHAI->value,
|
||||
],[
|
||||
'preg' => '/Shanghai Fall ([0-9]*?)/',
|
||||
'trans' => '秋季',
|
||||
'style' => ArticleStyleEnum::NULL->value,
|
||||
'location' => LocationEnum::SHANGHAI->value,
|
||||
],
|
||||
],
|
||||
'Bridal' => [
|
||||
[
|
||||
'preg' => '/Bridal Spring ([0-9]*?)/',
|
||||
'trans' => '春季婚纱礼服',
|
||||
'style' => ArticleStyleEnum::BRIDAL->value,
|
||||
'location' => LocationEnum::NULL->value,
|
||||
],[
|
||||
'preg' => '/Bridal Fall ([0-9]*?)/',
|
||||
'trans' => '秋季婚纱礼服',
|
||||
'style' => ArticleStyleEnum::BRIDAL->value,
|
||||
'location' => LocationEnum::NULL->value,
|
||||
],
|
||||
],
|
||||
'Australia' => [
|
||||
[
|
||||
'preg' => '/Australia Resort ([0-9]*)/',
|
||||
'trans' => '度假系列',
|
||||
'style' => 1,
|
||||
'location' => LocationEnum::AUSTRALIA->value,
|
||||
],[
|
||||
'preg' => '/Australia Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::AUSTRALIA->value,
|
||||
],
|
||||
],
|
||||
'Menswear' => [
|
||||
[
|
||||
'preg' => '/Spring ([0-9]*?) Menswear/',
|
||||
'trans' => '春季男装',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],[
|
||||
'preg' => '/Fall ([0-9]*?) Menswear/',
|
||||
'trans' => '秋季男装',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
]
|
||||
],
|
||||
'Ukraine' => [
|
||||
[
|
||||
'preg' => '/Ukraine Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::UKRAINE->value, // 乌克兰
|
||||
],
|
||||
[
|
||||
'preg' => '/Ukraine Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::UKRAINE->value, // 乌克兰
|
||||
],
|
||||
],
|
||||
'Kiev' => [
|
||||
[
|
||||
'preg' => '/Kiev Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::KIEV->value, // 基辅
|
||||
],[
|
||||
'preg' => '/Kiev Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::KIEV->value, // 基辅
|
||||
]
|
||||
],
|
||||
'Stockholm' => [
|
||||
[
|
||||
'preg' => '/Stockholm Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::STOCKHOLM->value, // 斯德哥尔摩
|
||||
],
|
||||
[
|
||||
'preg' => '/Stockholm Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::STOCKHOLM->value, // 斯德哥尔摩
|
||||
],
|
||||
],
|
||||
'Tbilisi' => [
|
||||
[
|
||||
'preg' => '/Tbilisi Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::TBILISI->value, // 斯德哥尔摩
|
||||
],
|
||||
[
|
||||
'preg' => '/Tbilisi Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::TBILISI->value, // 斯德哥尔摩
|
||||
],
|
||||
],
|
||||
'Mexico' => [
|
||||
[
|
||||
'preg' => '/Mexico Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::MEXICO->value, // 斯德哥尔摩
|
||||
],[
|
||||
'preg' => '/Mexico Spring ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::MEXICO->value, // 斯德哥尔摩
|
||||
],[
|
||||
'preg' => '/Mexico City Fall ([0-9]*)/',
|
||||
'trans' => '城市秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::MEXICO->value, // 斯德哥尔摩
|
||||
],[
|
||||
'preg' => '/Mexico City Spring ([0-9]*)/',
|
||||
'trans' => '城市春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::MEXICO->value, // 斯德哥尔摩
|
||||
],
|
||||
],
|
||||
'Tokyo' => [
|
||||
[
|
||||
'preg' => '/Tokyo Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::TOKYO->value, // 东京
|
||||
],[
|
||||
'preg' => '/Tokyo Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::TOKYO->value, // 东京
|
||||
],
|
||||
],
|
||||
'Berlin' => [
|
||||
[
|
||||
'preg' => '/Berlin Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::BERLIN->value, // 柏林
|
||||
],
|
||||
[
|
||||
'preg' => '/Berlin Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::BERLIN->value, // 柏林
|
||||
],
|
||||
],
|
||||
'Copenhagen' => [
|
||||
[
|
||||
'preg' => '/Copenhagen Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::COPENHAGEN->value, // 哥本哈根
|
||||
],[
|
||||
'preg' => '/Copenhagen Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::COPENHAGEN->value, // 哥本哈根
|
||||
],
|
||||
],
|
||||
'São Paulo' => [
|
||||
[
|
||||
'preg' => '/São Paulo Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::SAO_PAULO->value, // 哥本哈根
|
||||
],[
|
||||
'preg' => '/São Paulo Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::SAO_PAULO->value, // 哥本哈根
|
||||
],
|
||||
],
|
||||
'SEOUL' => [
|
||||
[
|
||||
'preg' => '/Seoul Spring ([0-9]*)/',
|
||||
'trans' => '春季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::SEOUL->value,
|
||||
],[
|
||||
'preg' => '/Seoul Fall ([0-9]*)/',
|
||||
'trans' => '秋季',
|
||||
'style' => 0,
|
||||
'location' => LocationEnum::SEOUL->value,
|
||||
],
|
||||
],
|
||||
'Spring' => [
|
||||
[
|
||||
'preg' => '/([0-9]*?) Spring Summer/',
|
||||
'trans' => '春夏',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
],
|
||||
'Autumn Winter' => [
|
||||
[
|
||||
'preg' => '/([0-9]*?) Autumn Winter/',
|
||||
'trans' => '秋冬',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
],
|
||||
'Ready-to-Wear' => [
|
||||
[
|
||||
'preg' => '/Fall ([0-9]*?) Ready-to-Wear/',
|
||||
'trans' => '秋季成衣',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],[
|
||||
'preg' => '/Spring ([0-9]*?) Ready-to-Wear/',
|
||||
'trans' => '春季成衣',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
],
|
||||
'Resort' => [
|
||||
[
|
||||
'preg' => '/Resort ([0-9]*?)/',
|
||||
'trans' => '度假系列',
|
||||
'style' => ArticleStyleEnum::RESORT->value,
|
||||
'location' => LocationEnum::NULL->value,
|
||||
],
|
||||
],
|
||||
'Couture' => [
|
||||
[
|
||||
'preg' => '/Spring ([0-9]*?) Couture/',
|
||||
'trans' => '春季高订系列',
|
||||
'style' => ArticleStyleEnum::RESORT->value,
|
||||
'location' => LocationEnum::NULL->value,
|
||||
],
|
||||
[
|
||||
'preg' => '/Fall ([0-9]*?) Couture/',
|
||||
'trans' => '秋季高订系列',
|
||||
'style' => ArticleStyleEnum::RESORT->value,
|
||||
'location' => LocationEnum::NULL->value,
|
||||
],
|
||||
],
|
||||
'Pre-Fall' => [
|
||||
[
|
||||
'preg' => '/Pre-Fall ([0-9]*)/',
|
||||
'trans' => '早秋',
|
||||
'style' => 0,
|
||||
'location' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$res = $englishTitle;
|
||||
foreach ($map as $keyword => $pregMap) {
|
||||
if (stripos($englishTitle, $keyword) !== false) {
|
||||
foreach ($pregMap as $pregItem) {
|
||||
preg_match_all($pregItem['preg'], $englishTitle, $matches);
|
||||
|
||||
if (count($matches) > 1 && $matches[1]) {
|
||||
$res = trim(current($matches[1]) . " {$pregItem['trans']}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($res) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
196
app/Helpers/TreeHelper.php
Executable file
196
app/Helpers/TreeHelper.php
Executable file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class TreeHelper
|
||||
{
|
||||
/**
|
||||
* 获取完整的树结构,包含祖先节点
|
||||
*/
|
||||
const INCLUDE_ANCESTORS = 1;
|
||||
|
||||
/**
|
||||
* 获取部分树,不包含祖先节点
|
||||
*/
|
||||
const EXCLUDE_ANCESTORS = 0;
|
||||
|
||||
/**
|
||||
* 数据
|
||||
* @var array
|
||||
*/
|
||||
protected array $data = [];
|
||||
|
||||
/**
|
||||
* 哈希树
|
||||
* @var array
|
||||
*/
|
||||
protected array $hashTree = [];
|
||||
|
||||
/**
|
||||
* 父级字段名
|
||||
* @var string
|
||||
*/
|
||||
protected string $pidName = 'pid';
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @param string $pid_name
|
||||
*/
|
||||
public function __construct($data, string $pid_name = 'pid')
|
||||
{
|
||||
$this->pidName = $pid_name;
|
||||
if (is_object($data) && method_exists($data, 'toArray')) {
|
||||
$this->data = $data->toArray();
|
||||
} else {
|
||||
$this->data = (array)$data;
|
||||
$this->data = array_map(function ($item) {
|
||||
if (is_object($item) && method_exists($item, 'toArray')) {
|
||||
return $item->toArray();
|
||||
}
|
||||
return $item;
|
||||
}, $this->data);
|
||||
}
|
||||
$this->hashTree = $this->getHashTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子孙节点
|
||||
* @param array $include
|
||||
* @param bool $with_self
|
||||
* @return array
|
||||
*/
|
||||
protected function getDescendant(array $include, bool $with_self = false): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ($include as $id) {
|
||||
if (!isset($this->hashTree[$id])) {
|
||||
return [];
|
||||
}
|
||||
if ($with_self) {
|
||||
$item = $this->hashTree[$id];
|
||||
unset($item['children']);
|
||||
$items[$item['id']] = $item;
|
||||
}
|
||||
foreach ($this->hashTree[$id]['children'] ?? [] as $item) {
|
||||
unset($item['children']);
|
||||
$items[$item['id']] = $item;
|
||||
foreach ($this->getDescendant([$item['id']]) as $it) {
|
||||
$items[$it['id']] = $it;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_values($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取哈希树
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function getHashTree(array $data = []): array
|
||||
{
|
||||
$data = $data ?: $this->data;
|
||||
$hash_tree = [];
|
||||
foreach ($data as $item) {
|
||||
$hash_tree[$item['id']] = $item;
|
||||
}
|
||||
foreach ($hash_tree as $index => $item) {
|
||||
if ($item[$this->pidName] && isset($hash_tree[$item[$this->pidName]])) {
|
||||
$hash_tree[$item[$this->pidName]]['children'][$hash_tree[$index]['id']] = &$hash_tree[$index];
|
||||
}
|
||||
}
|
||||
return $hash_tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树
|
||||
* @param array $include
|
||||
* @param int $type
|
||||
* @return array|null
|
||||
*/
|
||||
protected function _getTree(array $include = [], int $type = 1): ?array
|
||||
{
|
||||
// $type === static::EXCLUDE_ANCESTORS
|
||||
if ($type === static::EXCLUDE_ANCESTORS) {
|
||||
$items = [];
|
||||
$include = array_unique($include);
|
||||
foreach ($include as $id) {
|
||||
if (!isset($this->hashTree[$id])) {
|
||||
return [];
|
||||
}
|
||||
$items[] = $this->hashTree[$id];
|
||||
}
|
||||
return static::arrayValues($items);
|
||||
}
|
||||
|
||||
// $type === static::INCLUDE_ANCESTORS
|
||||
$hash_tree = $this->hashTree;
|
||||
$items = [];
|
||||
if ($include) {
|
||||
$map = [];
|
||||
foreach ($include as $id) {
|
||||
if (!isset($hash_tree[$id])) {
|
||||
continue;
|
||||
}
|
||||
$item = $hash_tree[$id];
|
||||
$max_depth = 100;
|
||||
while ($max_depth-- > 0 && $item[$this->pidName] && isset($hash_tree[$item[$this->pidName]])) {
|
||||
$last_item = $item;
|
||||
$pid = $item[$this->pidName];
|
||||
$item = $hash_tree[$pid];
|
||||
$item_id = $item['id'];
|
||||
if (empty($map[$item_id])) {
|
||||
$map[$item_id] = 1;
|
||||
$hash_tree[$pid]['children'] = [];
|
||||
}
|
||||
$hash_tree[$pid]['children'][$last_item['id']] = $last_item;
|
||||
$item = $hash_tree[$pid];
|
||||
}
|
||||
$items[$item['id']] = $item;
|
||||
}
|
||||
} else {
|
||||
$items = $hash_tree;
|
||||
}
|
||||
$formatted_items = [];
|
||||
foreach ($items as $item) {
|
||||
if (!$item[$this->pidName] || !isset($hash_tree[$item[$this->pidName]])) {
|
||||
$formatted_items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return static::arrayValues($formatted_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归重建数组下标
|
||||
* @param $array
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayValues($array): array
|
||||
{
|
||||
if (!$array) {
|
||||
return [];
|
||||
}
|
||||
if (!isset($array['children'])) {
|
||||
$current = current($array);
|
||||
if (!is_array($current)) {
|
||||
return $array;
|
||||
}
|
||||
$tree = array_values($array);
|
||||
foreach ($tree as $index => $item) {
|
||||
$tree[$index] = static::arrayValues($item);
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
$array['children'] = array_values($array['children']);
|
||||
foreach ($array['children'] as $index => $child) {
|
||||
$array['children'][$index] = static::arrayValues($child);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function getTree($data)
|
||||
{
|
||||
return (new self($data))->_getTree();
|
||||
}
|
||||
}
|
66
app/Listener/DbQueryExecutedListener.php
Executable file
66
app/Listener/DbQueryExecutedListener.php
Executable file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\Collection\Arr;
|
||||
use Hyperf\Database\Events\QueryExecuted;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[Listener]
|
||||
class DbQueryExecutedListener implements ListenerInterface
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->logger = $container->get(LoggerFactory::class)->get('sql');
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
QueryExecuted::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryExecuted $event
|
||||
*/
|
||||
public function process(object $event): void
|
||||
{
|
||||
if ($event instanceof QueryExecuted) {
|
||||
$sql = $event->sql;
|
||||
if (! Arr::isAssoc($event->bindings)) {
|
||||
$position = 0;
|
||||
foreach ($event->bindings as $value) {
|
||||
$position = strpos($sql, '?', $position);
|
||||
if ($position === false) {
|
||||
break;
|
||||
}
|
||||
$value = "'{$value}'";
|
||||
$sql = substr_replace($sql, $value, $position, 1);
|
||||
$position += strlen($value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info(sprintf('[%s] %s', $event->time, $sql));
|
||||
}
|
||||
}
|
||||
}
|
74
app/Listener/QueueHandleListener.php
Executable file
74
app/Listener/QueueHandleListener.php
Executable file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\AsyncQueue\AnnotationJob;
|
||||
use Hyperf\AsyncQueue\Event\AfterHandle;
|
||||
use Hyperf\AsyncQueue\Event\BeforeHandle;
|
||||
use Hyperf\AsyncQueue\Event\Event;
|
||||
use Hyperf\AsyncQueue\Event\FailedHandle;
|
||||
use Hyperf\AsyncQueue\Event\RetryHandle;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[Listener]
|
||||
class QueueHandleListener implements ListenerInterface
|
||||
{
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->logger = $container->get(LoggerFactory::class)->get('queue');
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
AfterHandle::class,
|
||||
BeforeHandle::class,
|
||||
FailedHandle::class,
|
||||
RetryHandle::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
if ($event instanceof Event && $event->getMessage()->job()) {
|
||||
$job = $event->getMessage()->job();
|
||||
$jobClass = get_class($job);
|
||||
if ($job instanceof AnnotationJob) {
|
||||
$jobClass = sprintf('Job[%s@%s]', $job->class, $job->method);
|
||||
}
|
||||
$date = date('Y-m-d H:i:s');
|
||||
|
||||
switch (true) {
|
||||
case $event instanceof BeforeHandle:
|
||||
$this->logger->info(sprintf('[%s] Processing %s.', $date, $jobClass));
|
||||
break;
|
||||
case $event instanceof AfterHandle:
|
||||
$this->logger->info(sprintf('[%s] Processed %s.', $date, $jobClass));
|
||||
break;
|
||||
case $event instanceof FailedHandle:
|
||||
$this->logger->error(sprintf('[%s] Failed %s.', $date, $jobClass));
|
||||
$this->logger->error((string) $event->getThrowable());
|
||||
break;
|
||||
case $event instanceof RetryHandle:
|
||||
$this->logger->warning(sprintf('[%s] Retried %s.', $date, $jobClass));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
app/Listener/ResumeExitCoordinatorListener.php
Executable file
35
app/Listener/ResumeExitCoordinatorListener.php
Executable file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\Command\Event\AfterExecute;
|
||||
use Hyperf\Coordinator\Constants;
|
||||
use Hyperf\Coordinator\CoordinatorManager;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
|
||||
#[Listener]
|
||||
class ResumeExitCoordinatorListener implements ListenerInterface
|
||||
{
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
AfterExecute::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
CoordinatorManager::until(Constants::WORKER_EXIT)->resume();
|
||||
}
|
||||
}
|
37
app/Model/AppAdminMenu.php
Executable file
37
app/Model/AppAdminMenu.php
Executable file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $title
|
||||
* @property string $icon
|
||||
* @property string $key
|
||||
* @property int $pid
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property string $href
|
||||
* @property int $type
|
||||
* @property int $weight
|
||||
*/
|
||||
class AppAdminMenu extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_admin_menus';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'pid' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'type' => 'integer', 'weight' => 'integer'];
|
||||
}
|
125
app/Model/AppArticle.php
Executable file
125
app/Model/AppArticle.php
Executable file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
use App\Enums\ArticleModuleEnum;
|
||||
use App\Enums\ArticlePublishedStatusEnum;
|
||||
use App\Enums\LocationEnum;
|
||||
use App\Helpers\TitleHelper;
|
||||
use Hyperf\Database\Model\Events\Saving;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $aid
|
||||
* @property string $title
|
||||
* @property string $description
|
||||
* @property string $cover
|
||||
* @property int $year
|
||||
* @property int $deleted_at
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property int $spider_article_id
|
||||
* @property int $style
|
||||
* @property int $location
|
||||
* @property int $images_count
|
||||
* @property int $published_at
|
||||
* @property mixed $images
|
||||
* @property mixed $module
|
||||
* @property mixed $brand
|
||||
* @property mixed $brand_name
|
||||
* @property mixed $published_status
|
||||
*/
|
||||
class AppArticle extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_articles';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'brand' => 'integer', 'year' => 'integer', 'module' => 'integer', 'deleted_at' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'created_by' => 'integer', 'updated_by' => 'integer', 'spider_article_id' => 'integer', 'style' => 'integer', 'location' => 'integer', 'images_count' => 'integer', 'published_status' => 'integer', 'published_at' => 'integer'];
|
||||
|
||||
protected ?string $dateFormat = 'U';
|
||||
|
||||
// public function saving(Saving $event)
|
||||
// {
|
||||
// if ($this->getAttribute('images_count') == 0) {
|
||||
// $this->setAttribute('images_count', count(json_decode($this->getAttribute('images'), true)));
|
||||
// }
|
||||
// }
|
||||
|
||||
public function setImagesAttribute($value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$imagesValue = json_decode($value, true);
|
||||
$this->setAttribute('images_count', count($imagesValue));
|
||||
} elseif (is_array($value)) {
|
||||
$this->setAttribute('images_count', count($value));
|
||||
$value = json_encode(array_values($value));
|
||||
}
|
||||
|
||||
$this->attributes['images'] = $value;
|
||||
}
|
||||
|
||||
public function getImagesAttribute($value)
|
||||
{
|
||||
return $this->format('images', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? json_decode($value, true) : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getModuleAttribute($value)
|
||||
{
|
||||
return $this->format('module', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? ArticleModuleEnum::from($value)->toString() : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getLocationAttribute($value)
|
||||
{
|
||||
return $this->format('location', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? LocationEnum::from($value)->toString() : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getBrandAttribute($value)
|
||||
{
|
||||
return $this->format('brand', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? AppBrand::find($value)?->name : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getBrandNameAttribute($value)
|
||||
{
|
||||
return $this->format('brand_name', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? AppBrand::find($value)?->name : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getPublishedStatusAttribute($value)
|
||||
{
|
||||
return $this->format('published_status', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? ArticlePublishedStatusEnum::from($value)->toString() : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getTranslateTitleAttribute($value)
|
||||
{
|
||||
return $this->format('translate_title', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? TitleHelper::translate($value) : $value;
|
||||
});
|
||||
}
|
||||
}
|
41
app/Model/AppBrand.php
Executable file
41
app/Model/AppBrand.php
Executable file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $cn_name
|
||||
* @property string $first_letter
|
||||
* @property string $logo
|
||||
* @property string $description
|
||||
* @property int $is_del
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property string $spider_origin
|
||||
*/
|
||||
class AppBrand extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_brands';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'is_del' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'created_by' => 'integer', 'updated_by' => 'integer'];
|
||||
|
||||
protected ?string $dateFormat = 'U';
|
||||
}
|
43
app/Model/AppKeywordsMonitor.php
Normal file
43
app/Model/AppKeywordsMonitor.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $keyword
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property int $deleted_at
|
||||
* @property int $is_delete
|
||||
* @property int $queried_at
|
||||
* @property int $platform
|
||||
*/
|
||||
class AppKeywordsMonitor extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_keywords_monitor';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'integer', 'is_delete' => 'integer', 'queried_at' => 'integer', 'platform' => 'integer'];
|
||||
|
||||
function getQueriedAtAttribute($value): string
|
||||
{
|
||||
if (!$value) {
|
||||
return '未查询';
|
||||
}
|
||||
return date('Y-m-d H:i:s', intval($value)) ?: '未查询';
|
||||
}
|
||||
}
|
42
app/Model/AppKeywordsMonitorResult.php
Normal file
42
app/Model/AppKeywordsMonitorResult.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $aid
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property int $deleted_at
|
||||
* @property int $is_delete
|
||||
* @property int $queried_at
|
||||
* @property string $title
|
||||
* @property string $description
|
||||
* @property string $platform
|
||||
* @property string $url
|
||||
* @property string $order
|
||||
* @property string $ip_address
|
||||
* @property string $ip_source
|
||||
* @property string $screen_path
|
||||
*/
|
||||
class AppKeywordsMonitorResult extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_keywords_monitor_result';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'aid' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'integer', 'is_delete' => 'integer', 'queried_at' => 'integer'];
|
||||
}
|
36
app/Model/AppKeywordsMonitorTask.php
Normal file
36
app/Model/AppKeywordsMonitorTask.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $keyword
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property int $deleted_at
|
||||
* @property int $is_delete
|
||||
* @property int $queried_at
|
||||
* @property int $platform
|
||||
* @property int $aid
|
||||
*/
|
||||
class AppKeywordsMonitorTask extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_keywords_monitor_task';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'integer', 'is_delete' => 'integer', 'queried_at' => 'integer', 'platform' => 'integer', 'aid' => 'integer'];
|
||||
}
|
48
app/Model/AppNews.php
Normal file
48
app/Model/AppNews.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $title
|
||||
* @property string $keywords
|
||||
* @property string $content
|
||||
* @property string $description
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property int $created_by
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property int $updated_by
|
||||
* @property int $deleted_at
|
||||
* @property int $deleted_by
|
||||
* @property int $platform
|
||||
* @property int $is_record
|
||||
* @property string $cover
|
||||
*/
|
||||
class AppNews extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_news';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'created_at' => 'datetime', 'created_by' => 'integer', 'updated_at' => 'datetime', 'updated_by' => 'integer', 'deleted_at' => 'integer', 'deleted_by' => 'integer', 'platform' => 'integer', 'is_record' => 'integer'];
|
||||
|
||||
protected ?string $dateFormat = 'U';
|
||||
|
||||
public function getCreatedAtAttribute($value)
|
||||
{
|
||||
return date('Y-m-d H:i:s', intval($value));
|
||||
}
|
||||
}
|
84
app/Model/AppSpiderArticle.php
Executable file
84
app/Model/AppSpiderArticle.php
Executable file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
use App\Enums\ArticleModuleEnum;
|
||||
use App\Enums\ArticlePublishedStatusEnum;
|
||||
use App\Enums\SpiderArticlePublishedStatusEnum;
|
||||
use Carbon\Carbon;
|
||||
use Hyperf\Database\Model\Builder;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $title
|
||||
* @property string $description
|
||||
* @property string $cover
|
||||
* @property int $brand
|
||||
* @property int $year
|
||||
* @property int $deleted_at
|
||||
* @property Carbon $updated_at
|
||||
* @property string $platform
|
||||
* @property string $source_url
|
||||
* @property int $published_status
|
||||
* @property int $published_at
|
||||
* @property mixed $created_at
|
||||
* @property mixed $module
|
||||
* @property mixed $images
|
||||
*/
|
||||
class AppSpiderArticle extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_spider_articles';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'brand' => 'integer', 'year' => 'integer', 'module' => 'integer', 'deleted_at' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'published_status' => 'integer', 'published_at' => 'integer'];
|
||||
|
||||
public function getCreatedAtAttribute($value): false|string
|
||||
{
|
||||
return $this->format('created_at', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? date('Y-m-d H:i:s', intval($value)) : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getModuleAttribute($value)
|
||||
{
|
||||
return $this->format('module', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? ArticleModuleEnum::from($value)->toString() : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getImagesAttribute($value)
|
||||
{
|
||||
return $this->format('images', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? json_decode($value, true) : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getBrandAttribute($value)
|
||||
{
|
||||
return $this->format('brand', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? AppBrand::find($value)?->name : $value;
|
||||
});
|
||||
}
|
||||
|
||||
public function getPublishedStatusAttribute($value)
|
||||
{
|
||||
return $this->format('published_status', function (bool $isFormat) use ($value) {
|
||||
return $isFormat ? SpiderArticlePublishedStatusEnum::from($value)->toString() : $value;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
37
app/Model/AppUser.php
Executable file
37
app/Model/AppUser.php
Executable file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $unique_id
|
||||
* @property string $avatar
|
||||
* @property string $email
|
||||
* @property string $email_verified_at
|
||||
* @property string $password
|
||||
* @property string $remember_token
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class AppUser extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'app_users';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
|
||||
}
|
46
app/Model/Model.php
Executable file
46
app/Model/Model.php
Executable file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Hyperf\Context\Context;
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use Hyperf\Database\Model\Builder;
|
||||
use Hyperf\DbConnection\Model\Model as BaseModel;
|
||||
use Hyperf\ModelCache\Cacheable;
|
||||
use Hyperf\ModelCache\CacheableInterface;
|
||||
|
||||
abstract class Model extends BaseModel implements CacheableInterface
|
||||
{
|
||||
use Cacheable;
|
||||
protected ?string $dateFormat = 'U';
|
||||
|
||||
const ENABLED_FORMATTER = __CLASS__ . 'ENABLED_FORMATTER';
|
||||
|
||||
public static function formatQuery(array $formatFields = []): Builder
|
||||
{
|
||||
Context::set(self::ENABLED_FORMATTER, $formatFields);
|
||||
return (new static())->newQuery();
|
||||
}
|
||||
|
||||
public static function query(): Builder
|
||||
{
|
||||
Context::set(self::ENABLED_FORMATTER, []);
|
||||
return (new static())->newQuery();
|
||||
}
|
||||
|
||||
protected function format(string $keyName, \Closure $callable)
|
||||
{
|
||||
$formatFields = Context::get(self::ENABLED_FORMATTER);
|
||||
return $callable(in_array($keyName, $formatFields ?: []));
|
||||
}
|
||||
}
|
21
app/Process/AsyncQueueConsumer.php
Executable file
21
app/Process/AsyncQueueConsumer.php
Executable file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace App\Process;
|
||||
|
||||
use Hyperf\AsyncQueue\Process\ConsumerProcess;
|
||||
use Hyperf\Process\Annotation\Process;
|
||||
|
||||
#[Process]
|
||||
class AsyncQueueConsumer extends ConsumerProcess
|
||||
{
|
||||
}
|
14
app/Proto/grpc.proto
Executable file
14
app/Proto/grpc.proto
Executable file
@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
package grpc;
|
||||
service hi {
|
||||
rpc sayHello (HiUser) returns (HiReply) {
|
||||
}
|
||||
}
|
||||
message HiUser {
|
||||
string name = 1;
|
||||
int32 sex = 2;
|
||||
}
|
||||
message HiReply {
|
||||
string message = 1;
|
||||
HiUser user = 2;
|
||||
}
|
33
app/Request/Test.php
Executable file
33
app/Request/Test.php
Executable file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Request;
|
||||
|
||||
use Hyperf\Validation\Request\FormRequest;
|
||||
|
||||
class Test extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'password' => 'required'
|
||||
];
|
||||
}
|
||||
|
||||
public function validationData(): array
|
||||
{
|
||||
return $this->getParsedBody();
|
||||
}
|
||||
}
|
11
app/Rpc/BaseService.php
Executable file
11
app/Rpc/BaseService.php
Executable file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rpc;
|
||||
|
||||
class BaseService
|
||||
{
|
||||
protected function getResponse()
|
||||
{
|
||||
return new RpcResponse();
|
||||
}
|
||||
}
|
27
app/Rpc/CalculatorService.php
Executable file
27
app/Rpc/CalculatorService.php
Executable file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rpc;
|
||||
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\Rpc\Response;
|
||||
use Hyperf\RpcServer\Annotation\RpcService;
|
||||
|
||||
// #[RpcService(name: "xxx-oo", server: "jsonrpc-http", protocol: "jsonrpc-http", publishTo: 'nacos')]
|
||||
class CalculatorService
|
||||
{
|
||||
public function add(int $a, int $b)
|
||||
{
|
||||
// \Hyperf\Logger\LoggerFactory::get('default')->info("Add called with: $a + $b");
|
||||
// 这里是服务方法的具体实现
|
||||
return ['xxxx' => 888];
|
||||
return $a + $b;
|
||||
}
|
||||
|
||||
public function add2(int $a, int $b)
|
||||
{
|
||||
// \Hyperf\Logger\LoggerFactory::get('default')->info("Add called with: $a + $b");
|
||||
// 这里是服务方法的具体实现
|
||||
return ['xxxx' => 888999];
|
||||
return $a + $b;
|
||||
}
|
||||
}
|
41
app/Rpc/RpcResponse.php
Executable file
41
app/Rpc/RpcResponse.php
Executable file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rpc;
|
||||
|
||||
class RpcResponse
|
||||
{
|
||||
public int $code = 0;
|
||||
public string $msg = 'ok';
|
||||
public array $data = [];
|
||||
public array $meta = [];
|
||||
public string $title = '';
|
||||
public array $pageModule = [];
|
||||
|
||||
|
||||
public function setData(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCode(int $code)
|
||||
{
|
||||
$this->code = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMsg(string $message)
|
||||
{
|
||||
$this->msg = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function send()
|
||||
{
|
||||
return [
|
||||
'code' => $this->code,
|
||||
'msg' => $this->msg,
|
||||
'data' => $this->data,
|
||||
];
|
||||
}
|
||||
}
|
19
app/Rpc/v1/IndexService.php
Executable file
19
app/Rpc/v1/IndexService.php
Executable file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rpc\v1;
|
||||
|
||||
use App\FormModel\rpc\v1\RpcIndexModel;
|
||||
use App\Rpc\BaseService;
|
||||
use Hyperf\RpcServer\Annotation\RpcService;
|
||||
|
||||
// #[RpcService(name: "index", server: "jsonrpc-http", protocol: "jsonrpc-http", publishTo: 'nacos')]
|
||||
class IndexService extends BaseService
|
||||
{
|
||||
public function index(): array
|
||||
{
|
||||
$model = new RpcIndexModel();
|
||||
$data = $model->index();
|
||||
$response = $this->getResponse()->setPageModule($data);
|
||||
return $response->send();
|
||||
}
|
||||
}
|
59
app/Rpc/v1/NewsService.php
Executable file
59
app/Rpc/v1/NewsService.php
Executable file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rpc\v1;
|
||||
|
||||
use App\Model\AppNews;
|
||||
use App\Rpc\BaseService;
|
||||
use Hyperf\RpcServer\Annotation\RpcService;
|
||||
|
||||
#[RpcService(name: "news", server: "jsonrpc-http", protocol: "jsonrpc-http")]
|
||||
class NewsService extends BaseService
|
||||
{
|
||||
/**
|
||||
* 查看单个新闻详情
|
||||
* @url /news/view
|
||||
* @param $id
|
||||
* @return array
|
||||
*/
|
||||
public function view($id): array
|
||||
{
|
||||
$query = AppNews::find($id)->toArray();
|
||||
// 相关文章
|
||||
$query['about'] = AppNews::formatQuery(['created_at'])
|
||||
->select(['title', 'id'])
|
||||
->limit(10)
|
||||
->orderBy('id', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
// 上一篇文章
|
||||
$query['prevNews'] = AppNews::find($id - 1, ['title', 'id'])?->toArray();
|
||||
// 下一篇文章
|
||||
$query['nextNews'] = AppNews::find($id + 1, ['title', 'id'])?->toArray();
|
||||
|
||||
return $this->getResponse()->setData($query)->setCode(0)->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看所有新闻
|
||||
* @url /news/index
|
||||
* @param int $limit
|
||||
* @param int $page
|
||||
* @return array
|
||||
*/
|
||||
public function index(int $limit = 10, int $page = 1): array
|
||||
{
|
||||
$query = AppNews::formatQuery(['created_at'])
|
||||
->select(['id'])
|
||||
->orderBy('id', 'desc');;
|
||||
$pagination = $query->paginate($limit, page: $page);
|
||||
$ids = [];
|
||||
foreach ($pagination->items() as $item) {
|
||||
$ids[] = $item->id;
|
||||
}
|
||||
|
||||
$value = AppNews::query()->whereIn('id', $ids)->orderBy('id', 'desc')->get()->toArray();
|
||||
|
||||
return $this->getResponse()->setData($value)->setCode(0)->send();
|
||||
}
|
||||
}
|
27
app/Rpc/v1/ShowsService.php
Executable file
27
app/Rpc/v1/ShowsService.php
Executable file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rpc\v1;
|
||||
|
||||
use App\FormModel\rpc\v1\RpcIndexModel;
|
||||
use App\Model\AppArticle;
|
||||
use App\Rpc\BaseService;
|
||||
use Hyperf\RpcServer\Annotation\RpcService;
|
||||
|
||||
// #[RpcService(name: "shows", server: "jsonrpc-http", protocol: "jsonrpc-http", publishTo: 'nacos')]
|
||||
class ShowsService extends BaseService
|
||||
{
|
||||
public function view($aid): array
|
||||
{
|
||||
$data = AppArticle::formatQuery(['images', 'title', 'cover', 'brand_name'])
|
||||
->select(['brand', 'aid', 'title', 'cover', 'brand as brand_name', 'images', 'description'])
|
||||
->where('aid', $aid)
|
||||
->first()
|
||||
->toArray();
|
||||
|
||||
$data['more'] = AppArticle::formatQuery(['images', 'title', 'cover', 'brand_name'])
|
||||
->where('brand', '=', $data['brand'])
|
||||
->orderBy('published_at', 'DESC')
|
||||
->limit(25);
|
||||
return $this->getResponse()->setPageModule($data)->send();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user