first commit
This commit is contained in:
99
Dockerfile
Executable file
99
Dockerfile
Executable file
@ -0,0 +1,99 @@
|
|||||||
|
## Default Dockerfile
|
||||||
|
##
|
||||||
|
## @link https://www.hyperf.io
|
||||||
|
## @document https://hyperf.wiki
|
||||||
|
## @contact group@hyperf.io
|
||||||
|
## @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||||
|
#
|
||||||
|
#FROM hyperf/hyperf:8.3-alpine-v3.19-swoole
|
||||||
|
#LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="Hyperf"
|
||||||
|
#
|
||||||
|
###
|
||||||
|
## ---------- env settings ----------
|
||||||
|
###
|
||||||
|
## --build-arg timezone=Asia/Shanghai
|
||||||
|
#ARG timezone
|
||||||
|
#
|
||||||
|
#ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
|
||||||
|
# APP_ENV=prod \
|
||||||
|
# SCAN_CACHEABLE=(true)
|
||||||
|
#
|
||||||
|
## update
|
||||||
|
#RUN set -ex \
|
||||||
|
# # show php version and extensions
|
||||||
|
# && php -v \
|
||||||
|
# && php -m \
|
||||||
|
# && php --ri swoole \
|
||||||
|
# # ---------- some config ----------
|
||||||
|
# && cd /etc/php* \
|
||||||
|
# # - config PHP
|
||||||
|
# && { \
|
||||||
|
# echo "upload_max_filesize=128M"; \
|
||||||
|
# echo "post_max_size=128M"; \
|
||||||
|
# echo "memory_limit=1G"; \
|
||||||
|
# echo "date.timezone=${TIMEZONE}"; \
|
||||||
|
# } | tee conf.d/99_overrides.ini \
|
||||||
|
# # - config timezone
|
||||||
|
# && ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
|
||||||
|
# && echo "${TIMEZONE}" > /etc/timezone \
|
||||||
|
# # ---------- clear works ----------
|
||||||
|
# && rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
|
||||||
|
# && echo -e "\033[42;37m Build Completed :).\033[0m\n" \
|
||||||
|
# && apk add --no-cache \
|
||||||
|
# php-xdebug
|
||||||
|
#
|
||||||
|
#RUN echo "zend_extension=xdebug.so" > /etc/php83/conf.d/50_xdebug.ini \
|
||||||
|
# && echo "xdebug.mode=debug" >> /etc/php83/conf.d/50_xdebug.ini \
|
||||||
|
# && echo "xdebug.start_with_request=yes" >> /etc/php83/conf.d/50_xdebug.ini \
|
||||||
|
# && echo "xdebug.client_host=192.168.31.221" >> /etc/php83/conf.d/50_xdebug.ini \
|
||||||
|
# && echo "xdebug.client_port=9003" >> /etc/php83/conf.d/50_xdebug.ini
|
||||||
|
#
|
||||||
|
#WORKDIR /opt/www
|
||||||
|
#
|
||||||
|
## Composer Cache
|
||||||
|
## COPY ./composer.* /opt/www/
|
||||||
|
##RUN composer install --no-dev --no-scripts
|
||||||
|
#
|
||||||
|
#COPY . /opt/www
|
||||||
|
##RUN composer install --no-dev -o && php bin/hyperf.php
|
||||||
|
## RUN composer install --no-dev -o -vvv
|
||||||
|
#
|
||||||
|
#EXPOSE 9501
|
||||||
|
#
|
||||||
|
##ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"]
|
||||||
|
#ENTRYPOINT ["php"]
|
||||||
|
|
||||||
|
|
||||||
|
#FROM php:8.3-apache
|
||||||
|
FROM phpswoole/swoole:php8.3
|
||||||
|
|
||||||
|
RUN set -ex \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
# gcc g++ make autoconf libc-dev \
|
||||||
|
# libpng-dev libjpeg-dev libfreetype6-dev \
|
||||||
|
# libcurl4-openssl-dev \
|
||||||
|
# libssl-dev \
|
||||||
|
# libcares2-dev \
|
||||||
|
libzip-dev zip unzip libpng-dev\
|
||||||
|
&& pecl install xdebug \
|
||||||
|
# && pecl install -D 'enable-openssl="yes" enable-swoole-curl="yes" enable-cares="yes"' swoole-6.0.2 \
|
||||||
|
## && pecl install redis \
|
||||||
|
&& docker-php-ext-install pcntl \
|
||||||
|
&& docker-php-ext-install gd \
|
||||||
|
&& docker-php-ext-install zip \
|
||||||
|
# && docker-php-ext-enable xdebug \
|
||||||
|
&& php -v \
|
||||||
|
&& php -m \
|
||||||
|
&& php --ri swoole
|
||||||
|
|
||||||
|
# RUN #echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name 'xdebug.so')" >> /usr/local/etc/php/php.ini \
|
||||||
|
# && echo "xdebug.mode=debug" >> /usr/local/etc/php/php.ini \
|
||||||
|
# && echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/php.ini \
|
||||||
|
# && echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/php.ini \
|
||||||
|
# && echo "xdebug.client_port=9003" >> /usr/local/etc/php/php.ini
|
||||||
|
|
||||||
|
|
||||||
|
EXPOSE 9501 9504
|
||||||
|
|
||||||
|
# docker build --no-cache --progress=plain -t xsvase:1.0 .
|
21
LICENSE
Executable file
21
LICENSE
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Hyperf
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
63
README.md
Executable file
63
README.md
Executable file
@ -0,0 +1,63 @@
|
|||||||
|
# Introduction
|
||||||
|
|
||||||
|
This is a skeleton application using the Hyperf framework. This application is meant to be used as a starting place for those looking to get their feet wet with Hyperf Framework.
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
Hyperf has some requirements for the system environment, it can only run under Linux and Mac environment, but due to the development of Docker virtualization technology, Docker for Windows can also be used as the running environment under Windows.
|
||||||
|
|
||||||
|
The various versions of Dockerfile have been prepared for you in the [hyperf/hyperf-docker](https://github.com/hyperf/hyperf-docker) project, or directly based on the already built [hyperf/hyperf](https://hub.docker.com/r/hyperf/hyperf) Image to run.
|
||||||
|
|
||||||
|
When you don't want to use Docker as the basis for your running environment, you need to make sure that your operating environment meets the following requirements:
|
||||||
|
|
||||||
|
- PHP >= 8.1
|
||||||
|
- Any of the following network engines
|
||||||
|
- Swoole PHP extension >= 5.0,with `swoole.use_shortname` set to `Off` in your `php.ini`
|
||||||
|
- Swow PHP extension >= 1.3
|
||||||
|
- JSON PHP extension
|
||||||
|
- Pcntl PHP extension
|
||||||
|
- OpenSSL PHP extension (If you need to use the HTTPS)
|
||||||
|
- PDO PHP extension (If you need to use the MySQL Client)
|
||||||
|
- Redis PHP extension (If you need to use the Redis Client)
|
||||||
|
- Protobuf PHP extension (If you need to use the gRPC Server or Client)
|
||||||
|
|
||||||
|
# Installation using Composer
|
||||||
|
|
||||||
|
The easiest way to create a new Hyperf project is to use [Composer](https://getcomposer.org/). If you don't have it already installed, then please install as per [the documentation](https://getcomposer.org/download/).
|
||||||
|
|
||||||
|
To create your new Hyperf project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer create-project hyperf/hyperf-skeleton path/to/install
|
||||||
|
```
|
||||||
|
|
||||||
|
If your development environment is based on Docker you can use the official Composer image to create a new Hyperf project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -it -v $(pwd):/app composer create-project --ignore-platform-reqs hyperf/hyperf-skeleton path/to/install
|
||||||
|
```
|
||||||
|
|
||||||
|
# Getting started
|
||||||
|
|
||||||
|
Once installed, you can run the server immediately using the command below.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd path/to/install
|
||||||
|
php bin/hyperf.php start
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if in a Docker based environment you can use the `docker-compose.yml` provided by the template:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd path/to/install
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
This will start the cli-server on port `9501`, and bind it to all network interfaces. You can then visit the site at `http://localhost:9501/` which will bring up Hyperf default home page.
|
||||||
|
|
||||||
|
## Hints
|
||||||
|
|
||||||
|
- A nice tip is to rename `hyperf-skeleton` of files like `composer.json` and `docker-compose.yml` to your actual project name.
|
||||||
|
- Take a look at `config/routes.php` and `app/Controller/IndexController.php` to see an example of a HTTP entrypoint.
|
||||||
|
|
||||||
|
**Remember:** you can always replace the contents of this README.md file to something that fits your project description.
|
35
TIMELINE.md
Executable file
35
TIMELINE.md
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
# TODO LIST
|
||||||
|
|
||||||
|
### ADMIN
|
||||||
|
|
||||||
|
### FRONTEND
|
||||||
|
- [ ] 用户登陆
|
||||||
|
- [ ] 迁移图床
|
||||||
|
|
||||||
|
### BACKEND
|
||||||
|
- [ ] golang staticPath 的问题
|
||||||
|
- [ ] 图片上传到图床
|
||||||
|
- [ ] 用户登陆
|
||||||
|
- [ ] 新增event 模块
|
||||||
|
- [ ] 图片数量和浏览量
|
||||||
|
- [ ] 文章标题转成中文
|
||||||
|
- [ ] 搜索模块
|
||||||
|
- [ ] 品牌编辑页面
|
||||||
|
- [ ] 脚本运行页面
|
||||||
|
- [ ] 图片打标签
|
||||||
|
|
||||||
|
### DAILY LOG [0528]
|
||||||
|
- [frontend] 现在文章的数据通过rpc来获取。
|
||||||
|
- [backend] 提供了rpc接口。
|
||||||
|
|
||||||
|
### DAILY LOG [0521]
|
||||||
|
- [admin] 优化文章编辑内图片太多导致页面卡顿的问题。
|
||||||
|
- [admin] 现在文章可以编辑位置信息了。
|
||||||
|
|
||||||
|
### DAILY LOG [0520]
|
||||||
|
- [admin] 修复爬虫文章预览图集展示可能不对齐的问题.
|
||||||
|
- [backend] 新增theimpression.com 街拍模块爬虫.
|
||||||
|
- [admin] 新增选择封面的功能.
|
||||||
|
- [admin] 修复更改文章图片顺序可能不生效的问题。
|
||||||
|
- [admin] 修复文章列表大分页慢的问题。
|
||||||
|
- [admin] 修复在编辑street模块可能让输入品牌的问题。
|
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();
|
||||||
|
}
|
||||||
|
}
|
31
bin/hyperf.php
Executable file
31
bin/hyperf.php
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
ini_set('display_errors', 'on');
|
||||||
|
ini_set('display_startup_errors', 'on');
|
||||||
|
ini_set('memory_limit', '1G');
|
||||||
|
date_default_timezone_set('Asia/Shanghai');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
|
||||||
|
|
||||||
|
require BASE_PATH . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', Hyperf\Engine\DefaultOption::hookFlags());
|
||||||
|
|
||||||
|
// Self-called anonymous function that creates its own scope and keep the global namespace clean.
|
||||||
|
(function () {
|
||||||
|
Hyperf\Di\ClassLoader::init();
|
||||||
|
/** @var Psr\Container\ContainerInterface $container */
|
||||||
|
$container = require BASE_PATH . '/config/container.php';
|
||||||
|
|
||||||
|
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
|
||||||
|
$application->run();
|
||||||
|
})();
|
18
bin/test.php
Executable file
18
bin/test.php
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Command\TestClass;
|
||||||
|
use Spatie\Crawler\Crawler;
|
||||||
|
|
||||||
|
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
|
||||||
|
|
||||||
|
require BASE_PATH . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
|
||||||
|
echo 123123;
|
||||||
|
function start()
|
||||||
|
{
|
||||||
|
\Hyperf\Context\ApplicationContext::getContainer()->get(\Hyperf\Redis\Redis::class)->set('xx', 1);
|
||||||
|
Crawler::create()->addCrawlObserver(new TestClass())->startCrawling('https://www.vogue.com.cn');
|
||||||
|
}
|
||||||
|
|
||||||
|
//start();
|
101
composer.json
Executable file
101
composer.json
Executable file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"name": "hyperf/hyperf-skeleton",
|
||||||
|
"type": "project",
|
||||||
|
"keywords": [
|
||||||
|
"php",
|
||||||
|
"swoole",
|
||||||
|
"framework",
|
||||||
|
"hyperf",
|
||||||
|
"microservice",
|
||||||
|
"middleware"
|
||||||
|
],
|
||||||
|
"description": "A coroutine framework that focuses on hyperspeed and flexible, specifically use for build microservices and middlewares.",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"hyperf/async-queue": "~3.1.0",
|
||||||
|
"hyperf/cache": "~3.1.0",
|
||||||
|
"hyperf/command": "~3.1.0",
|
||||||
|
"hyperf/config": "~3.1.0",
|
||||||
|
"hyperf/config-nacos": "~3.1.0",
|
||||||
|
"hyperf/constants": "~3.1.0",
|
||||||
|
"hyperf/database": "~3.1.0",
|
||||||
|
"hyperf/db-connection": "~3.1.0",
|
||||||
|
"hyperf/elasticsearch": "~3.1.0",
|
||||||
|
"hyperf/engine": "^2.10",
|
||||||
|
"hyperf/framework": "~3.1.0",
|
||||||
|
"hyperf/grpc-client": "^3.1",
|
||||||
|
"hyperf/grpc-server": "~3.1.0",
|
||||||
|
"hyperf/guzzle": "^3.1",
|
||||||
|
"hyperf/http-server": "~3.1.0",
|
||||||
|
"hyperf/json-rpc": "^3.1",
|
||||||
|
"hyperf/logger": "~3.1.0",
|
||||||
|
"hyperf/memory": "~3.1.0",
|
||||||
|
"hyperf/model-cache": "~3.1.0",
|
||||||
|
"hyperf/paginator": "^3.1",
|
||||||
|
"hyperf/process": "~3.1.0",
|
||||||
|
"hyperf/redis": "^3.1",
|
||||||
|
"hyperf/rpc-server": "^3.1",
|
||||||
|
"hyperf/service-governance-nacos": "^3.1",
|
||||||
|
"hyperf/tracer": "~3.1.0",
|
||||||
|
"hyperf/validation": "^3.1",
|
||||||
|
"hyperf/view": "^3.1",
|
||||||
|
"hyperf/view-engine": "^3.1",
|
||||||
|
"phpoffice/phpspreadsheet": "^4.3",
|
||||||
|
"qiniu/php-sdk": "^7.14",
|
||||||
|
"spatie/crawler": "^8.4",
|
||||||
|
"symfony/css-selector": "^7.2",
|
||||||
|
"symfony/dom-crawler": "^7.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.0",
|
||||||
|
"hyperf/devtool": "~3.1.0",
|
||||||
|
"hyperf/testing": "~3.1.0",
|
||||||
|
"hyperf/watcher": "^3.1",
|
||||||
|
"mockery/mockery": "^1.0",
|
||||||
|
"phpstan/phpstan": "^1.0",
|
||||||
|
"swoole/ide-helper": "^5.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-openssl": "Required to use HTTPS.",
|
||||||
|
"ext-json": "Required to use JSON.",
|
||||||
|
"ext-pdo": "Required to use MySQL Client.",
|
||||||
|
"ext-pdo_mysql": "Required to use MySQL Client.",
|
||||||
|
"ext-redis": "Required to use Redis Client."
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "app/",
|
||||||
|
"Grpc\\": "Grpc/",
|
||||||
|
"GPBMetadata\\": "GPBMetadata/"
|
||||||
|
},
|
||||||
|
"files": []
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"HyperfTest\\": "./test/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"extra": [],
|
||||||
|
"scripts": {
|
||||||
|
"post-root-package-install": [
|
||||||
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
|
],
|
||||||
|
"post-autoload-dump": [
|
||||||
|
"rm -rf runtime/container"
|
||||||
|
],
|
||||||
|
"test": "co-phpunit --prepend test/bootstrap.php --colors=always",
|
||||||
|
"cs-fix": "php-cs-fixer fix $1",
|
||||||
|
"analyse": "phpstan analyse --memory-limit 300M",
|
||||||
|
"start": [
|
||||||
|
"Composer\\Config::disableProcessTimeout",
|
||||||
|
"php ./bin/hyperf.php start"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
12718
composer.lock
generated
Executable file
12718
composer.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
21
config/autoload/annotations.php
Executable file
21
config/autoload/annotations.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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'scan' => [
|
||||||
|
'paths' => [
|
||||||
|
BASE_PATH . '/app',
|
||||||
|
],
|
||||||
|
'ignore_annotations' => [
|
||||||
|
'mixin',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
13
config/autoload/aspects.php
Executable file
13
config/autoload/aspects.php
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
];
|
28
config/autoload/async_queue.php
Executable file
28
config/autoload/async_queue.php
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'default' => [
|
||||||
|
'driver' => \Hyperf\AsyncQueue\Driver\RedisDriver::class,
|
||||||
|
'redis' => [
|
||||||
|
'pool' => 'default',
|
||||||
|
],
|
||||||
|
'channel' => '{queue}',
|
||||||
|
'timeout' => 2,
|
||||||
|
'retry_seconds' => 5,
|
||||||
|
'handle_timeout' => 10,
|
||||||
|
'processes' => 1,
|
||||||
|
'concurrent' => [
|
||||||
|
'limit' => 10,
|
||||||
|
],
|
||||||
|
'max_messages' => 0,
|
||||||
|
],
|
||||||
|
];
|
19
config/autoload/cache.php
Executable file
19
config/autoload/cache.php
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'default' => [
|
||||||
|
'driver' => Hyperf\Cache\Driver\RedisDriver::class,
|
||||||
|
'packer' => Hyperf\Codec\Packer\PhpSerializerPacker::class,
|
||||||
|
'prefix' => 'c:',
|
||||||
|
'skip_cache_results' => [],
|
||||||
|
],
|
||||||
|
];
|
13
config/autoload/commands.php
Executable file
13
config/autoload/commands.php
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
];
|
41
config/autoload/config_center.php
Executable file
41
config/autoload/config_center.php
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
use Hyperf\ConfigCenter\Mode;
|
||||||
|
|
||||||
|
use function Hyperf\Support\env;
|
||||||
|
|
||||||
|
return [
|
||||||
|
// 'enable' => (bool) env('CONFIG_CENTER_ENABLE', true),
|
||||||
|
// 'driver' => env('CONFIG_CENTER_DRIVER', 'nacos'),
|
||||||
|
// 'mode' => env('CONFIG_CENTER_MODE', Mode::PROCESS),
|
||||||
|
// 'drivers' => [
|
||||||
|
// 'nacos' => [
|
||||||
|
// 'driver' => Hyperf\ConfigNacos\NacosDriver::class,
|
||||||
|
// 'merge_mode' => Hyperf\ConfigNacos\Constants::CONFIG_MERGE_OVERWRITE,
|
||||||
|
// 'interval' => 3,
|
||||||
|
// 'default_key' => 'nacos_config',
|
||||||
|
// 'listener_config' => [
|
||||||
|
// // dataId, group, tenant, type, content
|
||||||
|
// 'nacos_config' => [
|
||||||
|
// 'tenant' => 'tenant', // corresponding with service.namespaceId
|
||||||
|
// 'data_id' => 'hyperf-service-config',
|
||||||
|
// 'group' => 'DEFAULT_GROUP',
|
||||||
|
// ],
|
||||||
|
// 'nacos_config.data' => [
|
||||||
|
// 'data_id' => 'hyperf-service-config-yml',
|
||||||
|
// 'group' => 'DEFAULT_GROUP',
|
||||||
|
// 'type' => 'yml',
|
||||||
|
// ],
|
||||||
|
// ],
|
||||||
|
// ],
|
||||||
|
// ],
|
||||||
|
];
|
55
config/autoload/databases.php
Executable file
55
config/autoload/databases.php
Executable file
@ -0,0 +1,55 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
use function Hyperf\Support\env;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => [
|
||||||
|
'driver' => env('DB_DRIVER', 'mysql'),
|
||||||
|
'host' => env('DB_HOST', '192.168.23.15'),
|
||||||
|
'port' => env('DB_PORT', 3306),
|
||||||
|
'database' => env('DB_DATABASE', 'seo'),
|
||||||
|
'username' => env('DB_USERNAME', 'root'),
|
||||||
|
'password' => env('DB_PASSWORD', 'root'),
|
||||||
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||||
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||||
|
'prefix' => env('DB_PREFIX', ''),
|
||||||
|
'logging' => [
|
||||||
|
'enable' => true, // ✅ 开启日志
|
||||||
|
'level' => \Psr\Log\LogLevel::DEBUG,
|
||||||
|
],
|
||||||
|
'pool' => [
|
||||||
|
'min_connections' => 1,
|
||||||
|
'max_connections' => 20,
|
||||||
|
'connect_timeout' => 10.0,
|
||||||
|
'wait_timeout' => 3.0,
|
||||||
|
'heartbeat' => -1,
|
||||||
|
'max_idle_time' => (float) env('DB_MAX_IDLE_TIME', 60),
|
||||||
|
],
|
||||||
|
'cache' => [
|
||||||
|
'handler' => Hyperf\ModelCache\Handler\RedisHandler::class,
|
||||||
|
'cache_key' => '{mc:%s:m:%s}:%s:%s',
|
||||||
|
'prefix' => 'default',
|
||||||
|
'ttl' => 3600 * 24,
|
||||||
|
'empty_model_ttl' => 600,
|
||||||
|
'load_script' => true,
|
||||||
|
],
|
||||||
|
'commands' => [
|
||||||
|
'gen:model' => [
|
||||||
|
'path' => 'app/Model',
|
||||||
|
'force_casts' => true,
|
||||||
|
'inheritance' => 'Model',
|
||||||
|
'uses' => '',
|
||||||
|
'table_mapping' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
13
config/autoload/dependencies.php
Executable file
13
config/autoload/dependencies.php
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
];
|
44
config/autoload/devtool.php
Executable file
44
config/autoload/devtool.php
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'generator' => [
|
||||||
|
'amqp' => [
|
||||||
|
'consumer' => [
|
||||||
|
'namespace' => 'App\\Amqp\\Consumer',
|
||||||
|
],
|
||||||
|
'producer' => [
|
||||||
|
'namespace' => 'App\\Amqp\\Producer',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'aspect' => [
|
||||||
|
'namespace' => 'App\\Aspect',
|
||||||
|
],
|
||||||
|
'command' => [
|
||||||
|
'namespace' => 'App\\Command',
|
||||||
|
],
|
||||||
|
'controller' => [
|
||||||
|
'namespace' => 'App\\Controller',
|
||||||
|
],
|
||||||
|
'job' => [
|
||||||
|
'namespace' => 'App\\Job',
|
||||||
|
],
|
||||||
|
'listener' => [
|
||||||
|
'namespace' => 'App\\Listener',
|
||||||
|
],
|
||||||
|
'middleware' => [
|
||||||
|
'namespace' => 'App\\Middleware',
|
||||||
|
],
|
||||||
|
'Process' => [
|
||||||
|
'namespace' => 'App\\Processes',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
20
config/autoload/exceptions.php
Executable file
20
config/autoload/exceptions.php
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'handler' => [
|
||||||
|
'http' => [
|
||||||
|
Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
|
||||||
|
App\Exception\Handler\AppExceptionHandler::class,
|
||||||
|
\Hyperf\Validation\ValidationExceptionHandler::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
15
config/autoload/listeners.php
Executable file
15
config/autoload/listeners.php
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler::class,
|
||||||
|
Hyperf\Command\Listener\FailToHandleListener::class,
|
||||||
|
];
|
47
config/autoload/logger.php
Executable file
47
config/autoload/logger.php
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'default' => [
|
||||||
|
'handler' => [
|
||||||
|
'class' => Monolog\Handler\StreamHandler::class,
|
||||||
|
'constructor' => [
|
||||||
|
'stream' => BASE_PATH . '/runtime/logs/hyperf.log',
|
||||||
|
'level' => Monolog\Logger::DEBUG,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'formatter' => [
|
||||||
|
'class' => Monolog\Formatter\LineFormatter::class,
|
||||||
|
'constructor' => [
|
||||||
|
'format' => null,
|
||||||
|
'dateFormat' => 'Y-m-d H:i:s',
|
||||||
|
'allowInlineLineBreaks' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'command' => [
|
||||||
|
'handler' => [
|
||||||
|
'class' => Monolog\Handler\StreamHandler::class,
|
||||||
|
'constructor' => [
|
||||||
|
'stream' => BASE_PATH . '/runtime/logs/hyperf-command.log',
|
||||||
|
'level' => Monolog\Logger::INFO,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'formatter' => [
|
||||||
|
'class' => Monolog\Formatter\LineFormatter::class,
|
||||||
|
'constructor' => [
|
||||||
|
'format' => null,
|
||||||
|
'dateFormat' => 'Y-m-d H:i:s',
|
||||||
|
'allowInlineLineBreaks' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
16
config/autoload/middlewares.php
Executable file
16
config/autoload/middlewares.php
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'http' => [
|
||||||
|
\Hyperf\Validation\Middleware\ValidationMiddleware::class
|
||||||
|
],
|
||||||
|
];
|
24
config/autoload/nacos.php
Executable file
24
config/autoload/nacos.php
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
<?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
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
// // nacos server url like https://nacos.hyperf.io, Priority is higher than host:port
|
||||||
|
// // 'url' => '',
|
||||||
|
// // The nacos host info
|
||||||
|
// 'host' => '172.17.0.4',
|
||||||
|
// 'port' => 8848,
|
||||||
|
// // The nacos account info
|
||||||
|
// 'username' => 'nacos',
|
||||||
|
// 'password' => 'nacos',
|
||||||
|
// 'guzzle' => [
|
||||||
|
// 'config' => null,
|
||||||
|
// ],
|
||||||
|
];
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user