no message

This commit is contained in:
hellcat 2026-02-12 12:36:53 +08:00
commit 65ca290e0b
209 changed files with 97940 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[compose.yaml]
indent_size = 4

65
.env.example Normal file
View File

@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

11
.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
*.log
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.fleet
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
Homestead.json
Homestead.yaml
Thumbs.db
!/_env/*

59
README.md Executable file
View File

@ -0,0 +1,59 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Redberry](https://redberry.international/laravel-development)**
- **[Active Logic](https://activelogic.com)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

85
_env/.env Executable file
View File

@ -0,0 +1,85 @@
token_base = wocaonide
is_loc = true
url_master_base = dd.loc
site_code = test.com
site_code_short = tt
page_limit = 2
app_title = SSR/V2ray/Clash免费节点每天更新 - v2rayfreenode
app_desc = SSR/V2ray/Clash免费节点每天更新
app_keywords = v2rayclashfreeClash,Quantumult,Shadowrocket,SSR,v2ray,免费节点,节点,订阅链接
url_resource_base = resource.loc
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
SCOUT_QUEUE=false
VITE_DEV_SERVER_URL=http://127.0.0.1:5173
APP_NAME=Laravelenv
APP_ENV=local
APP_KEY=base64:VkMoebPURG4A1ker22M5nfFV+tcjph+eYvQrJYANq0Y=
APP_DEBUG=true
APP_URL=http://fn-b.loc
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=fn_b
DB_USERNAME=root
DB_PASSWORD=root
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
RESPONSE_CACHE_ENABLED=false
CACHE_STORE=file
RESPONSE_CACHE_STORE=file
RESPONSE_CACHE_DRIVER=file
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

584
_env/sql.sql Normal file
View File

@ -0,0 +1,584 @@
/*M!999999\- enable the sandbox mode */
-- MariaDB dump 10.19-11.6.2-MariaDB, for osx10.19 (x86_64)
--
-- Host: localhost Database: fn_b
-- ------------------------------------------------------
-- Server version 11.6.2-MariaDB
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */;
--
-- Table structure for table `article_freenode_thumb_nums`
--
DROP TABLE IF EXISTS `article_freenode_thumb_nums`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `article_freenode_thumb_nums` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`num` int(11) DEFAULT 1,
`author_code` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `num` (`num`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `article_freenode_thumb_nums`
--
LOCK TABLES `article_freenode_thumb_nums` WRITE;
/*!40000 ALTER TABLE `article_freenode_thumb_nums` DISABLE KEYS */;
INSERT INTO `article_freenode_thumb_nums` VALUES
(2,2,'tg_xiuren_a');
/*!40000 ALTER TABLE `article_freenode_thumb_nums` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `article_likes`
--
DROP TABLE IF EXISTS `article_likes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `article_likes` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`iArticleId` bigint(20) unsigned NOT NULL COMMENT '文章ID',
`sIp` varchar(45) NOT NULL COMMENT '支持IPv4和IPv6',
`sFingerprint` varchar(64) NOT NULL COMMENT '浏览器唯一指纹(建议存储MD5或SHA1)',
`created_at` timestamp NULL DEFAULT current_timestamp(),
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_article_fingerprint` (`iArticleId`,`sFingerprint`),
KEY `idx_article_likes_sip` (`sIp`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='游客点赞记录表';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `article_likes`
--
LOCK TABLES `article_likes` WRITE;
/*!40000 ALTER TABLE `article_likes` DISABLE KEYS */;
INSERT INTO `article_likes` VALUES
(6,1,'127.0.0.1','bfed8360f6c1fc7039f49002245377d6','2026-02-01 02:03:19','2026-02-01 02:03:19'),
(7,2,'127.0.0.1','bfed8360f6c1fc7039f49002245377d6','2026-02-01 02:57:19','2026-02-01 02:57:19'),
(9,26,'127.0.0.1','bfed8360f6c1fc7039f49002245377d6','2026-02-02 00:03:59','2026-02-02 00:03:59'),
(10,10,'127.0.0.1','bfed8360f6c1fc7039f49002245377d6','2026-02-02 00:05:55','2026-02-02 00:05:55'),
(11,33,'127.0.0.1','bfed8360f6c1fc7039f49002245377d6','2026-02-05 20:47:30','2026-02-05 20:47:30'),
(12,34,'127.0.0.1','bfed8360f6c1fc7039f49002245377d6','2026-02-06 00:38:54','2026-02-06 00:38:54'),
(13,35,'127.0.0.1','bfed8360f6c1fc7039f49002245377d6','2026-02-07 00:51:18','2026-02-07 00:51:18'),
(14,9,'127.0.0.1','bfed8360f6c1fc7039f49002245377d6','2026-02-07 00:55:46','2026-02-07 00:55:46');
/*!40000 ALTER TABLE `article_likes` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `article_tag`
--
DROP TABLE IF EXISTS `article_tag`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `article_tag` (
`iArticleId` int(11) unsigned NOT NULL,
`iTagId` int(11) unsigned NOT NULL,
KEY `article_tag_idx` (`iArticleId`,`iTagId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `article_tag`
--
LOCK TABLES `article_tag` WRITE;
/*!40000 ALTER TABLE `article_tag` DISABLE KEYS */;
INSERT INTO `article_tag` VALUES
(35,5),
(35,6),
(35,7),
(35,8),
(35,9),
(35,10),
(35,11),
(35,12),
(36,5),
(36,6),
(36,7),
(36,8),
(36,9),
(36,10),
(36,11),
(36,12);
/*!40000 ALTER TABLE `article_tag` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `articles`
--
DROP TABLE IF EXISTS `articles`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `articles` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`iUserId` bigint(20) unsigned DEFAULT NULL,
`iCategoryId` bigint(20) unsigned NOT NULL COMMENT '所属分类ID',
`sTitle` varchar(200) NOT NULL COMMENT '标题',
`sTitleSub` varchar(255) DEFAULT NULL,
`sSlug` varchar(200) DEFAULT NULL COMMENT '文章别名',
`sContent` longtext NOT NULL COMMENT '正文',
`sThumb` varchar(255) DEFAULT NULL COMMENT '缩略图',
`iViews` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '阅读数',
`iStatus` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态1发布 0草稿',
`iIsTop` tinyint(4) DEFAULT 0,
`iLikeCount` int(11) DEFAULT 0,
`i7like` int(11) DEFAULT 0,
`i365like` int(11) DEFAULT 0,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `sSlug` (`sSlug`),
KEY `articles_icategoryid_index` (`iCategoryId`),
KEY `articles_istatus_index` (`iStatus`),
KEY `idx_i365like` (`i365like`),
KEY `iUserId` (`iUserId`),
CONSTRAINT `articles_category_fk` FOREIGN KEY (`iCategoryId`) REFERENCES `categories` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `articles`
--
LOCK TABLES `articles` WRITE;
/*!40000 ALTER TABLE `articles` DISABLE KEYS */;
INSERT INTO `articles` VALUES
(33,1,1,'「2月5日」2026年最新高速SSR/V2ray/Clash/Shadowrocket免费节点订阅链接','本次更新共50个可用节点最高速度5M/S。\n覆盖美国、美国、香港、乌克兰、阿根廷、土耳其等多个区域。\n复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。','fn20260205','加入tg频道获取更多新鲜免费节点<a href=\"https://t.me/v2rayfreenode_com\">https://t.me/v2rayfreenode_com</a>。\n\n本次更新共50个可用节点最高速度5M/S。\n覆盖美国、美国、香港、乌克兰、阿根廷、土耳其等多个区域。\n复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。\n\n机场推荐\n\n* 好好 - 就是好\n\n* 不好不好 - 就是不好\n\n* 你就是个瘠薄 - 知道不\n\n* 去死吧 - 去死\n\n免费节点订阅链接\n\nv2ray订阅链接\n\nhttps://nodehub.loc/vfn/5e08dd26fff8100b/20260205-v2ray.txt\n\nclash订阅链接\n\nhttps://nodehub.loc/vfn/5e08dd26fff8100b/20260205-clash.yaml\n\n温馨提示\n\n本站提供的都是免费且公共的节点稳定性与长时间连接速率无法与收费版的高速机场节点相提并论不能奢望太多。\n\n注意你自己的网络环境本地连接当中的DNS手动配置一下114.114.114.1148.8.8.8或是其它公共的DNS。\n\n如果某个网址无法代理访问可切换到全局代理模式一般可解决。',NULL,0,1,0,1,NULL,NULL,'2026-02-05 02:04:40','2026-02-05 20:47:30'),
(34,1,1,'「2月6日」2026年最新高速SSR/V2ray/Clash/Shadowrocket免费节点订阅链接','本次更新共24个可用节点最高速度8M/S。\n覆盖越南、瑞士、荷兰、泰国、美国、香港等多个区域。\n复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。','fn20260206','<p><b>加入tg频道获取更多新鲜免费节点<a target=\"__blank\" style=\"color:blue;\" href=\"https://t.me/t.me/v2rayfreenode_com\">t.me/v2rayfreenode_com</a>。</b></p><p></p><p>本次更新共24个可用节点最高速度8M/S。</p><p>覆盖越南、瑞士、荷兰、泰国、美国、香港等多个区域。</p><p>复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。</p><p></p><p><h2>机场推荐:</h2></p><p></p><p>* 好好 - 就是好</p><p></p><p>* 不好不好 - 就是不好</p><p></p><p>* 你就是个瘠薄 - 知道不</p><p></p><p>* 去死吧 - 去死</p><p></p><p><b><a target=\'__blank\' style=\'color:blue;\' href=\'https://fuc.loc\'>点击进入机场官网:富雨</a>。</b></p><p></p><p><h2>免费节点订阅链接:</h2></p><p></p><p><b>v2ray订阅链接</b></p><p></p><p><a href=\'javascript:;\' data-clipboard-text=\'https://nodehub.loc/vfn/90f98a22914332c7/20260206-v2ray.txt\' class=\'copy-permalink\'></p><p><span class=\'subCopy\'><i class=\'iconfont icon-link\'></i> https://nodehub.loc/vfn/90f98a22914332c7/20260206-v2ray.txt</span></a></p><p></p><p><b>clash订阅链接</b></p><p></p><p><a href=\'javascript:;\' data-clipboard-text=\'https://nodehub.loc/vfn/90f98a22914332c7/20260206-clash.yaml\' class=\'copy-permalink\'></p><p><span class=\'subCopy\'><i class=\'iconfont icon-link\'></i> https://nodehub.loc/vfn/90f98a22914332c7/20260206-clash.yaml</span></a></p><p></p><p>温馨提示:</p><p></p><p>本站提供的都是免费且公共的节点,稳定性与长时间连接速率无法与收费版的高速机场节点相提并论,不能奢望太多。</p><p></p><p>注意你自己的网络环境本地连接当中的DNS手动配置一下114.114.114.1148.8.8.8或是其它公共的DNS。</p><p></p><p>如果某个网址无法代理访问,可切换到全局代理模式,一般可解决。</p><p></p><p></p>','https://resource.loc/upload/images/tg_xiuren_a-01KFAWNMJ27J8TDP1H0YPMNDP4-1-thumb_c.jpg',0,1,0,1,NULL,NULL,'2026-02-05 23:36:50','2026-02-06 00:38:54'),
(35,1,1,'「2月7日」2026年最新高速SSR/V2ray/Clash/Shadowrocket免费节点订阅链接','本次更新共38个可用节点最高速度21M/S。\n覆盖香港、台湾、爱尔兰、乌克兰、阿根廷、韩国、德国等多个区域。\n复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。','fn20260207','<p><b>加入tg频道获取更多新鲜免费节点<a target=\"__blank\" style=\"color:blue;\" href=\"https://t.me/t.me/v2rayfreenode_com\">t.me/v2rayfreenode_com</a>。</b></p><p></p><p>本次更新共38个可用节点最高速度21M/S。</p><p>覆盖香港、台湾、爱尔兰、乌克兰、阿根廷、韩国、德国等多个区域。</p><p>复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。</p><p></p><p><h2>机场推荐:</h2></p><p></p><p>* 好好 - 就是好</p><p></p><p>* 不好不好 - 就是不好</p><p></p><p>* 你就是个瘠薄 - 知道不</p><p></p><p>* 去死吧 - 去死</p><p></p><p><b><a target=\'__blank\' style=\'color:blue;\' href=\'https://fuc.loc\'>点击进入机场官网:富雨</a>。</b></p><p></p><p><h2>免费节点订阅链接:</h2></p><p></p><p><b>v2ray订阅链接</b></p><p></p><p><a href=\'javascript:;\' data-clipboard-text=\'https://nodehub.loc/vfn/bdc9177b109b2339/20260207-v2ray.txt\' class=\'copy-permalink\'></p><p><span class=\'subCopy\'><i class=\'iconfont icon-link\'></i> https://nodehub.loc/vfn/bdc9177b109b2339/20260207-v2ray.txt</span></a></p><p></p><p><b>clash订阅链接</b></p><p></p><p><a href=\'javascript:;\' data-clipboard-text=\'https://nodehub.loc/vfn/bdc9177b109b2339/20260207-clash.yaml\' class=\'copy-permalink\'></p><p><span class=\'subCopy\'><i class=\'iconfont icon-link\'></i> https://nodehub.loc/vfn/bdc9177b109b2339/20260207-clash.yaml</span></a></p><p></p><p>温馨提示:</p><p></p><p>本站提供的都是免费且公共的节点,稳定性与长时间连接速率无法与收费版的高速机场节点相提并论,不能奢望太多。</p><p></p><p>注意你自己的网络环境本地连接当中的DNS手动配置一下114.114.114.1148.8.8.8或是其它公共的DNS。</p><p></p><p>如果某个网址无法代理访问,可切换到全局代理模式,一般可解决。</p><p></p><p></p>','https://resource.loc/upload/images/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-1-thumb_b.jpg',0,1,0,109,937,100010,'2026-02-07 00:09:20','2026-02-09 01:22:53'),
(36,1,1,'「2月11日」2026年最新高速SSR/V2ray/Clash/Shadowrocket免费节点订阅链接','本次更新共37个可用节点最高速度22M/S。\n覆盖台湾、越南、荷兰、冰岛、新加坡、台湾、土耳其、法国、加拿大等多个区域。\n复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。','fn20260211','<p><b>加入tg频道获取更多新鲜免费节点<a target=\"__blank\" style=\"color:blue;\" href=\"https://t.me/v2rayfreenode_com\">t.me/v2rayfreenode_com</a>。</b></p><p></p><p>本次更新共37个可用节点最高速度22M/S。</p><p>覆盖台湾、越南、荷兰、冰岛、新加坡、台湾、土耳其、法国、加拿大等多个区域。</p><p>复制下方的v2ray/Clash订阅链接在客户端添加即可正常使用。</p><p></p><p><h2>机场推荐:</h2></p><p></p><p>* 好好 - 就是好</p><p></p><p>* 不好不好 - 就是不好</p><p></p><p>* 你就是个瘠薄 - 知道不</p><p></p><p>* 去死吧 - 去死</p><p></p><p><b><a target=\'__blank\' style=\'color:blue;\' href=\'https://fuc.loc\'>点击进入机场官网:富雨</a>。</b></p><p></p><p><h2>免费节点订阅链接:</h2></p><p></p><p><b>v2ray订阅链接</b></p><p></p><p><a href=\'javascript:;\' data-clipboard-text=\'https://nodehub.loc/vfn/ff0acf9080acfa7e/20260211-v2ray.txt\' class=\'copy-permalink\'></p><p><span class=\'subCopy\'><i class=\'iconfont icon-link\'></i> https://nodehub.loc/vfn/ff0acf9080acfa7e/20260211-v2ray.txt</span></a></p><p></p><p><b>clash订阅链接</b></p><p></p><p><a href=\'javascript:;\' data-clipboard-text=\'https://nodehub.loc/vfn/ff0acf9080acfa7e/20260211-clash.yaml\' class=\'copy-permalink\'></p><p><span class=\'subCopy\'><i class=\'iconfont icon-link\'></i> https://nodehub.loc/vfn/ff0acf9080acfa7e/20260211-clash.yaml</span></a></p><p></p><p>温馨提示:</p><p></p><p>本站提供的都是免费且公共的节点,稳定性与长时间连接速率无法与收费版的高速机场节点相提并论,不能奢望太多。</p><p></p><p>注意你自己的网络环境本地连接当中的DNS手动配置一下114.114.114.1148.8.8.8或是其它公共的DNS。</p><p></p><p>如果某个网址无法代理访问,可切换到全局代理模式,一般可解决。</p><p></p><p></p>','https://resource.loc/upload/images/tg_xiuren_a-01KF5WX0N8VE3D773CFWZQBJVX-1-thumb_c.jpg',0,1,0,0,11,NULL,'2026-02-10 23:39:40','2026-02-10 23:59:48');
/*!40000 ALTER TABLE `articles` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `cache`
--
DROP TABLE IF EXISTS `cache`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `cache` (
`key` varchar(255) NOT NULL,
`value` mediumtext NOT NULL,
`expiration` int(11) NOT NULL,
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `cache`
--
LOCK TABLES `cache` WRITE;
/*!40000 ALTER TABLE `cache` DISABLE KEYS */;
/*!40000 ALTER TABLE `cache` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `cache_locks`
--
DROP TABLE IF EXISTS `cache_locks`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `cache_locks` (
`key` varchar(255) NOT NULL,
`owner` varchar(255) NOT NULL,
`expiration` int(11) NOT NULL,
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `cache_locks`
--
LOCK TABLES `cache_locks` WRITE;
/*!40000 ALTER TABLE `cache_locks` DISABLE KEYS */;
/*!40000 ALTER TABLE `cache_locks` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `categories`
--
DROP TABLE IF EXISTS `categories`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `categories` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`iParentId` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '父级ID',
`sName` varchar(50) NOT NULL COMMENT '分类名称',
`sSlug` varchar(50) NOT NULL COMMENT 'URL标识',
`sDescription` varchar(255) DEFAULT NULL COMMENT '描述',
`iSort` int(11) NOT NULL DEFAULT 0 COMMENT '排序',
`iStatus` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态1显示 0隐藏',
`sHtmlClass` varchar(50) DEFAULT NULL,
`iShowChildren` tinyint(4) DEFAULT 0,
`aBanner` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`aBanner`)),
PRIMARY KEY (`id`),
UNIQUE KEY `categories_sslug_unique` (`sSlug`),
KEY `categories_istatus_index` (`iStatus`),
KEY `categories_iparentid_index` (`iParentId`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `categories`
--
LOCK TABLES `categories` WRITE;
/*!40000 ALTER TABLE `categories` DISABLE KEYS */;
INSERT INTO `categories` VALUES
(1,0,'免费节点','freenode','',0,1,'fas fa-wifi',0,NULL),
(2,0,'客户端','client','',0,1,'fab fa-app-store-ios',1,NULL),
(3,2,'windows','windows',NULL,0,1,NULL,0,'{\"img_file\":\"windows.jpg\", \"img_top\":\"24%\"}'),
(4,2,'andorid','andorid',NULL,0,1,NULL,0,NULL);
/*!40000 ALTER TABLE `categories` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `dog_option`
--
DROP TABLE IF EXISTS `dog_option`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `dog_option` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`dog_name` varchar(255) DEFAULT NULL,
`k` varchar(255) DEFAULT NULL,
`v` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_this_name_k` (`dog_name`,`k`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `dog_option`
--
LOCK TABLES `dog_option` WRITE;
/*!40000 ALTER TABLE `dog_option` DISABLE KEYS */;
/*!40000 ALTER TABLE `dog_option` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `failed_jobs`
--
DROP TABLE IF EXISTS `failed_jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `failed_jobs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(255) NOT NULL,
`connection` text NOT NULL,
`queue` text NOT NULL,
`payload` longtext NOT NULL,
`exception` longtext NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `failed_jobs`
--
LOCK TABLES `failed_jobs` WRITE;
/*!40000 ALTER TABLE `failed_jobs` DISABLE KEYS */;
/*!40000 ALTER TABLE `failed_jobs` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `job_batches`
--
DROP TABLE IF EXISTS `job_batches`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `job_batches` (
`id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`total_jobs` int(11) NOT NULL,
`pending_jobs` int(11) NOT NULL,
`failed_jobs` int(11) NOT NULL,
`failed_job_ids` longtext NOT NULL,
`options` mediumtext DEFAULT NULL,
`cancelled_at` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`finished_at` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `job_batches`
--
LOCK TABLES `job_batches` WRITE;
/*!40000 ALTER TABLE `job_batches` DISABLE KEYS */;
/*!40000 ALTER TABLE `job_batches` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `jobs`
--
DROP TABLE IF EXISTS `jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `jobs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`queue` varchar(255) NOT NULL,
`payload` longtext NOT NULL,
`attempts` tinyint(3) unsigned NOT NULL,
`reserved_at` int(10) unsigned DEFAULT NULL,
`available_at` int(10) unsigned NOT NULL,
`created_at` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `jobs_queue_index` (`queue`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `jobs`
--
LOCK TABLES `jobs` WRITE;
/*!40000 ALTER TABLE `jobs` DISABLE KEYS */;
INSERT INTO `jobs` VALUES
(1,'default','{\"uuid\":\"314518ec-b054-4d77-a356-1aa9ec26eaac\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769775319,\"delay\":null}',0,NULL,1769775319,1769775319),
(2,'default','{\"uuid\":\"abc3fbce-24a0-4eaf-96c4-7fcaf00f95aa\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769776393,\"delay\":null}',0,NULL,1769776393,1769776393),
(3,'default','{\"uuid\":\"7ad35a86-09cb-4d96-82c7-42750b0231bc\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769776737,\"delay\":null}',0,NULL,1769776737,1769776737),
(4,'default','{\"uuid\":\"339cc071-8610-4382-9c5a-de4213562305\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769777044,\"delay\":null}',0,NULL,1769777044,1769777044),
(5,'default','{\"uuid\":\"d424ace0-602a-4d8d-a2f8-4421f9a81aeb\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769777247,\"delay\":null}',0,NULL,1769777247,1769777247),
(6,'default','{\"uuid\":\"6f098b90-ead8-4da3-87b9-59e253c03372\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769777583,\"delay\":null}',0,NULL,1769777583,1769777583),
(7,'default','{\"uuid\":\"e16751ef-3835-477a-bcdd-4f58799a7e6e\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769777778,\"delay\":null}',0,NULL,1769777778,1769777778),
(8,'default','{\"uuid\":\"d6494cf5-1d0b-47e2-ab4f-cc9a0a9a0f5c\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769777794,\"delay\":null}',0,NULL,1769777794,1769777794),
(9,'default','{\"uuid\":\"c6de7ee4-7f70-4a55-a954-f7b3d9c2a187\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769778083,\"delay\":null}',0,NULL,1769778083,1769778083),
(10,'default','{\"uuid\":\"219e91da-b167-4342-9135-3199d3d6731e\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769780368,\"delay\":null}',0,NULL,1769780368,1769780368),
(11,'default','{\"uuid\":\"d59f8460-c8ae-4587-b651-b42f1cd4c592\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769780529,\"delay\":null}',0,NULL,1769780529,1769780529),
(12,'default','{\"uuid\":\"901ccd34-c4cf-44f3-a770-265c71567ee7\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769780620,\"delay\":null}',0,NULL,1769780620,1769780620),
(13,'default','{\"uuid\":\"4616bf6b-cc4f-4379-941c-88d99f89e8cf\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769832922,\"delay\":null}',0,NULL,1769832922,1769832922),
(14,'default','{\"uuid\":\"28ee4b0b-2e02-4a73-9da2-c99180cca4ab\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769833122,\"delay\":null}',0,NULL,1769833122,1769833122),
(15,'default','{\"uuid\":\"1870c533-c149-498d-9b33-dc5dd75c9195\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769833645,\"delay\":null}',0,NULL,1769833646,1769833646),
(16,'default','{\"uuid\":\"9b079f8e-c05a-4f41-baa9-ae56908f2832\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769833715,\"delay\":null}',0,NULL,1769833715,1769833715),
(17,'default','{\"uuid\":\"e719def7-8635-4c24-b8c9-55dd64686001\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769833825,\"delay\":null}',0,NULL,1769833825,1769833825),
(18,'default','{\"uuid\":\"6e69c65a-6de9-4e29-ac49-dd0e62700493\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769833876,\"delay\":null}',0,NULL,1769833876,1769833876),
(19,'default','{\"uuid\":\"f6052246-365d-45e7-8124-963cc9a8e9cc\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769833926,\"delay\":null}',0,NULL,1769833926,1769833926),
(20,'default','{\"uuid\":\"7f0e95cd-0440-447e-b0ed-61deec6ef651\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769833959,\"delay\":null}',0,NULL,1769833959,1769833959),
(21,'default','{\"uuid\":\"a6c5b6b2-5029-48ff-93f4-3334c8f33983\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769834017,\"delay\":null}',0,NULL,1769834017,1769834017),
(22,'default','{\"uuid\":\"60f78b6b-3fc0-4a55-918d-d2a5db67211d\",\"displayName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"maxExceptions\":null,\"failOnTimeout\":false,\"backoff\":null,\"timeout\":null,\"retryUntil\":null,\"data\":{\"commandName\":\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\",\"command\":\"O:33:\\\"Laravel\\\\Scout\\\\Jobs\\\\MakeSearchable\\\":2:{s:6:\\\"models\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":5:{s:5:\\\"class\\\";s:19:\\\"App\\\\Models\\\\Articles\\\";s:2:\\\"id\\\";a:29:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:8;i:7;i:9;i:8;i:10;i:9;i:11;i:10;i:12;i:11;i:13;i:12;i:14;i:13;i:15;i:14;i:16;i:15;i:17;i:16;i:18;i:17;i:19;i:18;i:20;i:19;i:21;i:20;i:22;i:21;i:23;i:22;i:24;i:23;i:25;i:24;i:26;i:25;i:27;i:26;i:28;i:27;i:29;i:28;i:30;}s:9:\\\"relations\\\";a:0:{}s:10:\\\"connection\\\";s:5:\\\"mysql\\\";s:15:\\\"collectionClass\\\";N;}s:10:\\\"connection\\\";s:8:\\\"database\\\";}\"},\"createdAt\":1769834486,\"delay\":null}',0,NULL,1769834486,1769834486);
/*!40000 ALTER TABLE `jobs` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `migrations`
--
DROP TABLE IF EXISTS `migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `migrations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`migration` varchar(255) NOT NULL,
`batch` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `migrations`
--
LOCK TABLES `migrations` WRITE;
/*!40000 ALTER TABLE `migrations` DISABLE KEYS */;
/*!40000 ALTER TABLE `migrations` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `option`
--
DROP TABLE IF EXISTS `option`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `option` (
`k` varchar(255) NOT NULL,
`v` varchar(255) DEFAULT NULL,
PRIMARY KEY (`k`),
UNIQUE KEY `k` (`k`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `option`
--
LOCK TABLES `option` WRITE;
/*!40000 ALTER TABLE `option` DISABLE KEYS */;
INSERT INTO `option` VALUES
('dog_limit','30');
/*!40000 ALTER TABLE `option` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `password_reset_tokens`
--
DROP TABLE IF EXISTS `password_reset_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `password_reset_tokens` (
`email` varchar(255) NOT NULL,
`token` varchar(255) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `password_reset_tokens`
--
LOCK TABLES `password_reset_tokens` WRITE;
/*!40000 ALTER TABLE `password_reset_tokens` DISABLE KEYS */;
/*!40000 ALTER TABLE `password_reset_tokens` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `sessions`
--
DROP TABLE IF EXISTS `sessions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `sessions` (
`id` varchar(255) NOT NULL,
`user_id` bigint(20) unsigned DEFAULT NULL,
`ip_address` varchar(45) DEFAULT NULL,
`user_agent` text DEFAULT NULL,
`payload` longtext NOT NULL,
`last_activity` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `sessions_user_id_index` (`user_id`),
KEY `sessions_last_activity_index` (`last_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `sessions`
--
LOCK TABLES `sessions` WRITE;
/*!40000 ALTER TABLE `sessions` DISABLE KEYS */;
INSERT INTO `sessions` VALUES
('4wDk8oLxXxcc1ec81FFkzmEA7B1XnxwxAeibg2RA',NULL,'127.0.0.1','','YToyOntzOjY6Il90b2tlbiI7czo0MDoibTlsZnRTRUxnZ2FMNlZXUnZoRmZjQzZXUWNuOHdWRFZPQU9JWFp3USI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==',1770796010),
('H6gerOjhSpvfaVoI850Wit0KWDKobktEs0pmTVUz',NULL,'127.0.0.1','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15','YTozOntzOjY6Il90b2tlbiI7czo0MDoib3dMWEl2Q0pSNEc0R05xTEdqQVlkTXUxRmVGM2NiUHpuQll2OXhnbCI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6MjQ6Imh0dHA6Ly9mbi1iLmxvYy9mcmVlbm9kZSI7czo1OiJyb3V0ZSI7czoxMzoiYXJ0aWNsZXMubGlzdCI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fX0=',1770804442),
('qnewggVDrZqiqXZEXOQ9I5RbTBTsWf3RjEhkjm7F',NULL,'127.0.0.1','','YToyOntzOjY6Il90b2tlbiI7czo0MDoiWVRhTDFCWDdHWXR3bXFyWlBtenBGY0ZxWGNCZWxpcjFhYUtkR3VQeCI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==',1770795582),
('r3May3X9tUPh7997YKw6Dm6GrRIFrWF57eF16xTr',NULL,'127.0.0.1','','YToyOntzOjY6Il90b2tlbiI7czo0MDoiOTAzZEVza3pOMHRsSTFaTUNBSU1MRXFZcUl2V1VtNTNQNFhWYWpBcSI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==',1770796533),
('V6y6YVIMJpxRJb1FNFYLSEXBSs1SBLL5fMbwynb3',NULL,'127.0.0.1','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15','YTozOntzOjY6Il90b2tlbiI7czo0MDoiQlpZeHRIenZNbG13VmZIU2xOUDliS1dIVW9ZbGE4akdVc3NHbDVHSiI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6MTU6Imh0dHA6Ly9mbi1iLmxvYyI7czo1OiJyb3V0ZSI7czoxMzoiYXJ0aWNsZXMuaG9tZSI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fX0=',1770801403),
('wTzOJU2pCja2CqJ9ItP3p4Lj5gTs5cSMbeyzZpUA',NULL,'127.0.0.1','','YToyOntzOjY6Il90b2tlbiI7czo0MDoiQXBkRmh4TlJoTzBJY0IxbUVyMjVnNjE1bE4xT3JwQVVRUUlMeG82QiI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==',1770796790);
/*!40000 ALTER TABLE `sessions` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `tags`
--
DROP TABLE IF EXISTS `tags`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `tags` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`sName` varchar(50) NOT NULL,
`sSlug` varchar(50) NOT NULL,
`iArticleCount` int(11) unsigned DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `slug_unique` (`sSlug`),
KEY `iArticleCount` (`iArticleCount`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `tags`
--
LOCK TABLES `tags` WRITE;
/*!40000 ALTER TABLE `tags` DISABLE KEYS */;
INSERT INTO `tags` VALUES
(5,'Shadowrocket','shadowrocket-7vf7o',2),
(6,'Quantumult','quantumult-Ggc90',2),
(7,'Clash','clash-wd81q',2),
(8,'SSR','ssr-BjeEq',2),
(9,'节点','jie-dian-yIt5z',2),
(10,'订阅链接','ding-yue-lian-jie-ruNF0',2),
(11,'免费节点','mian-fei-jie-dian-hOpYo',2),
(12,'v2ray','v2ray-Pd4HR',2);
/*!40000 ALTER TABLE `tags` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Table structure for table `users`
--
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) NOT NULL,
`remember_token` varchar(100) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `users`
--
LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` VALUES
(1,'v2rayfreenode','v2rayfreenode',NULL,'111',NULL,NULL,NULL);
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */;
-- Dump completed on 2026-02-11 18:12:42

View File

@ -0,0 +1,40 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use App\Models\ArticleLikes;
class ClearDb extends Command
{
protected $signature = 'clear_db';
protected $description = '清理过期数据';
public function handle(): void
{
$this->info("开始清理过期数据...");
$this->articleLikes();
$this->info("数据清理完成 !");
}
private function articleLikes()
{
// 1. 确定 30 天前的时间点
$oThresholdDate = now()->subDays(30);
// 2. 执行删除:直接在数据库层面一锅端
// 药水哥提示:这样写只发一条 DELETE SQL效率最高
$iDeletedCount = ArticleLikes::where('created_at', '<', $oThresholdDate)->delete();
// 3. 给点反馈,这叫“开发者的温柔”
if ($iDeletedCount > 0) {
$this->warn("已成功清理 {$iDeletedCount} 条 30 天前的点赞记录!");
} else {
$this->info("点赞表很干净,没有过期数据。");
}
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\File;
use App\Models\ArticleLikes;
class StatOrderLikes extends Command
{
protected $signature = 'stat:order-likes';
protected $description = '每周校准标签统计并更新视图组件';
// public function handle(): void
// {
// $this->info("order-likes开始渲染...");
//
// $aOrderLikes = ArticleLikes::with(['oArticle' => function($query) {
// $query->where('iStatus', 1); // 只拿发布的,草稿别出来丢人
// }])
// ->orderBy('created_at', 'desc')
// ->limit(10) // 拿最近的 10 条点赞记录
// ->get();
//
// $sHtml = View::make('components.order-likes', ['oOrderLikes' => $aOrderLikes])->render();
//
// $sFilePath = resource_path('views/components/generated-order-likes.blade.php');
//
// File::put($sFilePath, "\n" . $sHtml);
//
// $this->info("渲染完成 !");
// }
public function handle(): void
{
$this->info("开始统计最近 7 天点赞排名并渲染...");
// 1. 获取 7 天前的时间点
$oStartDate = now()->subDays(30);
// 2. 高标准聚合查询
$aOrderLikes = ArticleLikes::select('iArticleId', \DB::raw('count(*) as iTotalLikes'))
->where('created_at', '>=', $oStartDate) // 只要最近 7 天的
->groupBy('iArticleId')
->orderBy('iTotalLikes', 'desc') // 按点赞总数排
->with(['oArticle' => function($query) {
$query->where('iStatus', 1); // 同样,草稿文章爬远点
}])
->limit(10)
->get()
->filter(function($oItem) {
return $oItem->oArticle !== null; // 过滤掉万一被删了的文章
});
// 3. 渲染 HTML
// 药水哥提示:这里的变量名我改成了更加语义化的 aHotArticles
$sHtml = View::make('components.order-likes', ['oOrderLikes' => $aOrderLikes])->render();
// 4. 写入文件(保持你的风格)
$sFilePath = resource_path('views/components/generated-order-likes.blade.php');
// 增加一行注释,方便后期 debug 知道这玩意儿是什么时候生成的
$sComment = "<?php /* Generated at: " . now()->toDateTimeString() . " */ ?>\n";
File::put($sFilePath, $sComment . $sHtml);
$this->info("7天点赞排名渲染完成 !");
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\File;
class StatTagsCloud extends Command
{
protected $signature = 'stat:tags-cloud';
protected $description = '每周校准标签统计并更新视图组件';
public function handle(): void
{
$this->info("tags-cloud开始渲染...");
DB::transaction(function () {
// 1. 使用 JOIN 方式批量更新iArticleCount 默认为 0
// 这种写法比子查询更符合数据库执行计划优化
DB::table('tags')->update(['iArticleCount' => 0]);
DB::statement("
UPDATE tags t
INNER JOIN (
SELECT iTagId, COUNT(*) as iTotal
FROM article_tag
GROUP BY iTagId
) at ON t.id = at.iTagId
SET t.iArticleCount = at.iTotal
");
});
// 2. 渲染视图并持久化
$this->renderToComponent();
$this->info("渲染完成!");
}
/**
* 高标准:将统计结果预渲染,减少运行时查询
*/
private function renderToComponent(): void
{
// a 前缀,获取统计后的标签列表
$aTags = DB::table('tags')
->where('iArticleCount', '>', 0)
->orderByDesc('iArticleCount')
->get()
->toArray();
// 渲染 Blade 模板内容
// 假设你的模板在 resources/views/components/tags-cloud-content.blade.php
$sHtml = View::make('components.tags-cloud', ['aTags' => $aTags])->render();
// 将渲染好的 HTML 写入 resources 目录下的一个静态片段中
// 这样组件调用时直接 @include性能起飞
$sFilePath = resource_path('views/components/generated-tags-cloud.blade.php');
File::put($sFilePath, "\n" . $sHtml);
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Articles;
use Illuminate\Support\Facades\Cache;
class SyncArticleLikes extends Command
{
// 在 Kernel.php 中建议设置 ->everyFiveMinutes()
protected $signature = 'article:sync-likes';
protected $description = '高标准点赞7天自动碎化 + 全年天级随机';
public function handle()
{
$iBurstCount = 0; // 记录本次执行爆发了多少文章
$iYearlyCount = 0; // 记录本次执行有多少文章中了全年奖
// 1. 【7天爆发】碎化注入逻辑
Articles::where('i7like', '>', 0)
->where('iStatus', 1)
->chunkById(100, function ($aArticles) use (&$iBurstCount) {
foreach ($aArticles as $oArticle) {
$iDaysPassed = $oArticle->created_at->diffInDays(now());
if ($iDaysPassed > 6) continue;
$iRemainingSlots = (7 - $iDaysPassed) * 24 * 12;
if ($iRemainingSlots <= 0) continue;
$iBaseAdd = (int)ceil($oArticle->i7like / $iRemainingSlots);
if (mt_rand(1, 100) <= 50) {
$iAdd = mt_rand(1, max(1, $iBaseAdd * 2));
$this->transferLikes($oArticle, 'i7like', $iAdd);
$this->info("ID [{$oArticle->id}] 爆发:注入 {$iAdd} 个赞,剩余库存 {$oArticle->i7like}");
$iBurstCount++;
}
}
});
// 2. 【全年增长】天级随机逻辑
$sLockKey = 'sync_yearly_likes_lock:' . now()->format('Ymd');
if (!Cache::has($sLockKey)) {
$this->warn("检测到今日尚未进行全年随机摇号,开始抽奖...");
Articles::where('i365like', '>', 0)
->where('iStatus', 1)
->chunkById(100, function ($aArticles) use (&$iYearlyCount) {
foreach ($aArticles as $oArticle) {
if (mt_rand(1, 365) <= $oArticle->i365like) {
$oArticle->increment('iLikeCount');
$this->line("ID [{$oArticle->id}] 全年:运气爆发,喜提 1 个赞");
$iYearlyCount++;
}
}
});
Cache::put($sLockKey, 1, now()->addDay());
}
$this->comment("本次任务结束:{$iBurstCount} 篇爆发增长,{$iYearlyCount} 篇全年喜提。");
}
/**
* 核心转移逻辑
*/
private function transferLikes(Articles $oArticle, string $sField, int $iAmount): void
{
$iRealAdd = min($oArticle->$sField, $iAmount);
if ($iRealAdd > 0) {
$oArticle->increment('iLikeCount', $iRealAdd);
// 这里注意increment/decrement 会同步更新 model 对象的内存值
$oArticle->decrement($sField, $iRealAdd);
}
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
//use App\Models\Articles as ModelArticles;
use Spatie\ResponseCache\Facades\ResponseCache;
use App\Http\Controllers\Controller;
use App\Models\Articles as ModelArticles;
class CacheController extends Controller
{
public function clear()
{
ResponseCache::clear();
echo '缓存清除完毕';
exit;
}
public function test1()
{
$o = ModelArticles::find(1);
$o->sTitle = time();
$o->save();
tt($o->toArray());
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Api\v1;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\TomTool\Flow;
use App\Services\TomTool\Telegram\Slave as TeleSlave;
use App\Models\Articles;
use App\Models\Categories;
use App\Models\ArticleFreenodeThumbNum;
use App\Services\TomTool\HttpV2;
use App\Services\TagService;
class ArticlesController extends Controller
{
public function receiveFreenode(Request $oRequest)
{
$sObjName = "nodehub:article_receive_freenode";
$oFlow = Flow::make($sObjName);
$aData = $oRequest->all();
$aArticle = $aData["article"];
$sArticleTitle = $aArticle["title"];
$sArticleTitleSub = $aArticle["title_sub"];
$sArticleContent = $aArticle["content"];
$aArticleTags = $aArticle["tags"];
$sSlug = 'fn'.date("Ymd");
$sImgAuthor = "tg_xiuren_a";
$i7like = $aArticle["7like"];
$oThumbNum = ArticleFreenodeThumbNum::firstOrCreate(["author_code" => $sImgAuthor]);
$iThumbNum = $oThumbNum->num;
$sImagesApi = "https://".config("path.url_resource_base")."/api/images/get/url-by-num";
$aApiArg = [
"sAuthorCode" => $sImgAuthor,
"iNum" => $iThumbNum+1,
];
$sApiResult = HttpV2::make($sImagesApi, "post")->withToken()->setDataA($aApiArg)->send();
$aApiResult = json_decode($sApiResult, true);
if (!isset($aApiResult["isDone"]) || !$aApiResult["isDone"]) {
echo $sApiResult;
return response()->json($oFlow->fail("{$sObjName}的图片api调用失败"));
}
$sThumbPath = $aApiResult["aData"]["sFileUrl"];
$sThumbNumTrue = $aApiResult["aData"]["iTrueNum"];
$oCategories = Categories::where("sSlug", "freenode")->first();
$oArticle = Articles::updateOrCreate(
['sSlug' => $sSlug],
[
'sThumb' => $sThumbPath,
'iCategoryId' => $oCategories->id,
'sTitle' => $sArticleTitle,
'sTitleSub' => $sArticleTitleSub,
'sContent' => $sArticleContent,
'i7like' => $i7like,
'iStatus' => 1,
'iUserId' => 1,
],
);
(new TagService())->syncArticleTagsByName($oArticle, $aArticleTags);
ArticleFreenodeThumbNum::where("author_code", $sImgAuthor)->update(["num" => $sThumbNumTrue]);
$sArticleUrl = config("app.url")."/feenode/".$sSlug;
TeleSlave::notify()->send($sArticleUrl." 发布成功");
return response()->json($oFlow->done());
}
}

View File

@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\v1\Telegram;
//use App\Models\Article;
use App\Services\TomTool\Http;
use App\Services\Tg\Http as TGHttp;
use App\Models\Hook as ModelHook;
use App\Services\TomTool\ThrowableHandler;
//use App\Services\TomTool\TelegramVdb;
use App\Services\TomTool\Telegram\Slave as TelegramSlave;
final class HookController
{
private $sCustomText = '';
public function cache_tg($sGroupCode)
{
try {
$sUpdate = file_get_contents("php://input");
$aUpdate = json_decode($sUpdate, true);
$sText = $aUpdate["message"]["text"] ?? "没有text";
if (!str_starts_with($sText, '/')) {
$this->sCustomText = "/cacheTg#add__{$sGroupCode}"." ".$sUpdate;
}
$this->cmd();
} catch (\Throwable $e) {
$aDataTg = ThrowableHandler::make($e)->enableTrace()->fetch();
TelegramSlave::fail()->enableSlaveQueue(false)->setMsgA([
"msg" => $aDataTg,
"data" => $sUpdate,
])->send();
$aDataCmd = $aDataTg;
$sDataCmd = json_encode($aDataCmd, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$sDataCmd = str_replace("\\n", "<br>", $sDataCmd);
echo $sDataCmd;
exit;
}
}
public function cmd_ffq_article()
{
try {
$sUpdate = file_get_contents("php://input");
$aUpdate = json_decode($sUpdate, true);
$sText = $aUpdate["message"]["text"] ?? "没有text";
if (!str_starts_with($sText, '/')) {
// $this->sCustomCmd = "article";
// $this->sCustomFunc = "cache";
// $this->aCustomOption[1] = "ffq";
$this->sCustomText = "/article#cacheAdd__ffq_tg_a"." ".$sUpdate;
}
$this->cmd();
} catch (\Throwable $e) {
$aDataTg = ThrowableHandler::make($e)->enableTrace()->fetch();
TelegramSlave::fail()->enableSlaveQueue(false)->setMsgA([
"msg" => $aDataTg,
"data" => $sUpdate,
])->send();
$aDataCmd = $aDataTg;
$sDataCmd = json_encode($aDataCmd, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$sDataCmd = str_replace("\\n", "<br>", $sDataCmd);
echo $sDataCmd;
exit;
}
}
public function cmd()
{
try {
$oTGHttp = new TGHttp();
$oTGHttp->sApiToken = env('telegram_token');
$oTGHttp->sApiChatId = env('telegram_chatid');
$sUpdate = file_get_contents("php://input");
$aUpdate = json_decode($sUpdate, true);
// @file_put_contents('/www/_tg/bot_log.txt', json_encode($aUpdate, JSON_PRETTY_PRINT));
$aData['chatId'] = (string) $aUpdate["message"]["chat"]["id"];
$aData['fromId'] = (string) $aUpdate["message"]["from"]["id"];
if ($this->sCustomText) {
$aUpdate["message"]["text"] = $this->sCustomText;
}
if (!isset($aUpdate["message"]["text"])) {
throw new \Exception(json_encode($aUpdate));
}
$sText = $aUpdate["message"]["text"];
// hook优先
// 非is_hook并且首字符非/
if ((!isset($aUpdate["is_hook"]) || $aUpdate["is_hook"] == false) && substr($sText, 0, 1) !== "/") {
$sHookKey = ModelHook::where("tg_user_id", $aData["fromId"])->first()?->key;
if ($sHookKey) {
if ($sText == "run") {
$sText = $sHookKey;
} else {
$sText = $sHookKey.$sText;
}
$aUpdate["is_hook"] = true;
}
}
@$aArg = explode(" ", $sText) ?? [];
@$aOption = explode("__", $aArg[0]) ?? [];
$sCmd = $this->cleanCmd($aOption[0]);
// 大于3个说明是hook或dove之类
$aCmd = explode("#", $sCmd);
// if (count($aCmd) >= 3) {
// $sHook = $aCmd[0];
// $sClass = $sHook;
// $sFunc = "go";
// } else {
$sClass = $aCmd[0] ?? false;
$sFunc = $aCmd[1] ?? false;
// }
if (strpos($sClass, "dove$") !== false) {
$sClass = "dove";
$sFunc = "go";
}
$sUClass = ucfirst($sClass);
$sUFunc = ucfirst($sFunc);
unset($aOption[0]);
unset($aArg[0]);
$aParams = [];
$aParams['aOption'] = $aOption;
$aParams['aArg'] = $aArg;
$sClassPath = "App\\Services\\Tg\\Hook\\Mod\\".$sClass;
$sUClassPath = "App\\Services\\Tg\\Hook\\Mod\\".$sUClass;
$oInstans = new $sUClassPath($aUpdate);
$r = $oInstans->$sFunc($aParams, $aUpdate);
$oTGHttp->send($r, $aUpdate);
echo $r;
} catch (\Throwable $e) {
$aDataTg = ThrowableHandler::make($e)->enableTrace()->fetch();
TelegramSlave::fail()->enableSlaveQueue(false)->setMsgA($aDataTg)->send();
$aDataCmd = $aDataTg;
$aDataCmd["_update"] = $aUpdate;
$sDataCmd = json_encode($aDataCmd, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$sDataCmd = str_replace("\\n", "<br>", $sDataCmd);
echo $sDataCmd;
exit;
// old
// $sMessage = "错误(文件: " . $e->getFile() . ",行: " . $e->getLine() . ": " . $e->getMessage();
// $oTGHttp->send($sMessage, $sUpdate);
// //Http::response(200, 'error'); 注释的
// old end
}
}
private function cleanCmd($input) {
$input = str_replace('\\', '', $input);
$input = str_replace('/', '', $input);
return $input;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TestController extends Controller
{
public function hello()
{
tt("hello");
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Services\ArticlesService;
use Illuminate\Http\Request;
use App\Models\Articles;
class ArticlesController extends Controller
{
protected $oArticlesService;
public function __construct(ArticlesService $oService)
{
$this->oArticlesService = $oService;
}
public function search(Request $oRequest, string $sKeyword)
{
$xData = $this->oArticlesService->search($sKeyword, 'view');
return view('pages.articles.default.index', $xData);
}
public function like(int $iArticleId, Request $oRequest)
{
// 1. 获取/生成指纹
$sFingerprint = $oRequest->cookie('visitor_token')
?? md5($oRequest->ip() . $oRequest->userAgent());
// 2. 调用 Service
$aResult = $this->oArticlesService->like($iArticleId, $oRequest->ip(), $sFingerprint);
if (!$aResult['status']) {
return response()->json($aResult);
}
// 3. 成功后下发持久 Cookie
return response()->json($aResult)
->withCookie(cookie()->forever('visitor_token', $sFingerprint));
}
public function listByTag(Request $oRequest, string $sSlug)
{
$xData = $this->oArticlesService->getTagArticles($sSlug, 'view');
return view('pages.articles.default.index', $xData);
}
public function list(Request $oRequest, string $sSlug1, string $sSlug2 = null)
{
$sSlug = $sSlug2 ?? $sSlug1;
$xData = $this->oArticlesService->getCategoryArticles($sSlug, 'view');
return view('pages.articles.default.index', $xData);
}
public function show(string $sArticleSlug, Request $oRequest)
{
$sFingerprint = $oRequest->cookie('visitor_token')
?? md5($oRequest->ip() . $oRequest->userAgent());
$xData = $this->oArticlesService->getArticle($sArticleSlug, $oRequest->ip(), $sFingerprint);
// $xData["siteUrl"] = config("app.url");
$sViewPath = 'pages.articles.default.show';
//
// if ($xData["oArticle"]->oCategory->sSlug == "freenode") {
// $sViewPath = 'pages.articles.freenode.show';
// }
return view($sViewPath, $xData);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Web;
use App\Models\Tags;
use App\Http\Controllers\Controller;
use Illuminate\View\View;
class TagsController extends Controller
{
public function show(Tags $oTags): View
{
// 高标准:关联查询 + 分页
// 假设你的关联关系叫 articles()
// $aArticles = $oTags->articles()
// ->with(['category', 'user']) // 预加载作者和分类,优雅!
// ->where('iStatus', 1) // 只看发布状态的
// ->orderByDesc('created_at')
// ->paginate(15);
//
// return view('tag.show', [
// 'oTag' => $oTag,
// 'aArticles' => $aArticles
// ]);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class ApiCheckToken
{
public function handle($oRequest, Closure $next)
{
// 获取 Token
$sIncomingToken = $oRequest->bearerToken();
$sLocalToken = (string) env('token_base', false);
// 严丝合缝的校验
if (!$sIncomingToken || $sIncomingToken !== $sLocalToken) {
return response()->json(['msg' => 'token error'], 401);
}
return $next($oRequest);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ArticleFreenodeThumbNum extends Model
{
protected $guarded = [];
public $timestamps = false;
}

23
app/Models/ArticleLikes.php Executable file
View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Articles;
//use Illuminate\Database\Eloquent\Relations\BelongsTo;
//use Illuminate\Database\Eloquent\Relations\HasMany;
class ArticleLikes extends Model
{
protected $guarded = [];
// public $timestamps = false;
// protected $casts = [
// 'aBanner' => 'array',
// ];
public function oArticle()
{
return $this->belongsTo(Articles::class, 'iArticleId', 'id');
}
}

113
app/Models/Articles.php Executable file
View File

@ -0,0 +1,113 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\ResponseCache\Facades\ResponseCache;
use App\Models\Tags;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Laravel\Scout\Searchable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Articles extends Model
{
protected $guarded = [];
protected $appends = ['s_created_at_diff'];
use Searchable;
protected function sCreatedAtDiff(): Attribute
{
return Attribute::get(function () {
// 1. 别直接用 $this->created_at防止它还没被实例化成 Carbon 对象
$oDate = \Carbon\Carbon::parse($this->created_at);
$oNow = \Carbon\Carbon::now();
// 2. 计算小时差(强制转整数)
$iHours = (int)$oDate->diffInHours($oNow);
if ($iHours < 1) {
// 不足 1 小时,看看是不是不足 1 分钟
$iMinutes = (int)$oDate->diffInMinutes($oNow);
return $iMinutes < 1 ? '刚刚' : $iMinutes . '分钟前';
}
if ($iHours < 24) {
return $iHours . '小时前';
}
// 3. 计算天数差(强制转整数)
$iDays = (int)$oDate->diffInDays($oNow);
if ($iDays < 30) {
return $iDays . '天前';
}
// 超过一个月直接显示日期
return $oDate->format('Y-m-d');
});
}
public function shouldBeSearchable(): bool
{
return (int)$this->iStatus === 1;
}
public function searchableAs(): string
{
return 'articles';
}
public function toSearchableArray(): array
{
// 药水哥原则:字段要精准,类型要死磕
return [
'id' => (int) $this->id,
'iCategoryId' => (int) $this->iCategoryId,
'sCategoryName' => (string) ($this->oCategory->sName ?? 'none'),
'sCategorySlug' => (string) ($this->oCategory->sSlug ?? 'none'),
'sTitle' => (string) $this->sTitle,
'sTitleSub' => (string) $this->sTitleSub,
'sSlug' => (string) $this->sSlug,
'sContent' => (string) mb_substr(strip_tags($this->sContent), 0, 500),
'iStatus' => (int) $this->iStatus,
'iIsTop' => (int) $this->iIsTop,
'updated_at' => (int) $this->updated_at->timestamp,
];
}
protected static function booted()
{
static::saved(function () {
ResponseCache::forget('/'); // 要改成具体文章
});
static::deleted(function () {
ResponseCache::forget('/'); // 要改成具体文章
});
}
public function oTags(): BelongsToMany
{
return $this->belongsToMany(
Tags::class,
'article_tag',
'iArticleId',
'iTagId'
);
}
public function oCategory(): BelongsTo {
return $this->belongsTo(Categories::class, 'iCategoryId', 'id');
}
public function oUser()
{
return $this->belongsTo(User::class, 'iUserId', 'id');
}
}

49
app/Models/Categories.php Executable file
View File

@ -0,0 +1,49 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Tag;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Categories extends Model
{
protected $guarded = [];
public $timestamps = false;
protected $casts = [
'aBanner' => 'array',
];
protected $touches = ['oArticles'];
public function aArticles(): HasMany {
return $this->hasMany(Articles::class, 'iCategoryId', 'id');
}
public function aChildren()
{
return $this->hasMany(Categories::class, 'iParentId', 'id')->orderBy('iSort', 'desc');
}
public function oParent()
{
return $this->belongsTo(Categories::class, 'iParentId', 'id');
}
protected static function booted()
{
static::saved(fn() => Cache::forget('global_nav_tree'));
static::deleted(fn() => Cache::forget('global_nav_tree')); // 删除时也要清,这才叫严谨!
}
public static function getGlobalNavTree()
{
return self::where('iParentId', 0)
->where('iStatus', 1)
->with(['aChildren' => fn($q) => $q->where('iStatus', 1)->orderBy('iSort', 'desc')])
->orderBy('iSort', 'desc')
->get();
}
}

43
app/Models/DogOption.php Executable file
View File

@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DogOption extends Model
{
protected $table = 'dog_option';
public $timestamps = false;
public static function _hit($sDogName, $k)
{
$v = self::where("dog_name", $sDogName)->where("k", $k)->first()?->v;
return $v;
}
public static function _list($sDogName)
{
$oSelf = self::where("dog_name", $sDogName)->get();
return self::_filterList($oSelf);
}
public static function _filter($oOption)
{
$arr = [];
$arr[$oOption->dog_name."#".$oOption->k] = $oOption->v;
return $arr;
}
public static function _filterList($oOption)
{
$arr = [];
foreach ($oOption as $oOptionRow) {
$arr[] = self::_filter($oOptionRow);
}
return $arr;
}
}

47
app/Models/Option.php Executable file
View File

@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Option extends Model
{
protected $table = 'option';
public $timestamps = false;
protected $primaryKey = 'k';
public $incrementing = false;
protected $keyType = 'string';
public static function _hit($k)
{
$v = self::where("k", $k)->first()?->v;
return $v;
}
public static function _list()
{
$oSelf = self::get();
return self::_filterList($oSelf);
}
public static function _filter($oOption)
{
$arr = [];
$arr[$oOption->k] = $oOption->v;
return $arr;
}
public static function _filterList($oOption)
{
$arr = [];
foreach ($oOption as $oOptionRow) {
$arr[] = self::_filter($oOptionRow);
}
return $arr;
}
}

26
app/Models/Tags.php Executable file
View File

@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
//use Illuminate\Database\Eloquent\Relations\MorphTo;
//use Illuminate\Database\Eloquent\Relations\MorphMany;
//use Spatie\ResponseCache\Facades\ResponseCache;
use App\Models\Tag;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Tags extends Model
{
protected $guarded = [];
public $timestamps = false;
public function oArticles(): BelongsToMany
{
return $this->belongsToMany(
Articles::class, // 关联的文章模型
'article_tag', // 中间表名
'iTagId', // 中间表中指向当前表(tags)的外键
'iArticleId' // 中间表中指向目标表(articles)的外键
);
}
}

54
app/Models/User.php Executable file
View File

@ -0,0 +1,54 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function aArticles(): HasMany
{
return $this->hasMany(Articles::class, 'iUserId', 'id');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Relations\Relation;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// Relation::morphMap([
// 'Gems' => 'App\Models\Gems',
// 'Articles' => 'App\Models\Articles',
// ]);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Cache;
use App\Services\CacheService;
//use App\Models\Categories;
class ArticleOrderLikesProvider extends ServiceProvider
{
public function boot(): void
{
// 只有渲染 components.order-likes 这个组件时,这段逻辑才会运行
View::composer('components.order-likes', function ($oView) {
// 从缓存拿数据,拿不到就触发一次更新(高标准兜底)
$aHotArticles = Cache::get(CacheService::CACHE_KEYS['article_order_likes']);
if (is_null($aHotArticles)) {
$aHotArticles = CacheService::updateOrderLikes();
}
$oView->with('oOrderLikes', $aHotArticles);
});
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Cache;
use App\Models\Categories;
class NavServiceProvider extends ServiceProvider
{
public function boot(): void
{
// 定义宁要注入数据的视图(可以是具体的 index也可以是布局 layouts.app
View::composer('components.nav-bar', function ($oView) {
// 1. 缓存 1 小时 (3600秒),宁写的 1 秒太凑合了,那是“皇帝的缓存”
$iCacheTime = 86400;
// 2. 优雅地从缓存或模型拿数据
$aNavs = Cache::remember('global_nav_tree', $iCacheTime, function () {
return Categories::getGlobalNavTree();
});
// 3. 把这份艺术品递给视图
$oView->with('aNavs', $aNavs);
});
}
}

277
app/Services/ArticlesService.php Executable file
View File

@ -0,0 +1,277 @@
<?php
namespace App\Services;
use App\Models\Categories;
use App\Models\Articles;
use App\Models\ArticleLikes;
use App\Models\Tags;
use App\Services\CacheService;
class ArticlesService
{
/**
* 第一步文章创建时生成一个“7天点赞计划”
* 规则:第一天占 70%,剩下 6 天分摊 30%,全部为整数
*/
public function generateSevenDayPlan(int $iTargetTotal): string
{
if ($iTargetTotal <= 0) return '[]';
$aPlan = array_fill(0, 7, 0);
// 1. 第一天分配 70% (向上取整,保证第一天绝对有动作)
$iDay1Likes = (int) ceil($iTargetTotal * 0.7);
$aPlan[0] = $iDay1Likes;
// 2. 剩下的 30% 分配给后面 6 天
$iRemaining = $iTargetTotal - $iDay1Likes;
if ($iRemaining > 0) {
// 随机把剩下的点赞塞进后 6 天的坑位里
for ($i = 0; $i < $iRemaining; $i++) {
$iRandomDay = mt_rand(1, 6);
$aPlan[$iRandomDay]++;
}
}
// 返回匈牙利命名法下的 json 字符串
return json_encode($aPlan);
}
/**
* 第二步:执行点赞任务 (建议每小时跑一次)
* 只负责把今天该加的赞加进去
*/
public function executeLikeTask(Article $oArticle): void
{
if (empty($oArticle->sLikePlan)) return;
$aPlan = json_decode($oArticle->sLikePlan, true);
$iDaysPassed = $oArticle->created_at->diffInDays(now());
// 如果已经超过7天或者计划里没这一天直接撤退
if ($iDaysPassed > 6 || !isset($aPlan[$iDaysPassed])) {
return;
}
$iShouldHaveAdded = $aPlan[$iDaysPassed];
// 重点:为了不让点赞在凌晨一瞬间加上去,我们可以配合小时数
// 比如现在是 14 点,我们应该加到今天总量的 14/24
$iHour = (int) date('H');
$iCurrentTarget = (int) floor($iShouldHaveAdded * ($iHour / 24));
// 这里需要一个逻辑记录今天已经加了多少,或者直接根据小时计算增量
// 简单处理:我们每天分 24 次把 $iShouldHaveAdded 加完
// 为保简洁,这里展示核心逻辑更新:
if ($iShouldHaveAdded > 0) {
// 算出每小时平均加几个,剩下的随机掉落
$iAddToday = $this->calculateHourlyIncrement($iShouldHaveAdded, $iHour);
// 这里更新 i7like 字段
// 注意i7like 应该是这篇文章总共通过这个逻辑加了多少赞
// ... 具体的数据库 Update 逻辑
}
}
/**
* 365天自动点赞:高标准随机触发
*/
public function applyYearlyGrowth(Article $oArticle): void
{
$iYearlyTarget = 10; // 哪怕一年只要 10 个
// 计算今天中奖的概率10 / 365
// 我们放大一万倍来摇号
$iProbability = (int)(($iYearlyTarget / 365) * 10000);
$iMagicNumber = mt_rand(1, 10000);
if ($iMagicNumber <= $iProbability) {
// 中奖了,加 1 个赞
$oArticle->increment('i365like');
}
}
public function like(int $iArticleId, string $sIp, string $sFingerprint): array
{
// 1. 检查是否已经点过
$bExists = ArticleLikes::where('iArticleId', $iArticleId)
->where(function ($q) use ($sIp, $sFingerprint) {
$q->where('sIp', $sIp)
->orWhere('sFingerprint', $sFingerprint);
})->exists();
if ($bExists) {
return ['status' => false, 'code' => 200, 'msg' => '感谢支持,点过赞了。'];
}
// 2. 事务扣杀
try {
// 定义一个变量来接收最新的点赞数
$iNewLikeCount = 0;
\DB::transaction(function () use ($iArticleId, $sIp, $sFingerprint, &$iNewLikeCount) {
ArticleLikes::create([
'iArticleId' => $iArticleId,
'sIp' => $sIp,
'sFingerprint' => $sFingerprint
]);
// 执行增加并直接获取最新的模型实例
$oArticle = Articles::where('id', $iArticleId);
$oArticle->increment('iLikeCount');
// 拿到自增后的最新数字
$iNewLikeCount = $oArticle->value('iLikeCount');
});
CacheService::updateOrderLikes();
return [
'status' => true,
'code' => 200,
'msg' => '感谢支持!',
'iLikeCount' => $iNewLikeCount // 药水哥绝杀:把热腾腾的数字发回去
];
} catch (\Exception $e) {
\Log::error("点赞失败: " . $e->getMessage());
return ['status' => false, 'code' => 500, 'msg' => '系统打了个盹,稍后再试。'];
}
}
public function search(string $sKeywords, string $sResultType)
{
if (empty($sKeywords)) {
return [];
}
$sKeywords = strip_tags(trim($sKeywords));
$oArticles = Articles::search($sKeywords)
->options([
'attributesToHighlight' => ['sTitle', 'sContent', 'sCategoryName'],
// 1. 把 attributesToSnippet 改成 attributesToCrop
'attributesToCrop' => ['sContent'],
// 2. 设置裁剪后的长度(单位是字符,按需调整)
'cropLength' => 100,
'highlightPreTag' => '<mark class="highlight">',
'highlightPostTag' => '</mark>',
])
->orderByDesc('iIsTop')
->paginate(config('app.page_limit'));
$oArticles->getCollection()->transform(function ($oArticle) {
$aMeta = $oArticle->scoutMetadata();
$aFormatted = $aMeta['_formatted'] ?? [];
if (!empty($aFormatted)) {
$oArticle->sTitle = $aFormatted['sTitle'] ?? $oArticle->sTitle;
$oArticle->sContent = $aFormatted['sContent'] ?? $oArticle->sContent;
if (isset($aFormatted['sCategoryName'])) {
$oArticle->sCategoryName = $aFormatted['sCategoryName'];
$oArticle->setRelation('oCategory', (object)[
'sName' => $aFormatted['sCategoryName'],
'sSlug' => $oArticle->sCategorySlug ?? ''
]);
}
}
return $oArticle;
});
if ($sResultType === "ajax") {
$sHtml = '';
foreach ($oArticles as $oArticle) {
$sHtml .= view('components.article-item', ['oArticle' => $oArticle])->render();
}
$xResult = $sHtml;
} else {
$xResult = [
'oArticles' => $oArticles,
];
}
return $xResult;
}
public function getTagArticles(string $sSlug, string $sResultType)
{
$oTags = Tags::where('sSlug', $sSlug)->firstOrFail();
$oArticles = $oTags->oArticles()
->where('iStatus', 1) // 只拿已发布的
->with(['oCategory']) // 预加载关联,拒绝 N+1
->orderByDesc('iIsTop') // 置顶优先
->orderByDesc('created_at') // 最新优先
->paginate(config('app.page_limit')); // 必须分页,否则数据多了会爆内存
if ($sResultType === "ajax") {
$sHtml = '';
foreach ($oArticles as $oArticle) {
$sHtml .= view('components.article-item', ['oArticle' => $oArticle])->render();
}
$xResult = $sHtml;
} else {
$xResult = [
'oArticles' => $oArticles,
];
}
return $xResult;
}
public function getCategoryArticles(string $sSlug, string $sResultType)
{
$oCategory = Categories::where('sSlug', $sSlug)->firstOrFail();
if ($oCategory->iShowChildren) {
$aCategoryIds = Categories::where('iParentId', $oCategory->id)
->pluck('id')
->push($oCategory->id)
->toArray();
} else {
$aCategoryIds = [$oCategory->id];
}
$oArticles = Articles::whereIn('iCategoryId', $aCategoryIds)
->with(['oUser', 'oCategory'])
->orderBy('iIsTop', 'desc')
->orderBy('created_at', 'desc')
->simplePaginate(config('app.page_limit'));
if ($sResultType === "ajax") {
$sHtml = '';
foreach ($oArticles as $oArticle) {
$sHtml .= view('components.article-item', ['oArticle' => $oArticle])->render();
}
$aResult = $sHtml;
} else {
$aResult = [
'oCategory' => $oCategory,
'oArticles' => $oArticles,
];
}
return $aResult;
}
public function getArticle(string $sArticleSlug, string $sIp, string $sFingerprint)
{
$oArticle = Articles::where('sSlug', $sArticleSlug)
->where('iStatus', 1)
->with(['oCategory.oParent', 'oTags'])
->firstOrFail();
$oArticle->bIsLiked = ArticleLikes::where('iArticleId', $oArticle->id)
->where(function($q) use ($sIp, $sFingerprint) {
$q->where('sIp', $sIp)
->orWhere('sFingerprint', $sFingerprint);
})->exists();
// return $oArticle;
return [
'oArticle' => $oArticle
];
}
}

68
app/Services/CacheService.php Executable file
View File

@ -0,0 +1,68 @@
<?php
namespace App\Services;
//use App\Models\Categories;
use App\Models\Articles;
use App\Models\ArticleLikes;
//use App\Models\Tags;
use Illuminate\Support\Facades\Cache;
class CacheService
{
public const CACHE_KEYS = [
'article_order_likes' => 'article_order_likes',
];
// public static function updateOrderLikes(): array
// {
// $oStartDate = now()->subDays(30);
//
// $aHotArticles = ArticleLikes::select('iArticleId', \DB::raw('count(*) as iTotalLikes'))
// ->where('created_at', '>=', $oStartDate)
// ->groupBy('iArticleId')
// ->orderBy('iTotalLikes', 'desc')
// ->with(['oArticle' => function($oQuery) {
// $oQuery->where('iStatus', 1);
// }])
// ->limit(10)
// ->get()
// ->filter(fn($oItem) => $oItem->oArticle !== null)
// ->toArray(); // 存入缓存建议转成数组,性能更稳
//
// // 存入缓存,永久保存(直到下一次更新)
// Cache::forever(self::CACHE_KEYS['article_order_likes'], $aHotArticles);
//
// return $aHotArticles;
// }
public static function updateOrderLikes(): array
{
// 1. 获取最近点赞的文章ID流
// 使用 max(id) 或 max(created_at) 来确保每个文章只出现一次,且取的是它最后一次被点赞的时间
$aHotArticles = ArticleLikes::select('iArticleId', \DB::raw('MAX(created_at) as sLatestLikeTime'))
->whereHas('oArticle', function($oQuery) {
$oQuery->where('iStatus', 1); // 只看发布状态的文章
})
->groupBy('iArticleId')
->orderBy('sLatestLikeTime', 'desc') // 按最后一次点赞时间倒序
->limit(10)
->with('oArticle') // 预加载文章详情
->get()
->map(function($oItem) {
// 格式化输出,把文章对象和点赞时间揉在一起
return [
'iArticleId' => $oItem->iArticleId,
'sLatestLikeTime' => $oItem->sLatestLikeTime,
'oArticle' => $oItem->oArticle
];
})
->toArray();
// 2. 存入缓存
Cache::forever(self::CACHE_KEYS['article_order_likes'], $aHotArticles);
return $aHotArticles;
}
}

70
app/Services/TagService.php Executable file
View File

@ -0,0 +1,70 @@
<?php
namespace App\Services;
use App\Models\Articles;
use App\Models\Tags;
use Illuminate\Support\Facades\DB;
use Overtrue\Pinyin\Pinyin;
use Illuminate\Support\Str;
class TagService
{
/**
* 给文章同步中文标签
* @param Article $oArticle 文章对象
* @param array $asTagNames 标签名数组 ['药水哥', '直播', 'Laravel']
*/
public function syncArticleTagsByName(Articles $oArticle, array $asTagNames): void
{
if (empty($asTagNames)) {
$oArticle->oTags()->detach();
return;
}
$oPinyin = new Pinyin();
// 1. 处理标签:不存在则创建
$aTagIds = collect($asTagNames)
->filter()
->unique()
->map(function ($sName) use ($oPinyin) {
// 修正sName作为查找条件
$oTag = Tags::firstOrCreate(
['sName' => trim($sName)],
[
// 生成拼音 Slug例如药水哥 -> yao-shui-ge
'sSlug' => Str::slug($oPinyin->permalink($sName)) . '-' . Str::random(5)
]
);
return $oTag->id;
})
->all();
// 2. 数据库事务处理
DB::transaction(function () use ($oArticle, $aTagIds) {
// 获取变更前的标签ID用于后续精准更新计数
$aOldTagIds = $oArticle->oTags()->pluck('id')->toArray();
// 同步中间表
$oArticle->oTags()->sync($aTagIds);
// 3. 高标准:精准更新受影响标签的 iArticleCount
$aAffectedTagIds = array_unique(array_merge($aOldTagIds, $aTagIds));
if (!empty($aAffectedTagIds)) {
$this->refreshTagCounts($aAffectedTagIds);
}
});
}
/**
* 精准刷新指定标签的文章计数
*/
protected function refreshTagCounts(array $aTagIds): void
{
foreach ($aTagIds as $iTagId) {
$iCount = DB::table('article_tag')->where('iTagId', $iTagId)->count();
Tags::where('id', $iTagId)->update(['iArticleCount' => $iCount]);
}
}
}

1052
app/Services/Tg/Hook/Dog.php Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
<?php
namespace App\Services\Tg\Hook\Mod;
//use App\Services\TomTool\Http;
//use App\Models\Article as ModelArticle;
use App\Services\TomTool\Http;
use App\Services\Tg\Http as TGHttp;
class Base {
public function __construct() {
$this->oHttp = new Http();
$this->oTGHttp = new TGHttp();
// $this->oTGHttp->sApiToken = env("telegram_token");
// $this->oTGHttp->sApiChatId = env("telegram_chatid");
}
public function toJson($aData)
{
return json_encode($aData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
public function eachSendTg($aData)
{
foreach ($aData as $row) {
$this->oTGHttp->sendToTG(json_encode($row, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
$this->oHttp::response(200, 'ok');
}
// protected function cmdGood($sCmd) {
//
// $sCmdNew = "";
// switch ($sCmd) {
// case "user":
// $sCmdNew = "user_name";
// break;
// case "project":
// $sCmdNew = "project_name";
// break;
// case "code":
// $sCmdNew = "sale_code";
// break;
// case "condition":
// $sCmdNew = "cron_condition";
// break;
// default:
// $sCmdNew = $sCmd;
// break;
// }
//
// return $sCmdNew;
//
// }
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Models\Option as ModelOption;
use App\Services\Tg\Hook\Dog;
use Illuminate\Support\Facades\Artisan;
class Console extends Base {
private $oDog;
public function __construct($aUpdate = [])
{
$this->oDog = new Dog(new ModelOption());
// $this->oDog->_setPrimaryKey("k"); // 可选默认id
$this->oDog->_setDogName("console"); // 必须
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
public function call($aParam)
{
$sName = $aParam["aArg"][1] ?? '';
if ($sName == "?") {
return "call [sName] 案例stat:tags-count";
}
if (!$sName) {
return "需要name";
}
$iExitCode = Artisan::call($sName);
$sOutput = Artisan::output();
return $sOutput;
}
public function t()
{
echo 11;
exit;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Services\Tg\Hook\Mod;
use App\Models\Option as ModelOption;
use App\Services\Tg\Hook\Dog;
class Option extends Base {
private $oDog;
public function __construct($aUpdate = [])
{
$this->oDog = new Dog(new ModelOption());
$this->oDog->_setPrimaryKey("k"); // 可选默认id
$this->oDog->_setDogName("option"); // 必须
}
public function __call($sName, $aArg)
{
return $this->oDog->$sName($aArg[0]);
}
// public function Pause($aParam)
// {
// $v = $aParam['aArg'][1];
//
// // 获取第一个匹配的选项
// $oOption = ModelOption::where("type", "pause")->first();
//
// // 检查是否找到了选项
// if ($oOption) {
// $oOption->value = $v;
//
// // 保存并返回结果
// return json_encode($oOption->save());
// }
//
// // 如果没有找到选项,返回一个错误信息
// return json_encode(['error' => 'Option not found']);
// }
//
// public function List()
// {
// $aOptionList = ModelOption::all()->toArray();
//
// return json_encode($aOptionList, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
// }
}

108
app/Services/Tg/Http.php Executable file
View File

@ -0,0 +1,108 @@
<?php
namespace App\Services\Tg;
use App\Services\TomTool\Http as TomHttp;
class Http {
public $sApiToken;
public $sApiChatId;
function __construct()
{
$this->sApiToken = env('telegram_token');
$this->sApiChatId = env('telegram_chatid');
}
public function send($sMsg = "msg空", $aUpdate = [])
{
$bSendTg = true;
$sShell = $aUpdate["shell"] ?? false;
$bIsDove = $aUpdate["is_dove"] ?? false;
if ($sShell == "cmd") {
$bSendTg = false;
}
if ($bIsDove == true) {
$bSendTg = false;
}
if (env("is_loc") == true) {
$bSendTg = false;
}
if (is_array($sMsg)) {
if ($bSendTg) {
foreach ($sMsg as $sMsgRow) {
$this->sendTg($sMsgRow);
}
} else {
echo json_encode($sMsg, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
} else {
if ($bSendTg) {
$this->sendTg($sMsg);
} else {
echo $sMsg;
}
}
if ($bSendTg) {
TomHttp::response(200);
} else {
exit;
}
}
private function sendTg($sMsg)
{
$oHttp = new TomHttp();
$oHttp->sMethod = "post";
$oHttp->sToUrl = "https://api.telegram.org/bot$this->sApiToken/sendMessage";
// $oHttp->bIsJson = true;
$oHttp->aData = [
"chat_id" => $this->sApiChatId,
"text" => $sMsg
];
$oHttp->send();
}
public function sendToTG($sMsg, $aUpdate = [])
{
if (isset($aUpdate["shell"]) && $aUpdate["shell"] == "cmd") {
echo $sMsg;
} else if (isset($aUpdate["is_dove"]) && $aUpdate["is_dove"] == true) {
echo $sMsg;
} else {
if (env("is_loc") == true) {
echo $sMsg;
} else {
$oHttp = new TomHttp();
$oHttp->sMethod = "post";
$oHttp->sToUrl = "https://api.telegram.org/bot$this->sApiToken/sendMessage";
// $oHttp->bIsJson = true;
$oHttp->aData = [
"chat_id" => $this->sApiChatId,
"text" => $sMsg
];
$oHttp->send();
}
}
}
}

425
app/Services/TomTool/Flow.php Executable file
View File

@ -0,0 +1,425 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
use App\Services\TomTool\TelegramVdb;
/**
默认:
name:none
isDone:false
new:
$oResult = Flow::make("name");
快速:
Flow::make("name")->fail("kkkkkkk");
进阶:
Flow::make("name")->noDone()->msg("哟哟哟哟哟哟")->sendTg()->fetch();
业务:
step()
msg()
data()
aData()
jData()
sData()
code()
put()
merge()
toDone()
noDone()
step("start")
action:
reset()
done(?$msg)
fail(?$msg, ?$aParentResult)
sendTg() 伪action
fetch() or get()
fetchAll() or geAll()
set
enableTg();
parent($aParentResult);
with:
withTraceId();
withTimestamp();
withStep();
判断:
::isDone($aResult);
::isFail($aResult);
*/
class Flow
{
protected array $aStep = [];
protected array $aParent = [];
protected bool $isToTg = false;
protected bool $isToTgOne = false;
protected array $aResult = [
'isDone' => false,
];
protected string $sName;
private bool $isChain = false;
public function __construct(string $name = 'none')
{
$this->sName = $name;
$this->aStep[] = "{$this->sName}::start";
}
// new
public static function make(string $name = 'none'): self
{
return new self($name);
}
// new
public static function start($sName = 'none')
{
$oInstance = new self($sName);
$oInstance->isChain = true;
return $oInstance;
}
// option
public function enableTg(bool $flag = true): self
{
$this->isToTg = $flag;
return $this;
}
// option
public function enableTgOne(bool $flag = true): self
{
$this->enableTg(true);
$this->isToTgOne = $flag;
return $this;
}
// option
public function parent(array $aParentResult): self
{
$this->aParent = $aParentResult;
return $this;
}
// data
public function step(string $step): self
{
$this->aStep[] = "{$this->sName}::{$step}";
return $this;
}
// data
public function msg($msg = "msg未设置"): self
{
if (is_array($msg)) {
$msg = json_encode($msg);
}
$this->put('sMsg', $msg);
return $this;
}
// data
public function aData(array $data): self
{
$this->put('aData', $data);
return $this;
}
// data
public function aDataPut($k, $v)
{
$this->aResult["aData"][$k] = $v;
return $this;
}
// data
public function sData(string $data): self
{
$this->put('sData', $data);
return $this;
}
// data
public function code(string $code): self
{
$this->put('sCode', $code);
return $this;
}
// data
public function put(string $key, mixed $value, $sDataKey = ''): self
{
if ($sDataKey) {
$this->aResult[$sDataKey][$key] = $value;
} else {
$this->aResult[$key] = $value;
}
return $this;
}
// data
public function merge(array $data): self
{
foreach ($data as $k => $v) {
$this->put($k, $v);
}
return $this;
}
// data
public function setDone(bool $b = true): self
{
$this->aResult['isDone'] = $b;
return $this;
}
// data
public function setFail(string $step = ''): self
{
if ($step) {
$this->step($step);
}
$this->aResult['isDone'] = false;
return $this;
}
// with
public function withTraceId(string $id = null): self
{
$this->aResult['sTraceId'] = $id ?? uniqid('trace_', true);
return $this;
}
// with
public function withTimestamp(): self
{
$this->aResult['sTimestamp'] = (new \DateTime())->format(\DateTime::ATOM);
return $this;
}
// with
public function withStep(bool $flag = true): self
{
if ($flag) {
$this->withParentStep();
$this->aResult['aStep'] = $this->aStep;
} else {
unset($this->aResult['aStep']);
}
return $this;
}
// action
public function reset(): self
{
$this->aStep = [$this->sName . '::start'];
$this->aParent = [];
$this->isToTg = false;
$this->isToTgOne = false;
$this->aResult = ['isDone' => false];
return $this;
}
// action
public function done(mixed $msg = null)
{
$this->setDone();
if ($msg) {
$this->msg($msg);
}
return $this->get();
}
// action
public function fail(mixed $msg = null, array $aParentResult = [])
{
$this->setFail('fail');
if ($aParentResult) {
$this->aParent = $aParentResult;
}
if ($msg) {
$this->msg($msg);
}
return $this->getFull();
}
// action
// public function sendTg() // 伪装成非set
// {
// $this->enableTgOne();
// return $this;
// }
// action
public function sendTg($sBotCode = 'base', $sChatCode = 'base')
{
}
public function getDataA()
{
return $this->aResult["aData"] ?? [];
}
public function getDataS()
{
return (string) $this->aResult["sData"] ?? '';
}
public function getDataI()
{
return (int) $this->aResult["iData"] ?? 0;
}
public function getDataB()
{
return (bool) $this->aResult["bData"] ?? false;
}
public function getDataX()
{
return $this->aResult["xData"] ?? '';
}
public function setDataA(array $aData): self
{
$this->aData($aData);
return $this;
}
public function setDataB(bool $bData): self
{
$this->aResult["bData"] = $bData;
return $this;
}
public function setDataS(string $sData): self
{
$this->aResult["sData"] = $sData;
return $this;
}
public function setDataI(int $iData): self
{
$this->aResult["iData"] = $iData;
return $this;
}
public function setDataX($xData): self
{
$this->aResult["xData"] = $xData;
return $this;
}
public function getResult(): array
{
return $this->aResult;
}
// action
public function get()
{
if ($this->isToTg) {
$this->_sendTg();
}
if ($this->isToTgOne) {
$this->isToTg = false;
$this->isToTgOne = false;
}
if ($this->isChain) {
return $this;
}
return $this->aResult;
}
// action
public function getFull()
{
$this->withStep();
return $this->get();
}
// action
public function toArray()
{
return $this->aResult;
}
// action
public function toJson($bFormat = true)
{
if ($bFormat === true) {
return json_encode($this->aResult, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
return json_encode($this->aResult);
}
}
// helper
public function isDone()
{
return $this->aResult["isDone"] ?? false;
}
// helper
public function isFail()
{
return !$this->isDone();
}
// helper
public static function isDoneResult(array $aResult): bool
{
return $aResult['isDone'] ?? false;
}
// helper
public static function isFailResult(array $aResult): bool
{
return !self::isDoneResult($aResult);
}
// shell
public function fetch()
{
return $this->get();
}
// shell
public function fetchAll()
{
return $this->getFull();
}
private function _sendTg()
{
TelegramVdb::make()->dataArr($this->aResult)->save();
}
protected function addStepParent(): void
{
$this->aStep[] = $this->aParent;
}
protected function withParentStep(): void
{
if ($this->aParent) {
$this->addStepParent();
}
}
}

93
app/Services/TomTool/Http.php Executable file
View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
class Http
{
public $sMethod = 'get';
public $aData = [];
public $sToUrl; // 必须
public $bIsJson = false;
public function send($sUrl = false): string
{
// 两种传递方式
if ($sUrl) {
$this->sToUrl = $sUrl;
}
// 初始化 cURL 会话
$ch = curl_init();
// 设置请求 URL
curl_setopt($ch, CURLOPT_URL, $this->sToUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回响应而不是输出
// 根据请求方法设置 cURL 选项
switch (strtolower($this->sMethod)) {
case 'post':
curl_setopt($ch, CURLOPT_POST, true); // 设置为 POST 请求
if ($this->bIsJson) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($this->aData)); // 设置 POST 数据为 JSON
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json', // 设置内容类型为 JSON
'Content-Length: ' . strlen(json_encode($this->aData)) // 设置内容长度
]);
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($this->aData)); // 设置 POST 数据为普通表单格式
}
break;
case 'put':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); // 设置为 PUT 请求
if ($this->bIsJson) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($this->aData)); // 设置 PUT 数据为 JSON
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json', // 设置内容类型为 JSON
'Content-Length: ' . strlen(json_encode($this->aData)) // 设置内容长度
]);
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($this->aData)); // 设置 PUT 数据为普通表单格式
}
break;
case 'get':
// 默认使用 GET 请求
curl_setopt($ch, CURLOPT_HTTPGET, true); // 设置为 GET 请求
curl_setopt($ch, CURLOPT_URL, $this->sToUrl . '?' . http_build_query($this->aData)); // 设置请求 URL
if ($this->bIsJson) {
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json' // 设置接受的内容类型为 JSON
]);
}
break;
}
// 禁用 SSL 验证
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 不验证对等证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 不验证主机名
// 执行 cURL 请求
$response = curl_exec($ch);
// 检查是否有错误
if (curl_errno($ch)) {
return 'Error: ' . curl_error($ch);
}
// 关闭 cURL 会话
curl_close($ch);
// 返回响应
return $response;
}
public static function response($iCode, $sMsg = '')
{
http_response_code($iCode);
echo $sMsg;
exit;
}
}

203
app/Services/TomTool/HttpV2.php Executable file
View File

@ -0,0 +1,203 @@
<?php
// 版本25-12-12
declare(strict_types=1);
namespace App\Services\TomTool;
use App\Services\TomTool\Flow;
use Exception;
/**
默认:
method:post
new:
HttpV2::make($url)
基本:
HttpV2::make($url)->send()
进阶:
HttpV2::make($url, "get")->setDataA($aData)->enableJsonAs()->enableAsync()->optMethod("get")->send();
action:
send()
data:
xData()
sData()
jData()
sData()
set:
optMethod()
enableJsonAs()
enableJsonSend()
enableJsonReturn()
@note:
默认关闭 SSL 验证(适用于开发环境),生产环境建议开启
异步模式下不会返回响应内容,仅快速触发请求
*/
class HttpV2
{
protected string $method;
protected $data = [];
protected string $url;
protected bool $sendJson = false;
protected bool $returnJson = false;
protected bool $async = false;
// 匈牙利命名法,仅新增一个 token 变量
protected string $sToken = '';
public static function make(string $url, string $method = 'post'): self
{
$instance = new self();
$instance->url = $url;
$instance->optMethod($method);
return $instance;
}
public function optMethod(string $method): self
{
$this->method = strtolower($method);
return $this;
}
/**
* 新增:支持携带 Token
* 如果不传值,默认从环境变量 token_base 获取
*/
public function withToken(?string $sToken = null): self
{
$this->sToken = $sToken ?? (string)env('token_base', false);
return $this;
}
public function setDataX($xData)
{
$this->setData($xData);
return $this;
}
public function setDataA($aData)
{
$this->setData($aData);
return $this;
}
public function setDataS($sData)
{
$this->setData($sData);
return $this;
}
public function setData($xData)
{
$this->data = $xData;
return $this;
}
public function enableJsonSend(bool $flag = true): self
{
$this->sendJson = $flag;
return $this;
}
public function enableJsonReturn(bool $flag = true): self
{
$this->returnJson = $flag;
return $this;
}
public function enableJsonAs(): self
{
$this->sendJson = true;
$this->returnJson = true;
return $this;
}
public function enableAsync(bool $flag = true): self
{
$this->async = $flag;
return $this;
}
public function send(): string
{
$oFlow = Flow::make("HttpV2::send");
$ch = curl_init();
$headers = [];
// --- 核心嵌入:如果 sToken 有值且不是 false塞进 Header ---
if ($this->sToken && $this->sToken !== 'false') {
$headers[] = 'Authorization: Bearer ' . $this->sToken;
}
// 构建 URL 和请求体 (完全保留原样)
if ($this->method === 'get') {
$url = $this->url . '?' . http_build_query($this->data);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPGET, true);
} else {
curl_setopt($ch, CURLOPT_URL, $this->url);
$payload = $this->sendJson ? json_encode($this->data) : http_build_query($this->data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
if ($this->method === 'post') {
curl_setopt($ch, CURLOPT_POST, true);
} else {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method));
}
if ($this->sendJson) {
$headers[] = 'Content-Type: application/json';
$headers[] = 'Content-Length: ' . strlen($payload);
}
}
if ($this->returnJson) {
$headers[] = 'Accept: application/json';
}
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
// SSL 设置 (完全保留原样)
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 异步发送 (完全保留原样)
if ($this->async) {
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 200);
curl_setopt($ch, CURLOPT_NOSIGNAL, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_exec($ch);
curl_close($ch);
return 'sent';
}
// 阻塞执行 (完全保留原样)
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("cURL Error: {$error}");
}
curl_close($ch);
return $response;
}
}

26
app/Services/TomTool/Map.php Executable file
View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
class Map
{
public static function fnClientToSfx($sClient)
{
$sfx = "";
switch ($sClient) {
case "clash":
$sfx = "yaml";
break;
case "v2ray":
$sfx = "txt";
break;
default:
break;
}
return $sfx;
}
}

View File

@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool\Telegram;
use App\Services\HttpQueue as ServiceHttpQueue;
use App\Services\TomTool\Flow;
use App\Services\TomTool\Telegram\Sdk as ServiceTelegramSdk;
use App\Models\TelegramKey as ModelTelegramKey;
/**
注意是master服务器端专用的这里会对code找mapslave不会。
new:
start(?$sBodeCode, ?$sChatCode)
set:
setBotCode()
setChatCode()
setBotToken()
setChatId()
setMsgS()
action:
send()
*/
final class Master
{
public string $sBotCode = "base";
public string $sChatCode = "base";
public string $sBotToken = '';
public int $iChatId = 0;
public string $sMsg = '';
public string $sPhotoUrl = '';
public int $iPhotoWidth = 0;
public int $iPhotoHeight = 0;
public string $sFileUrl = '';
public string $sFileName = '';
public string $sType = '';
public bool $enableQueue = false; // 应该用不到了
public bool $bKeyBegin = false;
public static function start($sBotCode = 'base', $sChatCode = 'base'): self
{
$oInstance = new self();
$oInstance->sBotCode = $sBotCode;
$oInstance->sChatCode = $sChatCode;
return $oInstance;
}
public function setBotCode(string $sBotCode): self
{
$this->sBotCode = $sBotCode;
return $this;
}
public function setChatCode(string $sChatCode): self
{
$this->sChatCode = $sChatCode;
return $this;
}
public function setBotToken(string $sBotToken): self
{
$this->sBotToken = $sBotToken;
return $this;
}
public function setChatId(int $iChatId): self
{
$this->iChatId = $iChatId;
return $this;
}
public function setMsgS(string $sMsg): self
{
$this->sMsg = $sMsg;
return $this;
}
public function setPhotoUrl(string $sPhotoUrl, int $iPhotoWidth = 0, int $iPhotoHeight = 0): self
{
$this->sPhotoUrl = $sPhotoUrl;
$this->iPhotoWidth = $iPhotoWidth;
$this->iPhotoHeight = $iPhotoHeight;
return $this;
}
public function setType(string $sType): self
{
$this->sType = $sType;
return $this;
}
public function setFileUrl(string $sFileUrl = '', string $sFileName = ''): self
{
$this->sFileUrl = $sFileUrl;
$this->sFileName = $sFileName;
return $this;
}
public function send($sMsg = ''): Flow
{
if ($sMsg) {
$this->sMsg = $sMsg;
}
if (!$this->sMsg) {
// throw new \Exception("未设置msg");
}
$this->keyBegin();
if ($this->enableQueue) {
$aData = $this->buildDataTom();
$oFlow = ServiceHttpQueue::start("telegram_local")->arg($aData)->save();
} else {
$oFlow = ServiceTelegramSdk::start()
->setBotToken($this->sBotToken)
->setChatId($this->iChatId)
->setMsgS($this->sMsg)
->setPhotoUrl($this->sPhotoUrl, $this->iPhotoWidth, $this->iPhotoHeight)
->setFileUrl($this->sFileUrl, $this->sFileName)
->setType($this->sType)
->send();
}
return $oFlow;
}
private function keyBegin()
{
if (!$this->bKeyBegin) {
if (!$this->sBotToken) {
$this->sBotToken = ModelTelegramKey::botTokenByCode($this->sBotCode);
}
if (!$this->iChatId) {
$this->iChatId = ModelTelegramKey::chatIdByCode($this->sChatCode);
}
if (!$this->sBotToken || !$this->iChatId) {
throw new \RuntimeException("botToken或chatId没找到botCode={$this->sBotCode}chatCode={$this->sChatCode}");
}
$this->bKeyBegin = true;
}
}
private function buildDataTom(): array
{
return [
"sBotCode" => $this->sBotCode ?? '',
"sChatCode" => $this->sChatCode ?? '',
"sBotToken" => $this->sBotToken ?? '',
"iChatId" => $this->iChatId ?? '',
"sPhotoUrl" => $this->sPhotoUrl ?? '',
"iPhotoWidth" => $this->iPhotoWidth,
"iPhotoHeight" => $this->iPhotoHeight,
"sFileUrl" => $this->sFileUrl,
"sFileName" => $this->sFileName,
"sMsg" => $this->sMsg ?? '',
"sType" => $this->sType ?? '',
];
}
}

View File

@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool\Telegram;
use Telegram\Bot\Api;
use Telegram\Bot\FileUpload\InputFile;
use Telegram\Bot\Exceptions\TelegramSDKException;
use App\Services\TomTool\Flow;
//use Intervention\Image\Laravel\Facades\Image;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
use App\Services\TomTool\HttpV2;
class Sdk
{
public $sMsgFormat = [];
public $sMsgFormatType = 'none';
public $sBotToken = '';
public $iChatId = 0;
public $sPhotoUrl = '';
public $iPhotoWidth = 0;
public $iPhotoHeight = 0;
public $sFileUrl = '';
public $sFileName = '';
public $sMsg;
public $oApi;
private const ALLOWED_FORMATS = ['none', 'html', 'markdown', 'markdownV2'];
public static function start()
{
$oInstance = new self();
return $oInstance;
}
public function setBotToken($sBotToken)
{
$this->oApi = new Api($sBotToken);
$this->sBotToken = $sBotToken;
return $this;
}
public function setChatId($iChatId)
{
$this->iChatId = $iChatId;
return $this;
}
public function setMsgS($sMsg = '')
{
$this->sMsg = $sMsg;
return $this;
}
public function setPhotoUrl($sUrl = '', $iPhotoWidth = 0, $iPhotoHeight = 0)
{
$this->sPhotoUrl = $sUrl;
$this->iPhotoWidth = $iPhotoWidth;
$this->iPhotoHeight = $iPhotoHeight;
return $this;
}
public function setFileUrl($sFileUrl = '', $sFileName = '')
{
$this->sFileUrl = $sFileUrl;
$this->sFileName = $sFileName;
return $this;
}
public function setType($sType)
{
if ($sType) {
$this->sMsgFormatType = $sType;
}
return $this;
}
public function send()
{
$oFlow = Flow::start("tg_sdk_send");
$this->msgFormat();
// try {
if ($this->sPhotoUrl) {
$r = $this->oApi->sendPhoto($this->sMsgFormat);
} else if ($this->sFileUrl) {
$r = $this->oApi->sendDocument($this->sMsgFormat);
} else {
$r = $this->oApi->sendMessage($this->sMsgFormat);
}
if (!($r && $r->getMessageId())) {
return $oFlow->fail($r);
}
// } catch (TelegramSDKException $e) {
// return $oFlow->fail(['error' => $e->getMessage()]);
// }
return $oFlow->done();
}
public function getMsgFormat()
{
$this->msgFormat();
return $this->sMsgFormat;
}
public function msgFormat()
{
if (!in_array($this->sMsgFormatType, self::ALLOWED_FORMATS, true)) {
throw new \RuntimeException("不支持的格式类型: {$this->sMsgFormatType}");
}
$method = 'msgFormat_' . $this->sMsgFormatType;
$this->sMsgFormat = $this->$method();
if ($this->sPhotoUrl) {
$oManager = new ImageManager(new Driver());
$sTempPath = $_ENV['dir_base'].'public/upload';
$sTempPath .= "/tg_output.png";
$sImg = file_get_contents($this->sPhotoUrl);
$oImage = $oManager->read($sImg);
if ($this->iPhotoWidth && $this->iPhotoHeight) {
$oImage = $oImage->cover($this->iPhotoWidth, $this->iPhotoHeight);
}
$oImage->toJpg(80)->save($sTempPath);
$photo = InputFile::create($sTempPath, date("Y-m-d").'.jpg');
$this->sMsgFormat["photo"] = $photo;
$this->sMsgFormat["caption"] = $this->sMsgFormat["text"];
unset($this->sMsgFormat["text"]);
} else if ($this->sFileUrl) {
if (!$this->sFileName) {
$sFileName = date("Y-m-d H:i:s").'.txt';
} else {
$sFileName = $this->sFileName;
}
$sFile = InputFile::create($this->sFileUrl, $sFileName);
$this->sMsgFormat["document"] = $sFile;
$this->sMsgFormat["caption"] = $this->sMsgFormat["text"];
unset($this->sMsgFormat["text"]);
}
return $this;
}
private function msgFormat_none()
{
return [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => '',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
private function msgFormat_html()
{
return [
'chat_id' => $this->iChatId,
'text' => strip_tags($this->sMsg, [
'b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del', 'span', 'tg-spoiler', 'a', 'tg-emoji',
'code', 'pre',
]),
'parse_mode' => 'HTML',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
private function msgFormat_markdown()
{
return [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => 'Markdown',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
private function msgFormat_markdownV2()
{
return [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => 'MarkdownV2',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
}

View File

@ -0,0 +1,361 @@
<?php
// 版本 25/12/12
declare(strict_types=1);
namespace App\Services\TomTool\Telegram;
use App\Services\TomTool\HttpV2;
use App\Services\TomTool\Telegram\Sdk as ServiceTelegramSdk;
use App\Services\TomTool\Flow;
use App\Services\HttpQueue as ServiceHttpQueue;
/**
提醒:
master自身已经没有队列了收到slave消息直接转发
默认:
走本地队列发送到master
走异步
new:
start()
log()
warn()
fail()
notify()
opt:
enableMaster(:true)
enableMasterQueue(:true)
enableSlaveQueue(:true)
enableDetail(:true)
set:
setMsgS()
setMsgA()
setBotCode()
setChatCode()
setBotToken()
setChatId()
action:
send()
sendMaster()
sendLocal()
*/
class Slave
{
public string $sBotCode;
public string $sChatCode;
public string $sBotToken = '';
public int $iChatId = 0;
public string $sMsg = '';
public string $sPhotoUrl = '';
public int $iPhotoWidth = 0;
public int $iPhotoHeight = 0;
public string $sFileUrl = '';
public string $sFileName = '';
public string $sType = '';
public bool $enableMaster = true;
public bool $enableMasterQueue = false; // 这里之前想错了不会有master队列只在slave有master转发就行了
public bool $enableSlaveQueue = false;
public bool $enableDetail = true;
public bool $enableAsync = false;
private bool $enableSend = true;
// new
public static function start(string $sBotCode = 'base', string $sChatCode = 'base'): self
{
$oInstance = new self;
$oInstance->sBotCode = $sBotCode;
$oInstance->sChatCode = $sChatCode;
$oInstance->enableSend = filter_var($_ENV["tg_send_enable"] ?? true, FILTER_VALIDATE_BOOLEAN);
return $oInstance;
}
// new
public static function log(string $sChatCode = 'admin'): self
{
return self::start("log", $sChatCode);
}
// new
public static function warn(string $sChatCode = 'admin'): self
{
return self::start("warn", $sChatCode);
}
// new
public static function fail(string $sChatCode = 'admin'): self
{
return self::start("fail", $sChatCode);
}
// new
public static function notify(string $sChatCode = 'admin'): self
{
return self::start("fail", $sChatCode);
}
// opt
public function enableAsync(bool $b = true): self
{
$this->enableAsync = $b;
return $this;
}
// opt
public function enableDetail(bool $b = true): self
{
$this->enableDetail = $b;
return $this;
}
// opt
public function enableSlaveQueue(bool $b = true): self
{
$this->enableSlaveQueue = $b;
return $this;
}
// opt
public function enableMaster(bool $b = true): self
{
$this->enableMaster = $b;
return $this;
}
// opt
public function enableMasterQueue(bool $b = true): self
{
$this->enableMasterQueue = $b;
return $this;
}
// set
public function setPhotoUrl(string $sPhotoUrl, int $iWidth = 0, int $iHeight = 0): self
{
$this->sPhotoUrl = $sPhotoUrl;
$this->iPhotoWidth = $iWidth;
$this->iPhotoHeight = $iHeight;
return $this;
}
// set
public function setFileUrl(string $sFileUrl, string $sFileName = ''): self
{
$this->sFileUrl = $sFileUrl;
$this->sFileName = $sFileName;
return $this;
}
// set
public function setType(string $sType): self
{
$this->sType = $sType;
return $this;
}
// set
public function setMsgS(string $sMsg = ''): self
{
if ($this->enableDetail) {
$sMsg = "[ ".$this->getEnvSiteCode()." | ".date("d:H:i:s")." ] ".$sMsg;
}
$this->sMsg = $sMsg;
return $this;
}
// set
public function setMsgA(array $aMsg = []): self
{
$aMsgNew = $aMsg;
if ($this->enableDetail) {
$aMsgNew = [];
$aMsgNew["site_code"] = $this->getEnvSiteCode();
$aMsgNew["date"] = date("Y-m-d H:i:s");
$aMsgNew["data"] = $aMsg;
}
$this->sMsg = json_encode($aMsgNew, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
return $this;
}
// set
public function setBotCode(string $sBotCode): self
{
$this->sBotCode = $sBotCode;
return $this;
}
// set
public function setChatCode(string $sChatCode): self
{
$this->sChatCode = $sChatCode;
return $this;
}
// set
public function setBotToken(string $sBotToken): self
{
$this->sBotToken = $sBotToken;
return $this;
}
// set
public function setChatId(int $iChatId): self
{
$this->iChatId = $iChatId;
return $this;
}
// action
public function send($xMsg = ''): Flow
{
if ($this->enableSend == false) {
$oFlow = Flow::start();
return $oFlow->done("不发送");
}
if ($xMsg) {
if (is_array($xMsg)) {
$this->setMsgA($xMsg);
} else {
$this->setMsgS($xMsg);
}
}
if (!$this->sMsg) {
// throw new \Exception("未设置msg");
}
if ($this->enableMaster) {
$oFlow = $this->_send_master();
} else {
$oFlow = $this->_send_local();
}
return $oFlow;
}
// action
public function sendMaster($xMsg = ''): Flow
{
$this->enableMaster(true);
return $this->send($xMsg);
}
// action
public function sendLocal($xMsg = ''): Flow
{
$this->enableMaster(false);
return $this->send($xMsg);
}
private function _send_master(): Flow
{
$aData = $this->buildDataTom();
if ($this->enableSlaveQueue) {
$oFlow = ServiceHttpQueue::start("telegram_master")->arg($aData)->save();
} else {
$oFlow = Flow::start("_send_master");
$sUrlMaSterBase = $_ENV["url_master_base"] ?? "";
if (!$sUrlMaSterBase) {
throw new \Exception("未设置env的url_master_base");
}
$sUrlMasterPost = "https://".$sUrlMaSterBase."/api/telegram/post";
try {
$oHttpV2 = HttpV2::make($sUrlMasterPost);
if ($this->enableAsync) {
$oHttpV2 = $oHttpV2->enableAsync();
}
$r = $oHttpV2->enableJsonAs()->setData($aData)->send();
if ($this->enableAsync) {
return $oFlow->sData("async")->done("async");
} else {
$a = json_decode($r, true);
$isDone = $a["isDone"] ?? false;
if ($isDone) {
return $oFlow->done();
} else {
return $oFlow->fail($r);
}
}
} catch (\Throwable $e) {
$oFlow = $oFlow->fail($e->getMessage());
}
}
return $oFlow;
}
// 直接从本地发到tg几乎用不上都走master了
private function _send_local(): Flow
{
if ($this->enableSlaveQueue) {
$aData = $this->buildDataTom();
$oFlow = ServiceHttpQueue::start("telegram_local")->arg($aData)->save();
} else {
$oFlow = ServiceTelegramSdk::start($this->sBotToken, $this->iChatId)->setMsgS($this->sMsg)->send();
}
return $oFlow;
}
private function buildDataTom(): array
{
return [
"sBotCode" => $this->sBotCode ?? '',
"sChatCode" => $this->sChatCode ?? '',
"sBotToken" => $this->sBotToken ?? '',
"iChatId" => $this->iChatId ?? '',
"enableMasterQueue" => $this->enableMasterQueue ?? '',
"enableMaster" => $this->enableMaster,
"sMsg" => $this->sMsg ?? '',
"sPhotoUrl" => $this->sPhotoUrl ?? '',
"iPhotoWidth" => $this->iPhotoWidth ?? 0,
"iPhotoHeight" => $this->iPhotoHeight ?? 0,
"sType" => $this->sType ?? '',
"sFileUrl" => $this->sFileUrl ?? '',
"sFileName" => $this->sFileName ?? '',
];
}
private function getEnvSiteCode(): string
{
$sEnvSiteCode = $_ENV["site_code"] ?? "未设置env的site_code";
return $sEnvSiteCode;
}
}

View File

@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace App\Services\IM;
use App\Models\Config;
use Telegram\Bot\Api;
use Telegram\Bot\Exceptions\TelegramSDKException;
use function strip_tags;
// 没有必须默认使用base配置
final class TelegramV2
{
private Api $bot;
public $iTo;
public $sToken;
public $sMsgType = false;
public function __construct($sToken = "base", $sTo = "base")
{
$this->setToken($sToken);
$this->setTo($sTo);
// $this->bot = new Api($this->sToken);
}
public function setMsgType($sMsgType)
{
$this->sMsgType = $sMsgType;
}
public function setTo($xTo, $sType = "code")
{
$iChatId = $xTo;
if ($sType == "code") {
// $sEnvKey = "tgChat_".$xTo;
$iChatId = $_ENV['tgChat'][$iChatId];
}
$this->iTo = $iChatId;
}
public function setToken($xToken, $sType = "code")
{
$sToken = $xToken;
if ($sType == "code") {
// $sEnvKey = "tgToken_".$xToken;
$sToken = $_ENV['tgToken'][$sToken];
}
$this->sToken = $sToken;
$this->bot = new Api($this->sToken);
}
public function send($sMsg, $sMsgType = 'str')
{
if ($sMsgType == "arr") {
$sMsg = json_encode($sMsg, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
if ($this->sMsgType === false) {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => $sMsg,
'parse_mode' => '',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
} else if ($this->sMsgType === "html") {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => strip_tags(
$sMsg,
['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike','del', 'span','tg-spoiler', 'a', 'tg-emoji',
'code', 'pre',
]
),
'parse_mode' => 'HTML',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
} else if ($this->sMsgType === "markdown") {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => $sMsg,
'parse_mode' => 'Markdown',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
} else if ($this->sMsgType === "markdownV2") {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => $sMsg,
'parse_mode' => 'MarkdownV2',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
} else {
$sendMessage = [
'chat_id' => $this->iTo,
'text' => $sMsg,
'parse_mode' => '',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
}
return $this->bot->sendMessage($sendMessage);
}
}

View File

@ -0,0 +1,362 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
use App\Services\HttpQueue as ServiceHttpQueue;
use App\Services\TomTool\Flow;
use App\Models\TelegramKey as ModelTelegramKey;
use Telegram\Bot\Api;
use Telegram\Bot\Exceptions\TelegramSDKException;
use function strip_tags;
/**
更新:
data会save清空不再属于set
new:
TelegramVdb::make(?$botCode, ?$chatCode)
快速:
TelegramVdb::make()->save($sData)
进阶:
TelegramVdb::make()->dataArr($aData)->setFormat("html")->save();
TelegramVdb::make()->setFormat()->data($xData)->toJson()->save();
data
data[Arr/Str/Json]($data)
data($aData)->toJson()
action:
save()
set:
setChatCode($code)
setBotCode($code)
setChatId($id) 覆盖code
setBotToken($token) 覆盖code
setFormat($type)
enableDate()
*/
final class TelegramVdb
{
public $sBotCode;
public $sChatCode;
public $sBotToken = null;
public $iChatId = null;
public $sMsg = '';
public $sMsgFormat = '';
public $sMsgFormatType = 'none';
public $bEnableDate = false;
private $bKeyBegin = false;
public $sClient = "master";
private const ALLOWED_FORMATS = ['none', 'html', 'markdown', 'markdownV2'];
public static function log()
{
return self::make("log", "admin");
}
public static function warn()
{
return self::make("warn", "admin");
}
public static function fail()
{
return self::make("fail", "admin");
}
public static function notify()
{
return self::make("notify", "admin");
}
public static function make($sBotCode = "base", $sChatCode = "base")
{
$oInstance = new self();
$oInstance->sBotCode = $sBotCode;
$oInstance->sChatCode = $sChatCode;
return $oInstance;
}
public function send($sMsg = '')
{
return $this->_ok($sMsg, true);
}
public function save($sMsg = '')
{
return $this->_ok($sMsg);
}
private function sendNow()
{
$oResult = Flow::make("send");
$oBot = new Api($this->sBotToken);
$r = $oBot->sendMessage($this->sMsgFormat);
if ($r->isOk()){
return $oResult->done();
} else {
return $oResult->fail("send失败");
}
}
private function HttpQueueSave()
{
$aTelegramArg = [
"bot_token" => $this->sBotToken,
"chat_id" => $this->iChatId,
"msg" => $this->sMsgFormat,
];
$aReturn = ServiceHttpQueue::start("telegram")->arg($aTelegramArg)->save()->getResult();
return $aReturn;
}
public function _ok($sMsg = '', $bSendNow = false)
{
$bTgSendEnable = $_ENV["tg_send_enable"] ?? false;
if (!$bTgSendEnable) {
return false;
}
$oResult = Flow::make("save");
$this->keyBegin();
if ($sMsg) {
if (is_array($sMsg)) {
$this->aData($sMsg);
} else {
$this->sData($sMsg);
}
}
$this->msgFormat();
if ($bSendNow) {
$aReturn = $this->sendNow();
} else {
$aReturn = $this->HttpQueueSave();
}
$this->sMsg = '';
return $aReturn;
}
public function enableDate($b = true)
{
$this->bEnableDate = $b;
return $this;
}
public function data($xData)
{
$this->sMsg = $xData;
return $this;
}
public function jData($sMsg = '')
{
$this->dataJson($sMsg);
return $this;
}
public function aData($sMsg = [])
{
$this->dataArr($sMsg);
return $this;
}
public function sData($sMsg = '')
{
$this->dataStr($sMsg);
return $this;
}
public function toJson()
{
$this->withDateArr();
$this->sMsg = json_encode($this->sMsg, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return $this;
}
public function dataStr($sMsg = '')
{
$this->withDateStr();
$this->sMsg = $sMsg;
return $this;
}
public function dataArr($sMsg = [])
{
$this->withDateArr();
$this->sMsg = json_encode($sMsg, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return $this;
}
public function dataJson($sMsg = '')
{
$this->dataStr($sMsg);
return $this;
}
private function withDateArr()
{
if ($this->bEnableDate) {
$this->sMsg["date"] = date("Y-m-d H:i:s");
}
}
private function withDateStr()
{
if ($this->bEnableDate) {
$this->sMsg = '['.date("Y-m-d H:i:s").'] '.$this->sMsg;
}
}
public function setFormat($sMsgFormatType = "none")
{
$this->sMsgFormatType = $sMsgFormatType;
return $this;
}
public function setBotCode($sBotCode)
{
$this->sBotCode = $sBotCode;
$this->bKeyBegin = false;
return $this;
}
public function setChatCode($sChatCode)
{
$this->sChatCode = $sChatCode;
$this->bKeyBegin = false;
return $this;
}
public function setBotToken($sBotToken)
{
$this->sBotToken = $sBotToken;
return $this;
}
public function setChatId($iChatId)
{
$this->iChatId = $iChatId;
return $this;
}
private function keyBegin()
{
if (!$this->bKeyBegin) {
if (!$this->sBotToken) {
$this->sBotToken = $this->botTokenByCode();
}
if (!$this->iChatId) {
$this->iChatId = $this->chatIdByCode();
}
if (!$this->sBotToken || !$this->iChatId) {
throw new \RuntimeException("botToken或chatId没找到botCode={$this->sBotCode}chatCode={$this->sChatCode}");
}
$this->bKeyBegin = true;
}
return $this;
}
private function msgFormat()
{
if (!in_array($this->sMsgFormatType, self::ALLOWED_FORMATS, true)) {
throw new \RuntimeException("不支持的格式类型: {$this->sMsgFormatType}");
}
$method = 'msgFormat_' . $this->sMsgFormatType;
$this->$method();
return $this;
}
private function botTokenByCode()
{
return ModelTelegramKey::botTokenByCode($this->sBotCode);
}
private function chatIdByCode()
{
return ModelTelegramKey::chatIdByCode($this->sChatCode);
}
private function msgFormat_none()
{
$this->sMsgFormat = [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => '',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
return $this;
}
private function msgFormat_html()
{
$this->sMsgFormat = [
'chat_id' => $this->iChatId,
'text' => strip_tags($this->sMsg, [
'b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del', 'span', 'tg-spoiler', 'a', 'tg-emoji',
'code', 'pre',
]),
'parse_mode' => 'HTML',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
return $this;
}
private function msgFormat_markdown()
{
$this->sMsgFormat = [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => 'Markdown',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
return $this;
}
private function msgFormat_markdownV2()
{
$this->sMsgFormat = [
'chat_id' => $this->iChatId,
'text' => $this->sMsg,
'parse_mode' => 'MarkdownV2',
'disable_web_page_preview' => false,
'reply_to_message_id' => null,
'reply_markup' => null,
];
return $this;
}
}

View File

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
use Throwable as ThrowableBase;
use Illuminate\Database\QueryException;
use App\Services\TomTool\TelegramVdb;
/**
new:
$oThrowableHandler = ThrowableHandler::make($e);
快速:
$aResult = ThrowableHandler::make($e)->toArr();
action
fetch() or get()
set:
enableTg()
enableTrace()
setMaxDepth()
*/
class ThrowableHandler
{
protected bool $tgEnable = false;
protected bool $showTrace = false;
protected int $maxDepth = 3;
protected ThrowableBase $e;
public static function make(ThrowableBase $e): self
{
$oInstance = new self();
$oInstance->e = $e;
return $oInstance;
}
public function fetch()
{
return $this->get();
}
public function get(): array
{
$data = $this->buildExceptionData($this->e, 0);
if ($this->tgEnable) {
$this->sendTelegram($data);
}
return $data;
}
public function toArr(): array
{
return $this->get();
}
public function toJson(): string
{
return json_encode($this->get(), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
public function enableTg(bool $enable = true): self
{
$this->tgEnable = $enable;
return $this;
}
public function enableTrace(bool $enable = true): self
{
$this->showTrace = $enable;
return $this;
}
public function setMaxDepth(int $depth): self
{
$this->maxDepth = max(1, $depth);
return $this;
}
protected function buildExceptionData(ThrowableBase $e, int $depth): array
{
$data = [
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
];
$code = $e->getCode();
if ($code !== null && $code !== 0) {
$data['code'] = $code;
}
if ($this->showTrace) {
$filteredTrace = array_filter($e->getTrace(), function ($frame) {
return empty($frame['file']) || !str_contains($frame['file'], '/vendor/');
});
$traceLines = [];
foreach ($filteredTrace as $frame) {
$file = $frame['file'] ?? '[internal]';
$line = $frame['line'] ?? '';
$func = isset($frame['class'], $frame['function'])
? "{$frame['class']}{$frame['type']}{$frame['function']}"
: ($frame['function'] ?? '[unknown]');
$traceLines[] = "{$file}:{$line} {$func}()";
}
$data['trace'] = implode("\n", $traceLines);
}
if ($depth < $this->maxDepth && $e->getPrevious() instanceof ThrowableBase) {
$data['previous'] = $this->buildExceptionData($e->getPrevious(), $depth + 1);
}
return $data;
}
protected function sendTelegram(array $data): void
{
TelegramVdb::make()->aData($data)->save();
}
}

21
app/Services/TomTool/Tom.php Executable file
View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Services\TomTool;
class Tom
{
public static function toJson($aData)
{
return json_encode($aData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
public static function toJsonBr($aData)
{
$sData = self::toJson($aData);
$sData = str_replace("\\n", "<br>", $sData);
return $sData;
}
}

18
artisan Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);

25
bootstrap/app.php Executable file
View File

@ -0,0 +1,25 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->prepend(\Spatie\ResponseCache\Middlewares\CacheResponse::class);
$middleware->alias([
'doCache' => \Spatie\ResponseCache\Middlewares\CacheResponse::class,
]);
$middleware->validateCsrfTokens(except: [
'api/*',
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();

2
bootstrap/cache/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

7
bootstrap/providers.php Executable file
View File

@ -0,0 +1,7 @@
<?php
return [
App\Providers\AppServiceProvider::class,
App\Providers\NavServiceProvider::class,
App\Providers\ArticleOrderLikesProvider::class,
];

91
composer.json Executable file
View File

@ -0,0 +1,91 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"http-interop/http-factory-guzzle": "^1.2",
"laravel/framework": "^12.0",
"laravel/scout": "^10.23",
"laravel/tinker": "^2.10.1",
"meilisearch/meilisearch-php": "^1.16",
"overtrue/pinyin": "~5.0",
"spatie/laravel-responsecache": "^7.7"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^11.5.3"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

8969
composer.lock generated Executable file

File diff suppressed because it is too large Load Diff

123
config/app.php Executable file
View File

@ -0,0 +1,123 @@
<?php
return [
'is_loc' => env('is_loc', false),
'page_limit' => env('page_limit', 10),
'name' => env('APP_NAME', '网站名字'),
'site_code' => env('site_code', 'site_code'),
'site_code_short' => env('site_code_short', 'site_code_short'),
'title' => env('app_title', '网站标题'),
'keywords' => env('app_keywords', '关键词'),
'desc' => env('app_desc', '页面简介'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
),
],
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];

115
config/auth.php Executable file
View File

@ -0,0 +1,115 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the number of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];

117
config/cache.php Executable file
View File

@ -0,0 +1,117 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'database'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane",
| "failover", "null"
|
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
];

8
config/constants.php Executable file
View File

@ -0,0 +1,8 @@
<?php
return [
'cache_ttl' => [
'one_week' => 60 * 60 * 24 * 7,
'one_day' => 60 * 60 * 24,
],
];

183
config/database.php Executable file
View File

@ -0,0 +1,183 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => env('DB_SSLMODE', 'prefer'),
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
],
];

96
config/filesystems.php Executable file
View File

@ -0,0 +1,96 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => rtrim(env('APP_URL'), '/').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
'upload' => [
'driver' => 'local',
'root' => public_path('upload'),
'url' => env('APP_URL').'/upload',
'visibility' => 'public',
'throw' => false,
],
'upload_images' => [
'driver' => 'local',
'root' => public_path('upload/images'),
'url' => env('APP_URL').'/upload/images',
'visibility' => 'public',
'throw' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

132
config/logging.php Executable file
View File

@ -0,0 +1,132 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'formatter' => env('LOG_STDERR_FORMATTER'),
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

118
config/mail.php Executable file
View File

@ -0,0 +1,118 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
'retry_after' => 60,
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
];

8
config/path.php Executable file
View File

@ -0,0 +1,8 @@
<?php
return [
'url_master_base' => env('url_master_base'),
'url_resource_base' => env('url_resource_base'),
];

129
config/queue.php Executable file
View File

@ -0,0 +1,129 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
| "deferred", "background", "failover", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
'deferred' => [
'driver' => 'deferred',
],
'background' => [
'driver' => 'background',
],
'failover' => [
'driver' => 'failover',
'connections' => [
'database',
'deferred',
],
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

97
config/responsecache.php Executable file
View File

@ -0,0 +1,97 @@
<?php
return [
/*
* Determine if the response cache middleware should be enabled.
*/
'enabled' => env('RESPONSE_CACHE_ENABLED', true),
/*
* The given class will determinate if a request should be cached. The
* default class will cache all successful GET-requests.
*
* You can provide your own class given that it implements the
* CacheProfile interface.
*/
'cache_profile' => Spatie\ResponseCache\CacheProfiles\CacheAllSuccessfulGetRequests::class,
// 'cache_profile' => Spatie\ResponseCache\CacheProfiles\CacheAllRequests::class,
// 'cache_profile' => \App\Support\CustomCacheProfile::class,
//'cache_profile' => \App\Support\BrutalCacheProfile::class,
/*
* Optionally, you can specify a header that will force a cache bypass.
* This can be useful to monitor the performance of your application.
*/
'cache_bypass_header' => [
'name' => env('CACHE_BYPASS_HEADER_NAME', null),
'value' => env('CACHE_BYPASS_HEADER_VALUE', null),
],
/*
* When using the default CacheRequestFilter this setting controls the
* default number of seconds responses must be cached.
*/
'cache_lifetime_in_seconds' => (int) env('RESPONSE_CACHE_LIFETIME', 60 * 60 * 24 * 7),
/*
* This setting determines if a http header named with the cache time
* should be added to a cached response. This can be handy when
* debugging.
*/
'add_cache_time_header' => true,
/*
* This setting determines the name of the http header that contains
* the time at which the response was cached
*/
'cache_time_header_name' => env('RESPONSE_CACHE_HEADER_NAME', 'laravel-responsecache'),
/*
* This setting determines if a http header named with the cache age
* should be added to a cached response. This can be handy when
* debugging.
* ONLY works when "add_cache_time_header" is also active!
*/
'add_cache_age_header' => true,
/*
* This setting determines the name of the http header that contains
* the age of cache
*/
'cache_age_header_name' => env('RESPONSE_CACHE_AGE_HEADER_NAME', 'laravel-responsecache-age'),
/*
* Here you may define the cache store that should be used to store
* requests. This can be the name of any store that is
* configured in app/config/cache.php
*/
'cache_store' => 'file',
/*
* Here you may define replacers that dynamically replace content from the response.
* Each replacer must implement the Replacer interface.
*/
'replacers' => [
\Spatie\ResponseCache\Replacers\CsrfTokenReplacer::class,
],
/*
* If the cache driver you configured supports tags, you may specify a tag name
* here. All responses will be tagged. When clearing the responsecache only
* items with that tag will be flushed.
*
* You may use a string or an array here.
*/
'cache_tag' => '',
/*
* This class is responsible for generating a hash for a request. This hash
* is used to look up a cached response.
*/
'hasher' => \Spatie\ResponseCache\Hasher\DefaultHasher::class,
/*
* This class is responsible for serializing responses.
*/
'serializer' => \Spatie\ResponseCache\Serializers\DefaultSerializer::class,
];

218
config/scout.php Executable file
View File

@ -0,0 +1,218 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Search Engine
|--------------------------------------------------------------------------
|
| This option controls the default search connection that gets used while
| using Laravel Scout. This connection is used when syncing all models
| to the search service. You should adjust this based on your needs.
|
| Supported: "algolia", "meilisearch", "typesense",
| "database", "collection", "null"
|
*/
'driver' => env('SCOUT_DRIVER', 'collection'),
/*
|--------------------------------------------------------------------------
| Index Prefix
|--------------------------------------------------------------------------
|
| Here you may specify a prefix that will be applied to all search index
| names used by Scout. This prefix may be useful if you have multiple
| "tenants" or applications sharing the same search infrastructure.
|
*/
'prefix' => env('SCOUT_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Queue Data Syncing
|--------------------------------------------------------------------------
|
| This option allows you to control if the operations that sync your data
| with your search engines are queued. When this is set to "true" then
| all automatic data syncing will get queued for better performance.
|
*/
'queue' => env('SCOUT_QUEUE', false),
/*
|--------------------------------------------------------------------------
| Database Transactions
|--------------------------------------------------------------------------
|
| This configuration option determines if your data will only be synced
| with your search indexes after every open database transaction has
| been committed, thus preventing any discarded data from syncing.
|
*/
'after_commit' => false,
/*
|--------------------------------------------------------------------------
| Chunk Sizes
|--------------------------------------------------------------------------
|
| These options allow you to control the maximum chunk size when you are
| mass importing data into the search engine. This allows you to fine
| tune each of these chunk sizes based on the power of the servers.
|
*/
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
/*
|--------------------------------------------------------------------------
| Soft Deletes
|--------------------------------------------------------------------------
|
| This option allows to control whether to keep soft deleted records in
| the search indexes. Maintaining soft deleted records can be useful
| if your application still needs to search for the records later.
|
*/
'soft_delete' => false,
/*
|--------------------------------------------------------------------------
| Identify User
|--------------------------------------------------------------------------
|
| This option allows you to control whether to notify the search engine
| of the user performing the search. This is sometimes useful if the
| engine supports any analytics based on this application's users.
|
| Supported engines: "algolia"
|
*/
'identify' => env('SCOUT_IDENTIFY', false),
/*
|--------------------------------------------------------------------------
| Algolia Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Algolia settings. Algolia is a cloud hosted
| search engine which works great with Scout out of the box. Just plug
| in your application ID and admin API key to get started searching.
|
*/
'algolia' => [
'id' => env('ALGOLIA_APP_ID', ''),
'secret' => env('ALGOLIA_SECRET', ''),
'index-settings' => [
// 'users' => [
// 'searchableAttributes' => ['id', 'name', 'email'],
// 'attributesForFaceting'=> ['filterOnly(email)'],
// ],
],
],
/*
|--------------------------------------------------------------------------
| Meilisearch Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Meilisearch settings. Meilisearch is an open
| source search engine with minimal configuration. Below, you can state
| the host and key information for your own Meilisearch installation.
|
| See: https://www.meilisearch.com/docs/learn/configuration/instance_options#all-instance-options
|
*/
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY'),
'index-settings' => [
'articles' => [
'searchableAttributes' => [
'sTitle',
'sTitleSub',
'sCategoryName',
'sContent'
],
'filterableAttributes' => ['iStatus', 'iCategoryId', 'iIsTop'],
'sortableAttributes' => ['updated_at', 'iIsTop'],
'displayedAttributes' => ['*'],
],
],
],
/*
|--------------------------------------------------------------------------
| Typesense Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Typesense settings. Typesense is an open
| source search engine using minimal configuration. Below, you will
| state the host, key, and schema configuration for the instance.
|
*/
'typesense' => [
'client-settings' => [
'api_key' => env('TYPESENSE_API_KEY', 'xyz'),
'nodes' => [
[
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
],
'nearest_node' => [
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2),
'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30),
'num_retries' => env('TYPESENSE_NUM_RETRIES', 3),
'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1),
],
// 'max_total_results' => env('TYPESENSE_MAX_TOTAL_RESULTS', 1000),
'model-settings' => [
// User::class => [
// 'collection-schema' => [
// 'fields' => [
// [
// 'name' => 'id',
// 'type' => 'string',
// ],
// [
// 'name' => 'name',
// 'type' => 'string',
// ],
// [
// 'name' => 'created_at',
// 'type' => 'int64',
// ],
// ],
// 'default_sorting_field' => 'created_at',
// ],
// 'search-parameters' => [
// 'query_by' => 'name'
// ],
// ],
],
'import_action' => env('TYPESENSE_IMPORT_ACTION', 'upsert'),
],
];

38
config/services.php Executable file
View File

@ -0,0 +1,38 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
'resend' => [
'key' => env('RESEND_API_KEY'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
];

217
config/session.php Executable file
View File

@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "memcached",
| "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain without subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

1
database/.gitignore vendored Executable file
View File

@ -0,0 +1 @@
*.sqlite*

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
}
}

File diff suppressed because one or more lines are too long

69
laji/buding_laji.js Executable file
View File

@ -0,0 +1,69 @@
<script data-no-optimize="1">
/**
* [补丁 1] 环境校验
* 处理 Cookie 逻辑确保主题 JS 不会因为找不到 Vary 而报错
*/
var litespeed_vary = document.cookie.replace(/(?:(?:^|.*;\s*)_lscache_vary\s*\=\s*([^;]*).*$)|^.*$/,"");
if(!litespeed_vary) {
fetch("/wp-content/plugins/litespeed-cache/guest.vary.php",{method:"POST",cache:"no-cache",redirect:"follow"})
.then(e=>e.json()).then(e=>{
if(e.hasOwnProperty("reload") && "yes"==e.reload) window.location.reload(!0);
});
}
</script>
<script data-optimized="1" type="litespeed/javascript" data-src="/wp-content/litespeed/js/a7edd6b036ef4b8e7cd93f435386d4da.js"></script>
<script>
/**
* [补丁 2] 侧边栏/动画激活引擎
*/
const litespeed_ui_events = ["mouseover","click","keydown","wheel","touchmove","touchstart"];
var urlCreator = window.URL || window.webkitURL;
// 交互强制加载
function litespeed_load_delayed_js_force() {
litespeed_ui_events.forEach(e => {
window.removeEventListener(e, litespeed_load_delayed_js_force, {passive:!0});
});
litespeed_load_delayed_js();
}
litespeed_ui_events.forEach(e => {
window.addEventListener(e, litespeed_load_delayed_js_force, {passive:!0});
});
async function litespeed_load_delayed_js() {
let scripts = [];
document.querySelectorAll('script[type="litespeed/javascript"]').forEach(e => { scripts.push(e); });
for (var i in scripts) {
await new Promise(res => litespeed_load_one(scripts[i], res));
}
// [核心漏点] 必须手动触发这个事件,侧边栏才会初始化
document.dispatchEvent(new Event("DOMContentLiteSpeedLoaded"));
window.dispatchEvent(new Event("DOMContentLiteSpeedLoaded"));
}
function litespeed_load_one(t, e) {
var d = document.createElement("script");
d.addEventListener("load", e);
d.addEventListener("error", e);
t.getAttributeNames().forEach(name => {
if (name != "type") d.setAttribute(name == "data-src" ? "src" : name, t.getAttribute(name));
});
d.type = "text/javascript";
if (!d.src && t.textContent) {
d.src = litespeed_inline2src(t.textContent);
}
t.after(d);
t.remove();
}
function litespeed_inline2src(t) {
try {
var d = urlCreator.createObjectURL(new Blob([t.replace(/^(?:)?$/gm, "$1")], {type: "text/javascript"}));
} catch (e) {
d = "data:text/javascript;base64," + btoa(t.replace(/^(?:)?$/gm, "$1"));
}
return d;
}
</script>

2533
package-lock.json generated Executable file

File diff suppressed because it is too large Load Diff

25
package.json Executable file
View File

@ -0,0 +1,25 @@
{
"$schema": "https://www.schemastore.org/package.json",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"@tailwindcss/vite": "^4.0.0",
"axios": "^1.11.0",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0.0",
"tailwindcss": "^4.0.0",
"vite": "^7.0.7"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^7.1.0",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
"clipboard": "^2.0.11",
"infinite-scroll": "^5.0.0",
"jquery": "^4.0.0"
}
}

35
phpunit.xml Executable file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<env name="NIGHTWATCH_ENABLED" value="false"/>
</php>
</phpunit>

25
public/.htaccess Executable file
View File

@ -0,0 +1,25 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Handle X-XSRF-Token Header
RewriteCond %{HTTP:x-xsrf-token} .
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

0
public/favicon.ico Executable file
View File

42
public/index.php Executable file
View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
function t($xData)
{
_t($xData);
}
function tt($xData)
{
_t($xData, 1);
}
function _t($aData, $iType = 0)
{
if (env("is_loc") == true) {
echo "<pre>";
var_dump($aData);
if ($iType == 1) {
exit;
}
}
}
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());

2
public/robots.txt Executable file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

24
public/sakai/css/common.css Executable file
View File

@ -0,0 +1,24 @@
.o-thumb-img {
width: 1001%;
height: 100%;
object-fit: cover; /* 效果等同于 background-size: cover */
}
/* 初始态:透明且向下偏移一点点 */
.ajax-item-fade-in {
opacity: 0;
transform: translateY(20px);
transition: all 0.6s cubic-bezier(0.21, 0.6, 0.35, 1);
}
/* 激活态:自然升起淡入 */
.ajax-item-fade-in.is-visible {
opacity: 1;
transform: translateY(0);
}
.subCopy {
background-color:gray;
padding:6px;
color:#fff;
}

6
public/sakai/css/single.css Executable file
View File

@ -0,0 +1,6 @@
.subCopy {
background-color:gray;
padding:6px;
color:#fff;
}

30
public/sakai/js/articleList.js Executable file
View File

@ -0,0 +1,30 @@
$(document).ready(function() {
$(".btn-more").click(function() {
$("#pageNo").val(parseInt($("#pageNo").val()) + 1);
$.ajax({
url: '/' + $("#type").val(),
type: 'GET',
data: {
pageNo: $("#pageNo").val(),
tagId: $("#tagId").val(),
search: $("#search").val(),
},
success: function(response) {
$(".list-home").append(response);
if ($("#haveMore").val() == 0) {
$(".btn-more").hide();
}
},
error: function(xhr, status, error) {
}
})
})
})

66
public/sakai/js/articleSinge.js Executable file
View File

@ -0,0 +1,66 @@
$(document).ready(function() {
// 复制
var clipboard = new ClipboardJS('.copy-permalink');
clipboard.on('success', function(e) {
ncPopupTips(!0, '复制成功');
e.clearSelection();
});
clipboard.on('error', function(e) {
ncPopupTips(1, '复制失败');
});
// 基本变量
$id = $("#article_id").val();
// 修复加载
$(".widget_nice_tag_cloud").load("/widget/toptags.html");
// 获取点赞数
$.ajax({
url: '/helper/like/get/'+$id,
type: 'GET',
data: {},
success: function(response) {
$(".like-count").html(response)
}
})
// 获取过去时间
$.ajax({
url: '/helper/timelost/'+$("#createdAt").val(),
type: 'GET',
data: {},
success: function(response) {
$("#timeLost").html(response)
},
error: function(xhr, status, error) {
}
})
// 点赞动作
$("[data-action='like']").click(function(){
$.ajax({
url: '/helper/like/up/'+$id,
type: 'GET',
data: {},
dataType: 'json',
success: function(response) {
if (response.iCode == 1) {
$(".like-count").html(response.iLikeNow)
}
}
})
return false;
});
});

14
public/sakai/js/common.js Executable file
View File

@ -0,0 +1,14 @@
$(document).ready(function() {
$('.search-input').on('keypress', function(event) {
if (event.which === 13) {
window.location.href = "/mix?search="+$(this).val();
return false;
}
})
// $(".widget_nice_tag_cloud").load("/widget/toptags.html");
// $(".site-main").click();
})

BIN
public/sites/vfn/icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/sites/vfn/siteimg.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

3916
public/static/js/lib/jquery.min.js vendored Executable file

File diff suppressed because it is too large Load Diff

11125
public/static/js/ui/core-ui.min.js vendored Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2
public/upload/images/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
*
!.gitignore

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More