mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-03 10:30:51 +08:00
feat: new xboard
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
* * * * * php /www/artisan schedule:run >> /dev/null 2>&1
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 7001 default_server;
|
|
||||||
listen [::]:7001 default_server;
|
|
||||||
|
|
||||||
root /www/public/;
|
|
||||||
index index.html index.htm;
|
|
||||||
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
# 开启 brotli 压缩
|
|
||||||
brotli on;
|
|
||||||
brotli_static on;
|
|
||||||
brotli_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
|
||||||
|
|
||||||
# 开启 gzip 压缩
|
|
||||||
gzip on;
|
|
||||||
gzip_static on;
|
|
||||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
|
||||||
|
|
||||||
location ~* \.(jpg|jpeg|png|gif|js|css|svg|woff2|woff|ttf|eot|wasm|json|ico|html|htm)$ {
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ .* {
|
|
||||||
proxy_pass http://127.0.0.1:7010;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Real-PORT $remote_port;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header Scheme $scheme;
|
|
||||||
proxy_set_header Server-Protocol $server_protocol;
|
|
||||||
proxy_set_header Server-Name $server_name;
|
|
||||||
proxy_set_header Server-Addr $server_addr;
|
|
||||||
proxy_set_header Server-Port $server_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ /\.ht {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
[supervisord]
|
|
||||||
nodaemon=true
|
|
||||||
user=root
|
|
||||||
logfile=/dev/null
|
|
||||||
logfile_maxbytes=0
|
|
||||||
pidfile=/tmp/supervisord.pid
|
|
||||||
|
|
||||||
[unix_http_server]
|
|
||||||
file=/run/supervisord.sock
|
|
||||||
chmod=0700
|
|
||||||
|
|
||||||
[rpcinterface:supervisor]
|
|
||||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
|
||||||
|
|
||||||
[program:chown]
|
|
||||||
directory=/www
|
|
||||||
command=sh -c "chown -R www:www /www && chmod -R 775 /www"
|
|
||||||
autostart=true
|
|
||||||
autorestart=false
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
|
|
||||||
[program:nginx]
|
|
||||||
command=nginx -g 'daemon off;'
|
|
||||||
user=root
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startretries=10
|
|
||||||
|
|
||||||
[program:cron]
|
|
||||||
command=crond -f -l 8
|
|
||||||
user=root
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startretries=10
|
|
||||||
|
|
||||||
; [program:laravels]
|
|
||||||
; command=php bin/laravels start
|
|
||||||
; directory=/www
|
|
||||||
; user=www-data
|
|
||||||
; numprocs=1
|
|
||||||
; stdout_logfile=/dev/stdout
|
|
||||||
; stdout_logfile_maxbytes=0
|
|
||||||
; stderr_logfile=/dev/stderr
|
|
||||||
; stderr_logfile_maxbytes=0
|
|
||||||
; autostart=true
|
|
||||||
; autorestart=true
|
|
||||||
; startretries=3
|
|
||||||
|
|
||||||
[program:adapterman]
|
|
||||||
command=php -c php.ini webman.php start
|
|
||||||
directory=/www
|
|
||||||
user=www
|
|
||||||
numprocs=1
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startretries=10
|
|
||||||
|
|
||||||
[program:xboard-queue]
|
|
||||||
command=php artisan horizon
|
|
||||||
directory=/www
|
|
||||||
user=www
|
|
||||||
stdout_logfile=/www/storage/logs/queue.log
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/www/storage/logs/queue_error.log
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startretries=10
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
FROM redis:7-alpine
|
|
||||||
|
|
||||||
RUN mkdir -p /run/redis-socket && chmod 777 /run/redis-socket
|
|
||||||
COPY ./redis.conf /etc/redis.conf
|
|
||||||
CMD ["redis-server", "/etc/redis.conf"]
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
unixsocket /run/redis-socket/redis.sock
|
|
||||||
unixsocketperm 777
|
|
||||||
port 0
|
|
||||||
|
|
||||||
save 900 1
|
|
||||||
save 300 10
|
|
||||||
save 60 10000
|
|
||||||
64
.docker/supervisor/supervisord.conf
Normal file
64
.docker/supervisor/supervisord.conf
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/dev/stdout
|
||||||
|
logfile_maxbytes=0
|
||||||
|
pidfil=/www/storage/logs/supervisor/supervisord.pid
|
||||||
|
loglevel=info
|
||||||
|
|
||||||
|
[program:octane]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /www/artisan octane:start --host=0.0.0.0 --port=7001
|
||||||
|
autostart=%(ENV_ENABLE_WEB)s
|
||||||
|
autorestart=true
|
||||||
|
user=www
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stdout_logfile_backups=0
|
||||||
|
numprocs=1
|
||||||
|
stopwaitsecs=10
|
||||||
|
stopsignal=QUIT
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
priority=100
|
||||||
|
|
||||||
|
[program:horizon]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /www/artisan horizon
|
||||||
|
autostart=%(ENV_ENABLE_HORIZON)s
|
||||||
|
autorestart=true
|
||||||
|
user=www
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stdout_logfile_backups=0
|
||||||
|
numprocs=1
|
||||||
|
stopwaitsecs=3
|
||||||
|
stopsignal=SIGINT
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
priority=200
|
||||||
|
|
||||||
|
[program:redis]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=redis-server --dir /data
|
||||||
|
--dbfilename dump.rdb
|
||||||
|
--save 900 1
|
||||||
|
--save 300 10
|
||||||
|
--save 60 10000
|
||||||
|
--unixsocket /data/redis.sock
|
||||||
|
--unixsocketperm 777
|
||||||
|
autostart=%(ENV_ENABLE_REDIS)s
|
||||||
|
autorestart=true
|
||||||
|
user=redis
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stdout_logfile_backups=0
|
||||||
|
numprocs=1
|
||||||
|
stopwaitsecs=3
|
||||||
|
stopsignal=TERM
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
priority=300
|
||||||
@@ -23,3 +23,4 @@ docker-compose.yml
|
|||||||
storage/laravels.conf
|
storage/laravels.conf
|
||||||
storage/laravels.pid
|
storage/laravels.pid
|
||||||
storage/laravels-timer-process.pid
|
storage/laravels-timer-process.pid
|
||||||
|
/frontend
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ APP_KEY=base64:PZXk5vTuTinfeEVG5FpYv2l6WEhLsyvGpiWK7IgJJ60=
|
|||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
ADMIN_SETTING_CACHE=60 #设置缓存时间(单位秒)
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
|
|||||||
66
.github/ISSUE_TEMPLATE/bug-report----问题反馈.md
vendored
66
.github/ISSUE_TEMPLATE/bug-report----问题反馈.md
vendored
@@ -1,43 +1,39 @@
|
|||||||
---
|
---
|
||||||
name: Bug report | 问题反馈
|
name: 🐛 问题反馈 | Bug Report
|
||||||
about: Tell us what problems you have encountered
|
about: 提交使用过程中遇到的问题 | Report an issue
|
||||||
title: "[BUG]"
|
title: "问题:"
|
||||||
labels: ''
|
labels: '🐛 bug'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
🙇♂️🙇♂️🙇♂️注意:XrayR等非XBoard问题请前往项目方提问
|
<!-- 🔴 请注意:XrayR等非XBoard问题请前往相应项目提问 -->
|
||||||
🙇♂️🙇♂️🙇♂️Note: XrayR and other non-XBoard issues please go to the project side to ask questions
|
<!-- 🔴 Note: For XrayR and other non-XBoard issues, please report to their respective projects -->
|
||||||
|
|
||||||
|
> ⚠️ 请务必按照模板填写完整信息,没有详细描述的issue可能会被忽略或关闭
|
||||||
|
> ⚠️ Please follow the template to provide complete information, issues without detailed description may be ignored or closed
|
||||||
|
|
||||||
|
**基本信息 | Basic Info**
|
||||||
|
```yaml
|
||||||
|
XBoard版本 | Version:
|
||||||
|
部署方式 | Deployment: [Docker/手动部署]
|
||||||
|
PHP版本 | Version:
|
||||||
|
数据库 | Database:
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题描述 | Description**
|
||||||
|
<!-- 简要描述你遇到的问题 -->
|
||||||
|
|
||||||
|
|
||||||
The XBoard version number you are using
|
**复现步骤 | Steps**
|
||||||
当前使用的XBoard版本号(git commit id)
|
<!-- 如何复现这个问题? -->
|
||||||
--------
|
1.
|
||||||
|
2.
|
||||||
|
|
||||||
Would you like to deploy using Docker?
|
**相关截图 | Screenshots**
|
||||||
你的部署方式(是否为Docker)
|
<!-- 拖拽图片到这里(请注意隐藏敏感信息)-->
|
||||||
--------
|
|
||||||
|
|
||||||
|
**日志信息 | Logs**
|
||||||
Please briefly describe the issue you encountered (preferably with reproducible steps).
|
<!-- storage/logs 目录下的日志 -->
|
||||||
简单描述你遇到的问题(最好带上复现步骤)
|
```log
|
||||||
--------
|
// 粘贴日志内容到这里
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Screenshot of the reported error(Please do desensitization)
|
|
||||||
报告错误的截图(请做脱敏处理)
|
|
||||||
--------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Screenshot of the reported error(Please do desensitization)
|
|
||||||
报告错误的截图(请做脱敏处理)
|
|
||||||
--------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Run the php artisan log:export 7 command to export log files (where 7 represents logs for the last 7 days).
|
|
||||||
运行`php artisan log:export 7` 命令导出的日志文件(其中7为最近7天的日志)。
|
|
||||||
--------
|
|
||||||
|
|||||||
31
.github/ISSUE_TEMPLATE/feature-request---功能请求.md
vendored
31
.github/ISSUE_TEMPLATE/feature-request---功能请求.md
vendored
@@ -1,11 +1,28 @@
|
|||||||
---
|
---
|
||||||
name: Feature request | 功能请求
|
name: ✨ 功能请求 | Feature Request
|
||||||
about: Tell us what you need
|
about: 提交新功能建议或改进意见 | Suggest an idea
|
||||||
title: "[Feature request]"
|
title: "建议:"
|
||||||
labels: ''
|
labels: '✨ enhancement'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Please describe in detail the problems or needs you have encountered.
|
> ⚠️ 请务必按照模板详细描述你的需求,没有详细描述的issue可能会被忽略或关闭
|
||||||
请详细描述你遇到的问题或需求。
|
> ⚠️ Please follow the template to describe your request in detail, issues without detailed description may be ignored or closed
|
||||||
|
|
||||||
|
**需求描述 | Description**
|
||||||
|
<!-- 描述你希望添加的功能或改进建议 -->
|
||||||
|
|
||||||
|
|
||||||
|
**使用场景 | Use Case**
|
||||||
|
<!-- 描述这个功能会在什么场景下使用,解决什么问题 -->
|
||||||
|
|
||||||
|
|
||||||
|
**功能建议 | Suggestion**
|
||||||
|
<!-- 你期望这个功能是什么样的?可以描述一下具体实现方式 -->
|
||||||
|
```yaml
|
||||||
|
功能形式 | Type: [新功能/功能优化/界面改进]
|
||||||
|
预期效果 | Expected:
|
||||||
|
```
|
||||||
|
|
||||||
|
**补充说明 | Additional**
|
||||||
|
<!-- 其他补充说明或者参考示例 -->
|
||||||
|
|||||||
128
.github/workflows/docker-publish.yml
vendored
128
.github/workflows/docker-publish.yml
vendored
@@ -1,89 +1,93 @@
|
|||||||
name: Docker
|
name: Docker Build and Publish
|
||||||
|
|
||||||
# This workflow uses actions that are not certified by GitHub.
|
|
||||||
# They are provided by a third-party and are governed by
|
|
||||||
# separate terms of service, privacy policy, and support
|
|
||||||
# documentation.
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "dev" ]
|
branches: ["master"]
|
||||||
# Publish semver tags as releases.
|
workflow_dispatch:
|
||||||
tags: [ 'v*.*.*' ]
|
|
||||||
workflow_dispatch: # Enable manual trigger
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Use docker.io for Docker Hub if empty
|
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
# github.repository as <account>/<repo>
|
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
# This is used to complete the identity challenge
|
|
||||||
# with sigstore/fulcio when running outside of PRs.
|
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- uses: satackey/action-docker-layer-caching@v0.0.11
|
with:
|
||||||
continue-on-error: true
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: 'arm64,amd64'
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
driver-opts: |
|
||||||
|
image=moby/buildkit:latest
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=sha,format=long
|
||||||
|
type=raw,value=new
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: get_version
|
||||||
|
run: echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Update version in app.php
|
||||||
|
run: |
|
||||||
|
VERSION=$(date '+%Y%m%d')-$(git rev-parse --short HEAD)
|
||||||
|
sed -i "s/'version' => '.*'/'version' => '$VERSION'/g" config/app.php
|
||||||
|
echo "Updated version to: $VERSION"
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
tags: |
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:master
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:latest
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard
|
||||||
|
${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:${{ steps.get_version.outputs.version }}
|
||||||
|
build-args: |
|
||||||
|
BUILDKIT_INLINE_CACHE=1
|
||||||
|
provenance: false
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
uses: sigstore/cosign-installer@v3.4.0
|
uses: sigstore/cosign-installer@v3.4.0
|
||||||
with:
|
with:
|
||||||
cosign-release: 'v2.2.2'
|
cosign-release: 'v2.2.2'
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Sign image
|
||||||
uses: docker/setup-buildx-action@v3.2.0
|
if: steps.build-and-push.outputs.digest != ''
|
||||||
|
|
||||||
# Login against a Docker registry except on PR
|
|
||||||
# https://github.com/docker/login-action
|
|
||||||
- name: Log into registry ${{ env.REGISTRY }}
|
|
||||||
uses: docker/login-action@v3.1.0
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract Docker metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5.5.1
|
|
||||||
with:
|
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
id: get_version
|
|
||||||
run: echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
id: build-and-push
|
|
||||||
uses: docker/build-push-action@v5.3.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: ${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:latest,${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard,${{ env.REGISTRY }}/${{ github.repository_owner }}/xboard:${{ steps.get_version.outputs.version }}
|
|
||||||
# Sign the resulting Docker image digest except on PRs.
|
|
||||||
# This will only write to the public Rekor transparency log when the Docker
|
|
||||||
# repository is public to avoid leaking data. If you would like to publish
|
|
||||||
# transparency data even for private images, pass --force to cosign below.
|
|
||||||
# https://github.com/sigstore/cosign
|
|
||||||
- name: Sign the published Docker image
|
|
||||||
env:
|
env:
|
||||||
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
|
COSIGN_EXPERIMENTAL: 1
|
||||||
TAGS: ${{ steps.meta.outputs.tags }}
|
run: |
|
||||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes "{}@${{ steps.build-and-push.outputs.digest }}"
|
||||||
# This step uses the identity token to provision an ephemeral certificate
|
|
||||||
# against the sigstore community Fulcio instance.
|
|
||||||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -27,4 +27,7 @@ storage/laravels.pid
|
|||||||
storage/laravels-timer-process.pid
|
storage/laravels-timer-process.pid
|
||||||
cli-php.ini
|
cli-php.ini
|
||||||
frontend
|
frontend
|
||||||
docker-compose.yaml
|
docker-compose.yaml
|
||||||
|
bun.lockb
|
||||||
|
compose.yaml
|
||||||
|
.scribe
|
||||||
30
Dockerfile
30
Dockerfile
@@ -1,17 +1,27 @@
|
|||||||
FROM phpswoole/swoole:php8.1-alpine
|
FROM phpswoole/swoole:php8.2-alpine
|
||||||
|
|
||||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
||||||
|
RUN install-php-extensions pcntl bcmath zip redis \
|
||||||
|
&& apk --no-cache add shadow sqlite mysql-client mysql-client mysql-dev mariadb-connector-c git patch supervisor redis \
|
||||||
|
&& addgroup -S -g 1000 www && adduser -S -G www -u 1000 www \
|
||||||
|
&& (getent group redis || addgroup -S redis) \
|
||||||
|
&& (getent passwd redis || adduser -S -G redis -H -h /data redis)
|
||||||
|
|
||||||
RUN install-php-extensions pcntl bcmath inotify \
|
|
||||||
&& apk --no-cache add shadow supervisor nginx sqlite nginx-mod-http-brotli mysql-client git patch \
|
|
||||||
&& addgroup -S -g 1000 www && adduser -S -G www -u 1000 www
|
|
||||||
#复制项目文件以及配置文件
|
|
||||||
WORKDIR /www
|
WORKDIR /www
|
||||||
COPY .docker /
|
COPY .docker /
|
||||||
COPY . /www
|
COPY . /www
|
||||||
RUN composer install --optimize-autoloader --no-cache --no-dev \
|
COPY .docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
&& php artisan storage:link \
|
|
||||||
&& chown -R www:www /www \
|
|
||||||
&& chmod -R 775 /www
|
|
||||||
|
|
||||||
CMD /usr/bin/supervisord --nodaemon -c /etc/supervisor/supervisord.conf
|
RUN composer install --optimize-autoloader --no-cache --no-dev \
|
||||||
|
&& php artisan storage:link \
|
||||||
|
&& chown -R www:www /www \
|
||||||
|
&& chmod -R 775 /www \
|
||||||
|
&& mkdir -p /data \
|
||||||
|
&& chown redis:redis /data
|
||||||
|
|
||||||
|
ENV ENABLE_WEB=true \
|
||||||
|
ENABLE_HORIZON=true \
|
||||||
|
ENABLE_REDIS=false
|
||||||
|
|
||||||
|
EXPOSE 7001
|
||||||
|
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
129
README.md
129
README.md
@@ -1,64 +1,91 @@
|
|||||||
# 关于Xboard
|
# Xboard
|
||||||
Xboard是基于V2board二次开发,在性能上和功能上都有大部分增强的**面板
|
|
||||||
|
|
||||||
# 免责声明
|
<div align="center">
|
||||||
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
|
|
||||||
|
|
||||||
# Xboard 特点
|
[](https://t.me/XboardOfficial)
|
||||||
基于V2board 二次开发,增加了以下特性
|

|
||||||
- 升级Laravel10
|

|
||||||
- 适配Laravels (提升至10+倍并发)
|
[](LICENSE)
|
||||||
- 适配Webman (比laravels快50%左右)
|
|
||||||
- 修改配置从数据库中获取
|
|
||||||
- 支持Docker部署、分布式部署
|
|
||||||
- 支持根据用户IP归属地来下发订阅
|
|
||||||
- 增加Hy2支持
|
|
||||||
- 增加sing-box下发
|
|
||||||
- 支持直接从cloudflare获取访问者真实IP
|
|
||||||
- 支持根据客户端版本自动下发新协议
|
|
||||||
- 支持线路筛选(订阅地址后面增加 &filter=香港|美国)
|
|
||||||
- 支持Sqlite安装(代替Mysql,自用用户福音)
|
|
||||||
- 使用Vue3 + TypeScript + NaiveUI + Unocss + Pinia重构用户前端
|
|
||||||
- 修复大量BUG
|
|
||||||
|
|
||||||
# **系统架构**
|
English | [简体中文](README_CN.md)
|
||||||
|
|
||||||
- PHP8.1+
|
</div>
|
||||||
- Composer
|
|
||||||
- MySQL5.7+
|
|
||||||
- Redis
|
|
||||||
- Laravel
|
|
||||||
|
|
||||||
## 性能对比 [查看详情](./docs/性能对比.md)
|
## 📖 Introduction
|
||||||
> xboard 无论前端还是后端性能都有巨大的提升
|
|
||||||
|
|
||||||
|场景 | php-fpm(传统) | php-fpm(传统开启opcache) | laravels | webman(docker)|
|
Xboard is a modern panel system built on Laravel 11, focusing on providing a clean and efficient user experience.
|
||||||
|---- | ---- |---- |----| ---|
|
|
||||||
|首页 | 6请求/秒 | 157请求/秒 | 477请求/秒 | 803请求/秒 |
|
|
||||||
|用户订阅 | 6请求/秒 | 196请求/秒 | 586请求/秒 | 1064请求/秒 |
|
|
||||||
|用户首页延迟| 308ms | 110ms | 101ms | 98ms |
|
|
||||||
|
|
||||||
## 页面展示
|
## ✨ Features
|
||||||

|
|
||||||
|
|
||||||
## 安装 / 更新 / 回滚
|
- 🚀 Built with Laravel 11 + Octane for significant performance gains
|
||||||
你可以点击查看下列方式的**安装、更新**步骤
|
- 🎨 Redesigned admin interface (React + Shadcn UI)
|
||||||
- [1panel 部署](./docs/1panel安装指南.md)
|
- 📱 Modern user frontend (Vue3 + TypeScript)
|
||||||
- [Docker Compose 纯命令行快速部署](./docs/docker-compose安装指南.md)
|
- 🐳 Ready-to-use Docker deployment solution
|
||||||
- [aapanel + Docker Compose (推荐)](./docs/aapanel+docker安装指南.md)
|
- 🎯 Optimized system architecture for better maintainability
|
||||||
- [aapanel 部署](./docs/aapanel安装指南.md)
|
|
||||||
### 从其他版本迁移
|
|
||||||
#### 数据库迁移
|
|
||||||
**根据你的版本查看对应的迁移指南进行迁移**
|
|
||||||
- v2board dev 23/10/27的版本 [点击跳转迁移指南](./docs/v2b_dev迁移指南.md)
|
|
||||||
- v2board 1.7.4 [点击跳转迁移指南](./docs/v2b_1.7.4迁移指南.md)
|
|
||||||
- v2board 1.7.3 [点击跳转迁移指南](./docs/v2b_1.7.3迁移指南.md)
|
|
||||||
- v2board wyx2685 [点击跳转迁移指南](./docs/v2b_wyx2685迁移指南.md)
|
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### 注意
|
```bash
|
||||||
> 修改后台路径需要重启才能生效
|
git clone -b compose --depth 1 https://github.com/cedar2025/Xboard && \
|
||||||
|
cd Xboard && \
|
||||||
|
docker compose run -it --rm \
|
||||||
|
-e ENABLE_SQLITE=true \
|
||||||
|
-e ENABLE_REDIS=true \
|
||||||
|
-e ADMIN_ACCOUNT=admin@demo.com \
|
||||||
|
web php artisan xboard:install && \
|
||||||
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> After installation, visit: http://SERVER_IP:7001
|
||||||
|
> ⚠️ Make sure to save the admin credentials shown during installation
|
||||||
|
|
||||||
|
## 📖 Documentation
|
||||||
|
|
||||||
|
### 🔄 Upgrade Notice
|
||||||
|
> 🚨 **Important:** This version involves significant changes. Please strictly follow the upgrade documentation and backup your database before upgrading. Note that upgrading and migration are different processes, do not confuse them.
|
||||||
|
|
||||||
|
### Deployment Guides
|
||||||
|
- [Deploy with 1Panel](./docs/zh-CN/installation/1panel.md)
|
||||||
|
- [Deploy with Docker Compose](./docs/zh-CN/installation/docker-compose.md)
|
||||||
|
- [Deploy with aaPanel](./docs/zh-CN/installation/aapanel.md)
|
||||||
|
- [Deploy with aaPanel + Docker](./docs/zh-CN/installation/aapanel-docker.md) (Recommended)
|
||||||
|
|
||||||
|
### Migration Guides
|
||||||
|
- [Migrate from v2board dev](./docs/zh-CN/migration/v2board-dev.md)
|
||||||
|
- [Migrate from v2board 1.7.4](./docs/zh-CN/migration/v2board-1.7.4.md)
|
||||||
|
- [Migrate from v2board 1.7.3](./docs/zh-CN/migration/v2board-1.7.3.md)
|
||||||
|
- [Migrate from v2board wyx2685](./docs/zh-CN/migration/v2board-wyx2685.md)
|
||||||
|
|
||||||
|
## 🛠️ Tech Stack
|
||||||
|
|
||||||
|
- Backend: Laravel 11 + Octane
|
||||||
|
- Admin Panel: React + Shadcn UI + TailwindCSS
|
||||||
|
- User Frontend: Vue3 + TypeScript + NaiveUI
|
||||||
|
- Deployment: Docker + Docker Compose
|
||||||
|
- Caching: Redis + Octane Cache
|
||||||
|
|
||||||
|
## 📷 Preview
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## ⚠️ Disclaimer
|
||||||
|
|
||||||
|
This project is for learning and communication purposes only. Users are responsible for any consequences of using this project.
|
||||||
|
|
||||||
|
## 🔔 Important Notes
|
||||||
|
|
||||||
|
1. Restart required after modifying admin path:
|
||||||
|
```bash
|
||||||
docker compose restart
|
docker compose restart
|
||||||
```
|
```
|
||||||
> 如果是是aapanel安装则需要重启 webman守护进程
|
|
||||||
|
2. For aaPanel installations, restart the Octane daemon process
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Issues and Pull Requests are welcome to help improve the project.
|
||||||
|
|
||||||
|
## 📈 Star History
|
||||||
|
|
||||||
|
[](https://starchart.cc/cedar2025/Xboard)
|
||||||
96
README_CN.md
Normal file
96
README_CN.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Xboard
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://t.me/XboardOfficial)
|
||||||
|

|
||||||
|

|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
[English](README.md) | 简体中文
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## 📖 简介
|
||||||
|
|
||||||
|
Xboard 是一个基于 Laravel 11 开发的现代化面板系统,专注于提供简洁、高效的用户体验。
|
||||||
|
|
||||||
|
## ✨ 特性
|
||||||
|
|
||||||
|
- 🚀 基于 Laravel 11 + Octane,性能提升显著
|
||||||
|
- 🎨 全新设计的管理界面 (React + Shadcn UI)
|
||||||
|
- 📱 现代化的用户前端 (Vue3 + TypeScript)
|
||||||
|
- 🐳 开箱即用的 Docker 部署方案
|
||||||
|
- 🎯 优化的系统架构,提供更好的可维护性
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone -b compose --depth 1 https://github.com/cedar2025/Xboard && \
|
||||||
|
cd Xboard && \
|
||||||
|
docker compose run -it --rm \
|
||||||
|
-e ENABLE_SQLITE=true \
|
||||||
|
-e ENABLE_REDIS=true \
|
||||||
|
-e ADMIN_ACCOUNT=admin@demo.com \
|
||||||
|
web php artisan xboard:install && \
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
> 安装完成后访问:http://服务器IP:7001
|
||||||
|
> ⚠️ 请务必保存安装时显示的管理员账号密码
|
||||||
|
|
||||||
|
## 📚 使用文档
|
||||||
|
|
||||||
|
### 🔄 升级提示
|
||||||
|
> 🚨 **重要:** 此次版本跨度较大,请严格按照升级文档进行升级,必要时请备份数据库再进行升级。升级跟迁移不是一个东西,请不要混淆。
|
||||||
|
|
||||||
|
### 部署教程
|
||||||
|
- [使用 1Panel 部署](./docs/zh-CN/installation/1panel.md)
|
||||||
|
- [Docker Compose 部署](./docs/zh-CN/installation/docker-compose.md)
|
||||||
|
- [使用 aaPanel 部署](./docs/zh-CN/installation/aapanel.md)
|
||||||
|
- [aaPanel + Docker 部署](./docs/zh-CN/installation/aapanel-docker.md)(推荐)
|
||||||
|
|
||||||
|
### 迁移指南
|
||||||
|
- [从 v2board dev 迁移](./docs/zh-CN/migration/v2board-dev.md)
|
||||||
|
- [从 v2board 1.7.4 迁移](./docs/zh-CN/migration/v2board-1.7.4.md)
|
||||||
|
- [从 v2board 1.7.3 迁移](./docs/zh-CN/migration/v2board-1.7.3.md)
|
||||||
|
- [从 v2board wyx2685 迁移](./docs/zh-CN/migration/v2board-wyx2685.md)
|
||||||
|
|
||||||
|
## 🤝 参与贡献
|
||||||
|
|
||||||
|
欢迎提交 Issue 和 Pull Request 来帮助改进项目。
|
||||||
|
|
||||||
|
## 🛠️ 技术栈
|
||||||
|
|
||||||
|
- 后端:Laravel 11 + Octane
|
||||||
|
- 管理面板:React + Shadcn UI + TailwindCSS
|
||||||
|
- 用户前端:Vue3 + TypeScript + NaiveUI
|
||||||
|
- 部署方案:Docker + Docker Compose
|
||||||
|
- 缓存系统:Redis + Octane Cache
|
||||||
|
|
||||||
|
## 📷 界面预览
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## ⚠️ 免责声明
|
||||||
|
|
||||||
|
本项目仅供学习交流使用,使用本项目造成的任何后果由使用者自行承担。
|
||||||
|
|
||||||
|
## 🔔 注意事项
|
||||||
|
|
||||||
|
1. 修改后台路径后需要重启:
|
||||||
|
```bash
|
||||||
|
docker compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
2. aaPanel 环境下需要重启 Octane 守护进程
|
||||||
|
|
||||||
|
## 🤝 参与贡献
|
||||||
|
|
||||||
|
欢迎提交 Issue 和 Pull Request 来帮助改进项目。
|
||||||
|
|
||||||
|
## 📈 Star 增长趋势
|
||||||
|
|
||||||
|
[](https://starchart.cc/cedar2025/Xboard)
|
||||||
@@ -4,6 +4,8 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Google\Cloud\Storage\StorageClient;
|
use Google\Cloud\Storage\StorageClient;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
class BackupDatabase extends Command
|
class BackupDatabase extends Command
|
||||||
@@ -85,14 +87,14 @@ class BackupDatabase extends Command
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// 输出文件链接
|
// 输出文件链接
|
||||||
\Log::channel('backup')->info("🎉:数据库备份已上传到 Google Cloud Storage: $objectName");
|
Log::channel('backup')->info("🎉:数据库备份已上传到 Google Cloud Storage: $objectName");
|
||||||
$this->info("🎉:数据库备份已上传到 Google Cloud Storage: $objectName");
|
$this->info("🎉:数据库备份已上传到 Google Cloud Storage: $objectName");
|
||||||
\File::delete($compressedBackupPath);
|
File::delete($compressedBackupPath);
|
||||||
}
|
}
|
||||||
}catch(\Exception $e){
|
}catch(\Exception $e){
|
||||||
\Log::channel('backup')->error("😔:数据库备份失败 \n" . $e);
|
Log::channel('backup')->error("😔:数据库备份失败 \n" . $e);
|
||||||
$this->error("😔:数据库备份失败\n" . $e);
|
$this->error("😔:数据库备份失败\n" . $e);
|
||||||
\File::delete($compressedBackupPath);
|
File::delete($compressedBackupPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class ClearUser extends Command
|
|||||||
->where('last_login_at', NULL);
|
->where('last_login_at', NULL);
|
||||||
$count = $builder->count();
|
$count = $builder->count();
|
||||||
if ($builder->delete()) {
|
if ($builder->delete()) {
|
||||||
$this->info("已删除${count}位没有任何数据的用户");
|
$this->info("已删除{$count}位没有任何数据的用户");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class ExportV2Log extends Command
|
class ExportV2Log extends Command
|
||||||
{
|
{
|
||||||
@@ -20,7 +21,7 @@ class ExportV2Log extends Command
|
|||||||
$days = $this->argument('days');
|
$days = $this->argument('days');
|
||||||
$date = Carbon::now()->subDays($days)->startOfDay();
|
$date = Carbon::now()->subDays($days)->startOfDay();
|
||||||
|
|
||||||
$logs = \DB::table('v2_log')
|
$logs = DB::table('v2_log')
|
||||||
->where('created_at', '>=', $date->timestamp)
|
->where('created_at', '>=', $date->timestamp)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class MigrateFromV2b extends Command
|
class MigrateFromV2b extends Command
|
||||||
{
|
{
|
||||||
@@ -51,11 +53,12 @@ class MigrateFromV2b extends Command
|
|||||||
],
|
],
|
||||||
'1.7.3' => [
|
'1.7.3' => [
|
||||||
'ALTER TABLE `v2_stat_order` RENAME TO `v2_stat`;',
|
'ALTER TABLE `v2_stat_order` RENAME TO `v2_stat`;',
|
||||||
"ALTER TABLE `v2_stat` CHANGE COLUMN order_amount order_total INT COMMENT '订单合计';",
|
"ALTER TABLE `v2_stat` CHANGE COLUMN order_amount paid_total INT COMMENT '订单合计';",
|
||||||
|
"ALTER TABLE `v2_stat` CHANGE COLUMN order_count paid_count INT COMMENT '邀请佣金';",
|
||||||
"ALTER TABLE `v2_stat` CHANGE COLUMN commission_amount commission_total INT COMMENT '佣金合计';",
|
"ALTER TABLE `v2_stat` CHANGE COLUMN commission_amount commission_total INT COMMENT '佣金合计';",
|
||||||
"ALTER TABLE `v2_stat`
|
"ALTER TABLE `v2_stat`
|
||||||
ADD COLUMN paid_count INT NULL,
|
ADD COLUMN order_count INT NULL,
|
||||||
ADD COLUMN paid_total INT NULL,
|
ADD COLUMN order_total INT NULL,
|
||||||
ADD COLUMN register_count INT NULL,
|
ADD COLUMN register_count INT NULL,
|
||||||
ADD COLUMN invite_count INT NULL,
|
ADD COLUMN invite_count INT NULL,
|
||||||
ADD COLUMN transfer_used_total VARCHAR(32) NULL;
|
ADD COLUMN transfer_used_total VARCHAR(32) NULL;
|
||||||
@@ -132,7 +135,7 @@ class MigrateFromV2b extends Command
|
|||||||
try {
|
try {
|
||||||
foreach ($sqlCommands[$version] as $sqlCommand) {
|
foreach ($sqlCommands[$version] as $sqlCommand) {
|
||||||
// Execute SQL command
|
// Execute SQL command
|
||||||
\DB::statement($sqlCommand);
|
DB::statement($sqlCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info('1️⃣、数据库差异矫正成功');
|
$this->info('1️⃣、数据库差异矫正成功');
|
||||||
@@ -158,7 +161,7 @@ class MigrateFromV2b extends Command
|
|||||||
|
|
||||||
public function MigrateV2ConfigToV2Settings()
|
public function MigrateV2ConfigToV2Settings()
|
||||||
{
|
{
|
||||||
\Artisan::call('config:clear');
|
Artisan::call('config:clear');
|
||||||
$configValue = config('v2board') ?? [];
|
$configValue = config('v2board') ?? [];
|
||||||
|
|
||||||
foreach ($configValue as $k => $v) {
|
foreach ($configValue as $k => $v) {
|
||||||
@@ -167,16 +170,16 @@ class MigrateFromV2b extends Command
|
|||||||
|
|
||||||
// 如果记录不存在,则插入
|
// 如果记录不存在,则插入
|
||||||
if ($existingSetting) {
|
if ($existingSetting) {
|
||||||
$this->warn("配置 ${k} 在数据库已经存在, 忽略");
|
$this->warn("配置 {$k} 在数据库已经存在, 忽略");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Setting::create([
|
Setting::create([
|
||||||
'name' => $k,
|
'name' => $k,
|
||||||
'value' => is_array($v)? json_encode($v) : $v,
|
'value' => is_array($v)? json_encode($v) : $v,
|
||||||
]);
|
]);
|
||||||
$this->info("配置 ${k} 迁移成功");
|
$this->info("配置 {$k} 迁移成功");
|
||||||
}
|
}
|
||||||
\Artisan::call('config:cache');
|
Artisan::call('config:cache');
|
||||||
|
|
||||||
$this->info('所有配置迁移完成');
|
$this->info('所有配置迁移完成');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,16 @@ class SendRemindMail extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
if (!(bool) admin_setting('remind_mail_enable', false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$users = User::all();
|
$users = User::all();
|
||||||
$mailService = new MailService();
|
$mailService = new MailService();
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
if ($user->remind_expire) $mailService->remindExpire($user);
|
if ($user->remind_expire)
|
||||||
if ($user->remind_traffic) $mailService->remindTraffic($user);
|
$mailService->remindExpire($user);
|
||||||
|
if ($user->remind_traffic)
|
||||||
|
$mailService->remindTraffic($user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ use Illuminate\Encryption\Encrypter;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
use Illuminate\Support\Env;
|
use Illuminate\Support\Env;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use function Laravel\Prompts\confirm;
|
use function Laravel\Prompts\confirm;
|
||||||
use function Laravel\Prompts\text;
|
use function Laravel\Prompts\text;
|
||||||
use function Laravel\Prompts\note;
|
use function Laravel\Prompts\note;
|
||||||
@@ -45,17 +49,17 @@ class XboardInstall extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$isDocker = env('docker', false);
|
$isDocker = file_exists('/.dockerenv');
|
||||||
$enableSqlite = env('enable_sqlite', false);
|
$enableSqlite = env('ENABLE_SQLITE', false);
|
||||||
$enableRedis = env('enable_redis', false);
|
$enableRedis = env('ENABLE_REDIS', false);
|
||||||
$adminAccount = env('admin_account', '');
|
$adminAccount = env('ADMIN_ACCOUNT', '');
|
||||||
$this->info("__ __ ____ _ ");
|
$this->info("__ __ ____ _ ");
|
||||||
$this->info("\ \ / /| __ ) ___ __ _ _ __ __| | ");
|
$this->info("\ \ / /| __ ) ___ __ _ _ __ __| | ");
|
||||||
$this->info(" \ \/ / | __ \ / _ \ / _` | '__/ _` | ");
|
$this->info(" \ \/ / | __ \ / _ \ / _` | '__/ _` | ");
|
||||||
$this->info(" / /\ \ | |_) | (_) | (_| | | | (_| | ");
|
$this->info(" / /\ \ | |_) | (_) | (_| | | | (_| | ");
|
||||||
$this->info("/_/ \_\|____/ \___/ \__,_|_| \__,_| ");
|
$this->info("/_/ \_\|____/ \___/ \__,_|_| \__,_| ");
|
||||||
if (
|
if (
|
||||||
(\File::exists(base_path() . '/.env') && $this->getEnvValue('INSTALLED'))
|
(File::exists(base_path() . '/.env') && $this->getEnvValue('INSTALLED'))
|
||||||
|| (env('INSTALLED', false) && $isDocker)
|
|| (env('INSTALLED', false) && $isDocker)
|
||||||
) {
|
) {
|
||||||
$securePath = admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key'))));
|
$securePath = admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key'))));
|
||||||
@@ -86,11 +90,11 @@ class XboardInstall extends Command
|
|||||||
'DB_PASSWORD' => '',
|
'DB_PASSWORD' => '',
|
||||||
];
|
];
|
||||||
try {
|
try {
|
||||||
\Config::set("database.default", 'sqlite');
|
Config::set("database.default", 'sqlite');
|
||||||
\Config::set("database.connections.sqlite.database", base_path($envConfig['DB_DATABASE']));
|
Config::set("database.connections.sqlite.database", base_path($envConfig['DB_DATABASE']));
|
||||||
\DB::purge('sqlite');
|
DB::purge('sqlite');
|
||||||
\DB::connection('sqlite')->getPdo();
|
DB::connection('sqlite')->getPdo();
|
||||||
if (!blank(\DB::connection('sqlite')->getPdo()->query("SELECT name FROM sqlite_master WHERE type='table'")->fetchAll(\PDO::FETCH_COLUMN))) {
|
if (!blank(DB::connection('sqlite')->getPdo()->query("SELECT name FROM sqlite_master WHERE type='table'")->fetchAll(\PDO::FETCH_COLUMN))) {
|
||||||
if (confirm(label: '检测到数据库中已经存在数据,是否要清空数据库以便安装新的数据?', default: false, yes: '清空', no: '退出安装')) {
|
if (confirm(label: '检测到数据库中已经存在数据,是否要清空数据库以便安装新的数据?', default: false, yes: '清空', no: '退出安装')) {
|
||||||
$this->info('正在清空数据库请稍等');
|
$this->info('正在清空数据库请稍等');
|
||||||
$this->call('db:wipe', ['--force' => true]);
|
$this->call('db:wipe', ['--force' => true]);
|
||||||
@@ -115,16 +119,16 @@ class XboardInstall extends Command
|
|||||||
'DB_PASSWORD' => text(label: '请输入数据库密码', required: false),
|
'DB_PASSWORD' => text(label: '请输入数据库密码', required: false),
|
||||||
];
|
];
|
||||||
try {
|
try {
|
||||||
\Config::set("database.default", 'mysql');
|
Config::set("database.default", 'mysql');
|
||||||
\Config::set("database.connections.mysql.host", $envConfig['DB_HOST']);
|
Config::set("database.connections.mysql.host", $envConfig['DB_HOST']);
|
||||||
\Config::set("database.connections.mysql.port", $envConfig['DB_PORT']);
|
Config::set("database.connections.mysql.port", $envConfig['DB_PORT']);
|
||||||
\Config::set("database.connections.mysql.database", $envConfig['DB_DATABASE']);
|
Config::set("database.connections.mysql.database", $envConfig['DB_DATABASE']);
|
||||||
\Config::set("database.connections.mysql.username", $envConfig['DB_USERNAME']);
|
Config::set("database.connections.mysql.username", $envConfig['DB_USERNAME']);
|
||||||
\Config::set("database.connections.mysql.password", $envConfig['DB_PASSWORD']);
|
Config::set("database.connections.mysql.password", $envConfig['DB_PASSWORD']);
|
||||||
\DB::purge('mysql');
|
DB::purge('mysql');
|
||||||
\DB::connection('mysql')->getPdo();
|
DB::connection('mysql')->getPdo();
|
||||||
$isMysqlValid = true;
|
$isMysqlValid = true;
|
||||||
if (!blank(\DB::connection('mysql')->select('SHOW TABLES'))) {
|
if (!blank(DB::connection('mysql')->select('SHOW TABLES'))) {
|
||||||
if (confirm(label: '检测到数据库中已经存在数据,是否要清空数据库以便安装新的数据?', default: false, yes: '清空', no: '不清空')) {
|
if (confirm(label: '检测到数据库中已经存在数据,是否要清空数据库以便安装新的数据?', default: false, yes: '清空', no: '不清空')) {
|
||||||
$this->info('正在清空数据库请稍等');
|
$this->info('正在清空数据库请稍等');
|
||||||
$this->call('db:wipe', ['--force' => true]);
|
$this->call('db:wipe', ['--force' => true]);
|
||||||
@@ -141,12 +145,11 @@ class XboardInstall extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$envConfig['APP_KEY'] = 'base64:' . base64_encode(Encrypter::generateKey('AES-256-CBC'));
|
$envConfig['APP_KEY'] = 'base64:' . base64_encode(Encrypter::generateKey('AES-256-CBC'));
|
||||||
$envConfig['INSTALLED'] = true;
|
|
||||||
$isReidsValid = false;
|
$isReidsValid = false;
|
||||||
while (!$isReidsValid) {
|
while (!$isReidsValid) {
|
||||||
// 判断是否为Docker环境
|
// 判断是否为Docker环境
|
||||||
if ($isDocker == 'true' && ($enableRedis || confirm(label: '是否启用Docker内置的Redis', default: true, yes: '启用', no: '不启用'))) {
|
if ($isDocker == 'true' && ($enableRedis || confirm(label: '是否启用Docker内置的Redis', default: true, yes: '启用', no: '不启用'))) {
|
||||||
$envConfig['REDIS_HOST'] = '/run/redis-socket/redis.sock';
|
$envConfig['REDIS_HOST'] = '/data/redis.sock';
|
||||||
$envConfig['REDIS_PORT'] = 0;
|
$envConfig['REDIS_PORT'] = 0;
|
||||||
$envConfig['REDIS_PASSWORD'] = null;
|
$envConfig['REDIS_PASSWORD'] = null;
|
||||||
} else {
|
} else {
|
||||||
@@ -171,6 +174,8 @@ class XboardInstall extends Command
|
|||||||
// 连接失败,输出错误消息
|
// 连接失败,输出错误消息
|
||||||
$this->error("redis连接失败:" . $e->getMessage());
|
$this->error("redis连接失败:" . $e->getMessage());
|
||||||
$this->info("请重新输入REDIS配置");
|
$this->info("请重新输入REDIS配置");
|
||||||
|
$enableRedis = false;
|
||||||
|
sleep(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,10 +196,10 @@ class XboardInstall extends Command
|
|||||||
$this->saveToEnv($envConfig);
|
$this->saveToEnv($envConfig);
|
||||||
|
|
||||||
$this->call('config:cache');
|
$this->call('config:cache');
|
||||||
\Artisan::call('cache:clear');
|
Artisan::call('cache:clear');
|
||||||
$this->info('正在导入数据库请稍等...');
|
$this->info('正在导入数据库请稍等...');
|
||||||
\Artisan::call("migrate", ['--force' => true]);
|
Artisan::call("migrate", ['--force' => true]);
|
||||||
$this->info(\Artisan::output());
|
$this->info(Artisan::output());
|
||||||
$this->info('数据库导入完成');
|
$this->info('数据库导入完成');
|
||||||
$this->info('开始注册管理员账号');
|
$this->info('开始注册管理员账号');
|
||||||
if (!$this->registerAdmin($email, $password)) {
|
if (!$this->registerAdmin($email, $password)) {
|
||||||
@@ -206,6 +211,8 @@ class XboardInstall extends Command
|
|||||||
|
|
||||||
$defaultSecurePath = hash('crc32b', config('app.key'));
|
$defaultSecurePath = hash('crc32b', config('app.key'));
|
||||||
$this->info("访问 http(s)://你的站点/{$defaultSecurePath} 进入管理面板,你可以在用户中心修改你的密码。");
|
$this->info("访问 http(s)://你的站点/{$defaultSecurePath} 进入管理面板,你可以在用户中心修改你的密码。");
|
||||||
|
$envConfig['INSTALLED'] = true;
|
||||||
|
$this->saveToEnv($envConfig);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->error($e);
|
$this->error($e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ class XboardStatistics extends Command
|
|||||||
{
|
{
|
||||||
$startAt = microtime(true);
|
$startAt = microtime(true);
|
||||||
ini_set('memory_limit', -1);
|
ini_set('memory_limit', -1);
|
||||||
$this->statUser();
|
// $this->statUser();
|
||||||
$this->statServer();
|
// $this->statServer();
|
||||||
$this->stat();
|
$this->stat();
|
||||||
info('统计任务执行完毕。耗时:' . (microtime(true) - $startAt) / 1000);
|
info('统计任务执行完毕。耗时:' . (microtime(true) - $startAt) / 1000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Utils\CacheKey;
|
|||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use App\Services\UserOnlineService;
|
||||||
|
|
||||||
class Kernel extends ConsoleKernel
|
class Kernel extends ConsoleKernel
|
||||||
{
|
{
|
||||||
@@ -44,6 +45,10 @@ class Kernel extends ConsoleKernel
|
|||||||
if (env('ENABLE_AUTO_BACKUP_AND_UPDATE', false)) {
|
if (env('ENABLE_AUTO_BACKUP_AND_UPDATE', false)) {
|
||||||
$schedule->command('backup:database', ['true'])->daily()->onOneServer();
|
$schedule->command('backup:database', ['true'])->daily()->onOneServer();
|
||||||
}
|
}
|
||||||
|
// 每分钟清理过期的在线状态
|
||||||
|
$schedule->call(function () {
|
||||||
|
app(UserOnlineService::class)->cleanExpiredOnlineStatus();
|
||||||
|
})->everyMinute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
10
app/Contracts/PaymentInterface.php
Normal file
10
app/Contracts/PaymentInterface.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
interface PaymentInterface
|
||||||
|
{
|
||||||
|
public function form(): array;
|
||||||
|
public function pay($order): array;
|
||||||
|
public function notify($params);
|
||||||
|
}
|
||||||
12
app/Contracts/ProtocolInterface.php
Normal file
12
app/Contracts/ProtocolInterface.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
interface ProtocolInterface
|
||||||
|
{
|
||||||
|
public function getFlags(): array;
|
||||||
|
/**
|
||||||
|
* 处理并生成配置
|
||||||
|
*/
|
||||||
|
public function handle();
|
||||||
|
}
|
||||||
@@ -1,15 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
use App\Support\Setting;
|
use App\Support\Setting;
|
||||||
|
|
||||||
|
|
||||||
if (! function_exists("get_request_content")){
|
|
||||||
function get_request_content(){
|
|
||||||
|
|
||||||
return request()->getContent() ?: json_encode($_POST);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! function_exists('admin_setting')) {
|
if (! function_exists('admin_setting')) {
|
||||||
/**
|
/**
|
||||||
* 获取或保存配置参数.
|
* 获取或保存配置参数.
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\CouponGenerate;
|
|
||||||
use App\Http\Requests\Admin\CouponSave;
|
|
||||||
use App\Models\Coupon;
|
|
||||||
use App\Utils\Helper;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class CouponController extends Controller
|
|
||||||
{
|
|
||||||
public function fetch(Request $request)
|
|
||||||
{
|
|
||||||
$current = $request->input('current') ? $request->input('current') : 1;
|
|
||||||
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
|
|
||||||
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
|
|
||||||
$sort = $request->input('sort') ? $request->input('sort') : 'id';
|
|
||||||
$builder = Coupon::orderBy($sort, $sortType);
|
|
||||||
$total = $builder->count();
|
|
||||||
$coupons = $builder->forPage($current, $pageSize)
|
|
||||||
->get();
|
|
||||||
return response([
|
|
||||||
'data' => $coupons,
|
|
||||||
'total' => $total
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function show(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'id' => 'required|numeric'
|
|
||||||
],[
|
|
||||||
'id.required' => '优惠券ID不能为空',
|
|
||||||
'id.numeric' => '优惠券ID必须为数字'
|
|
||||||
]);
|
|
||||||
$coupon = Coupon::find($request->input('id'));
|
|
||||||
if (!$coupon) {
|
|
||||||
return $this->fail([400202,'优惠券不存在']);
|
|
||||||
}
|
|
||||||
$coupon->show = !$coupon->show;
|
|
||||||
if (!$coupon->save()) {
|
|
||||||
return $this->fail([500,'保存失败']);
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generate(CouponGenerate $request)
|
|
||||||
{
|
|
||||||
if ($request->input('generate_count')) {
|
|
||||||
$this->multiGenerate($request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$params = $request->validated();
|
|
||||||
if (!$request->input('id')) {
|
|
||||||
if (!isset($params['code'])) {
|
|
||||||
$params['code'] = Helper::randomChar(8);
|
|
||||||
}
|
|
||||||
if (!Coupon::create($params)) {
|
|
||||||
return $this->fail([500,'创建失败']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Coupon::find($request->input('id'))->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500,'保存失败']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function multiGenerate(CouponGenerate $request)
|
|
||||||
{
|
|
||||||
$coupons = [];
|
|
||||||
$coupon = $request->validated();
|
|
||||||
$coupon['created_at'] = $coupon['updated_at'] = time();
|
|
||||||
$coupon['show'] = 1;
|
|
||||||
unset($coupon['generate_count']);
|
|
||||||
for ($i = 0;$i < $request->input('generate_count');$i++) {
|
|
||||||
$coupon['code'] = Helper::randomChar(8);
|
|
||||||
array_push($coupons, $coupon);
|
|
||||||
}
|
|
||||||
try{
|
|
||||||
DB::beginTransaction();
|
|
||||||
if (!Coupon::insert(array_map(function ($item) use ($coupon) {
|
|
||||||
// format data
|
|
||||||
if (isset($item['limit_plan_ids']) && is_array($item['limit_plan_ids'])) {
|
|
||||||
$item['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
|
|
||||||
}
|
|
||||||
if (isset($item['limit_period']) && is_array($item['limit_period'])) {
|
|
||||||
$item['limit_period'] = json_encode($coupon['limit_period']);
|
|
||||||
}
|
|
||||||
return $item;
|
|
||||||
}, $coupons))) {
|
|
||||||
throw new \Exception();
|
|
||||||
}
|
|
||||||
DB::commit();
|
|
||||||
}catch(\Exception $e){
|
|
||||||
DB::rollBack();
|
|
||||||
return $this->fail([500, '生成失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = "名称,类型,金额或比例,开始时间,结束时间,可用次数,可用于订阅,券码,生成时间\r\n";
|
|
||||||
foreach($coupons as $coupon) {
|
|
||||||
$type = ['', '金额', '比例'][$coupon['type']];
|
|
||||||
$value = ['', ($coupon['value'] / 100),$coupon['value']][$coupon['type']];
|
|
||||||
$startTime = date('Y-m-d H:i:s', $coupon['started_at']);
|
|
||||||
$endTime = date('Y-m-d H:i:s', $coupon['ended_at']);
|
|
||||||
$limitUse = $coupon['limit_use'] ?? '不限制';
|
|
||||||
$createTime = date('Y-m-d H:i:s', $coupon['created_at']);
|
|
||||||
$limitPlanIds = isset($coupon['limit_plan_ids']) ? implode("/", $coupon['limit_plan_ids']) : '不限制';
|
|
||||||
$data .= "{$coupon['name']},{$type},{$value},{$startTime},{$endTime},{$limitUse},{$limitPlanIds},{$coupon['code']},{$createTime}\r\n";
|
|
||||||
}
|
|
||||||
echo $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function drop(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'id' => 'required|numeric'
|
|
||||||
],[
|
|
||||||
'id.required' => '优惠券ID不能为空',
|
|
||||||
'id.numeric' => '优惠券ID必须为数字'
|
|
||||||
]);
|
|
||||||
$coupon = Coupon::find($request->input('id'));
|
|
||||||
if (!$coupon) {
|
|
||||||
return $this->fail([400202,'优惠券不存在']);
|
|
||||||
}
|
|
||||||
if (!$coupon->delete()) {
|
|
||||||
return $this->fail([500,'删除失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\NoticeSave;
|
|
||||||
use App\Models\Notice;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class NoticeController extends Controller
|
|
||||||
{
|
|
||||||
public function fetch(Request $request)
|
|
||||||
{
|
|
||||||
return $this->success(Notice::orderBy('id', 'DESC')->get());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save(NoticeSave $request)
|
|
||||||
{
|
|
||||||
$data = $request->only([
|
|
||||||
'title',
|
|
||||||
'content',
|
|
||||||
'img_url',
|
|
||||||
'tags'
|
|
||||||
]);
|
|
||||||
if (!$request->input('id')) {
|
|
||||||
if (!Notice::create($data)) {
|
|
||||||
return $this->fail([500 ,'保存失败']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Notice::find($request->input('id'))->update($data);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->fail([500 ,'保存失败']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function show(Request $request)
|
|
||||||
{
|
|
||||||
if (empty($request->input('id'))) {
|
|
||||||
return $this->fail([500 ,'公告ID不能为空']);
|
|
||||||
}
|
|
||||||
$notice = Notice::find($request->input('id'));
|
|
||||||
if (!$notice) {
|
|
||||||
return $this->fail([400202 ,'公告不存在']);
|
|
||||||
}
|
|
||||||
$notice->show = $notice->show ? 0 : 1;
|
|
||||||
if (!$notice->save()) {
|
|
||||||
return $this->fail([500 ,'保存失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function drop(Request $request)
|
|
||||||
{
|
|
||||||
if (empty($request->input('id'))) {
|
|
||||||
return $this->fail([422 ,'公告ID不能为空']);
|
|
||||||
}
|
|
||||||
$notice = Notice::find($request->input('id'));
|
|
||||||
if (!$notice) {
|
|
||||||
return $this->fail([400202 ,'公告不存在']);
|
|
||||||
}
|
|
||||||
if (!$notice->delete()) {
|
|
||||||
return $this->fail([500 ,'删除失败']);
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\OrderAssign;
|
|
||||||
use App\Http\Requests\Admin\OrderFetch;
|
|
||||||
use App\Http\Requests\Admin\OrderUpdate;
|
|
||||||
use App\Models\CommissionLog;
|
|
||||||
use App\Models\Order;
|
|
||||||
use App\Models\Plan;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Services\OrderService;
|
|
||||||
use App\Services\UserService;
|
|
||||||
use App\Utils\Helper;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class OrderController extends Controller
|
|
||||||
{
|
|
||||||
private function filter(Request $request, &$builder)
|
|
||||||
{
|
|
||||||
if ($request->input('filter')) {
|
|
||||||
foreach ($request->input('filter') as $filter) {
|
|
||||||
if ($filter['key'] === 'email') {
|
|
||||||
$user = User::where('email', "%{$filter['value']}%")->first();
|
|
||||||
if (!$user) continue;
|
|
||||||
$builder->where('user_id', $user->id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($filter['condition'] === '模糊') {
|
|
||||||
$filter['condition'] = 'like';
|
|
||||||
$filter['value'] = "%{$filter['value']}%";
|
|
||||||
}
|
|
||||||
$builder->where($filter['key'], $filter['condition'], $filter['value']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function detail(Request $request)
|
|
||||||
{
|
|
||||||
$order = Order::find($request->input('id'));
|
|
||||||
if (!$order) return $this->fail([400202 ,'订单不存在']);
|
|
||||||
$order['commission_log'] = CommissionLog::where('trade_no', $order->trade_no)->get();
|
|
||||||
if ($order->surplus_order_ids) {
|
|
||||||
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
|
|
||||||
}
|
|
||||||
return $this->success($order);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fetch(OrderFetch $request)
|
|
||||||
{
|
|
||||||
$current = $request->input('current') ? $request->input('current') : 1;
|
|
||||||
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
|
|
||||||
$orderModel = Order::orderBy('created_at', 'DESC');
|
|
||||||
if ($request->input('is_commission')) {
|
|
||||||
$orderModel->where('invite_user_id', '!=', NULL);
|
|
||||||
$orderModel->whereNotIn('status', [0, 2]);
|
|
||||||
$orderModel->where('commission_balance', '>', 0);
|
|
||||||
}
|
|
||||||
$this->filter($request, $orderModel);
|
|
||||||
$total = $orderModel->count();
|
|
||||||
$res = $orderModel->forPage($current, $pageSize)
|
|
||||||
->get();
|
|
||||||
$plan = Plan::get();
|
|
||||||
for ($i = 0; $i < count($res); $i++) {
|
|
||||||
for ($k = 0; $k < count($plan); $k++) {
|
|
||||||
if ($plan[$k]['id'] == $res[$i]['plan_id']) {
|
|
||||||
$res[$i]['plan_name'] = $plan[$k]['name'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response([
|
|
||||||
'data' => $res,
|
|
||||||
'total' => $total
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function paid(Request $request)
|
|
||||||
{
|
|
||||||
$order = Order::where('trade_no', $request->input('trade_no'))
|
|
||||||
->first();
|
|
||||||
if (!$order) {
|
|
||||||
return $this->fail([400202 ,'订单不存在']);
|
|
||||||
}
|
|
||||||
if ($order->status !== 0) return $this->fail([400 ,'只能对待支付的订单进行操作']);
|
|
||||||
|
|
||||||
$orderService = new OrderService($order);
|
|
||||||
if (!$orderService->paid('manual_operation')) {
|
|
||||||
return $this->fail([500 ,'更新失败']);
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cancel(Request $request)
|
|
||||||
{
|
|
||||||
$order = Order::where('trade_no', $request->input('trade_no'))
|
|
||||||
->first();
|
|
||||||
if (!$order) {
|
|
||||||
return $this->fail([400202 ,'订单不存在']);
|
|
||||||
}
|
|
||||||
if ($order->status !== 0) return $this->fail([400 ,'只能对待支付的订单进行操作']);
|
|
||||||
|
|
||||||
$orderService = new OrderService($order);
|
|
||||||
if (!$orderService->cancel()) {
|
|
||||||
return $this->fail([400 ,'更新失败']);
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(OrderUpdate $request)
|
|
||||||
{
|
|
||||||
$params = $request->only([
|
|
||||||
'commission_status'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$order = Order::where('trade_no', $request->input('trade_no'))
|
|
||||||
->first();
|
|
||||||
if (!$order) {
|
|
||||||
return $this->fail([400202 ,'订单不存在']);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$order->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500 ,'更新失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function assign(OrderAssign $request)
|
|
||||||
{
|
|
||||||
$plan = Plan::find($request->input('plan_id'));
|
|
||||||
$user = User::where('email', $request->input('email'))->first();
|
|
||||||
|
|
||||||
if (!$user) {
|
|
||||||
return $this->fail([400202 ,'该用户不存在']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$plan) {
|
|
||||||
return $this->fail([400202 ,'该订阅不存在']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$userService = new UserService();
|
|
||||||
if ($userService->isNotCompleteOrderByUserId($user->id)) {
|
|
||||||
return $this->fail([400 ,'该用户还有待支付的订单,无法分配']);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
DB::beginTransaction();
|
|
||||||
$order = new Order();
|
|
||||||
$orderService = new OrderService($order);
|
|
||||||
$order->user_id = $user->id;
|
|
||||||
$order->plan_id = $plan->id;
|
|
||||||
$order->period = $request->input('period');
|
|
||||||
$order->trade_no = Helper::guid();
|
|
||||||
$order->total_amount = $request->input('total_amount');
|
|
||||||
|
|
||||||
if ($order->period === 'reset_price') {
|
|
||||||
$order->type = Order::TYPE_RESET_TRAFFIC;
|
|
||||||
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
|
|
||||||
$order->type = Order::TYPE_UPGRADE;
|
|
||||||
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
|
|
||||||
$order->type = Order::TYPE_RENEWAL;
|
|
||||||
} else {
|
|
||||||
$order->type = Order::TYPE_NEW_PURCHASE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$orderService->setInvite($user);
|
|
||||||
|
|
||||||
if (!$order->save()) {
|
|
||||||
DB::rollBack();
|
|
||||||
return $this->fail([500 ,'订单创建失败']);
|
|
||||||
}
|
|
||||||
DB::commit();
|
|
||||||
}catch(\Exception $e){
|
|
||||||
DB::rollBack();
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success($order->trade_no);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin\Server;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Plan;
|
|
||||||
use App\Models\ServerGroup;
|
|
||||||
use App\Models\ServerVmess;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Services\ServerService;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class GroupController extends Controller
|
|
||||||
{
|
|
||||||
public function fetch(Request $request)
|
|
||||||
{
|
|
||||||
if ($request->input('group_id')) {
|
|
||||||
return $this->success([ServerGroup::find($request->input('group_id'))]);
|
|
||||||
}
|
|
||||||
$serverGroups = ServerGroup::get();
|
|
||||||
$servers = ServerService::getAllServers();
|
|
||||||
foreach ($serverGroups as $k => $v) {
|
|
||||||
$serverGroups[$k]['user_count'] = User::where('group_id', $v['id'])->count();
|
|
||||||
$serverGroups[$k]['server_count'] = 0;
|
|
||||||
foreach ($servers as $server) {
|
|
||||||
if (in_array($v['id'], $server['group_id'])) {
|
|
||||||
$serverGroups[$k]['server_count'] = $serverGroups[$k]['server_count']+1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($serverGroups);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save(Request $request)
|
|
||||||
{
|
|
||||||
if (empty($request->input('name'))) {
|
|
||||||
return $this->fail([422,'组名不能为空']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$serverGroup = ServerGroup::find($request->input('id'));
|
|
||||||
} else {
|
|
||||||
$serverGroup = new ServerGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
$serverGroup->name = $request->input('name');
|
|
||||||
return $this->success($serverGroup->save());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function drop(Request $request)
|
|
||||||
{
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$serverGroup = ServerGroup::find($request->input('id'));
|
|
||||||
if (!$serverGroup) {
|
|
||||||
return $this->fail([400202,'组不存在']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$servers = ServerVmess::all();
|
|
||||||
foreach ($servers as $server) {
|
|
||||||
if (in_array($request->input('id'), $server->group_id)) {
|
|
||||||
return $this->fail([400,'该组已被节点所使用,无法删除']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Plan::where('group_id', $request->input('id'))->first()) {
|
|
||||||
return $this->fail([400, '该组已被订阅所使用,无法删除']);
|
|
||||||
}
|
|
||||||
if (User::where('group_id', $request->input('id'))->first()) {
|
|
||||||
return $this->fail([400, '该组已被用户所使用,无法删除']);
|
|
||||||
}
|
|
||||||
return $this->success($serverGroup->delete());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin\Server;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\ServerHysteria;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class HysteriaController extends Controller
|
|
||||||
{
|
|
||||||
public function save(Request $request)
|
|
||||||
{
|
|
||||||
$params = $request->validate([
|
|
||||||
'show' => '',
|
|
||||||
'name' => 'required',
|
|
||||||
'group_id' => 'required|array',
|
|
||||||
'route_id' => 'nullable|array',
|
|
||||||
'parent_id' => 'nullable|integer',
|
|
||||||
'host' => 'required',
|
|
||||||
'port' => 'required',
|
|
||||||
'server_port' => 'required',
|
|
||||||
'tags' => 'nullable|array',
|
|
||||||
'excludes' => 'nullable|array',
|
|
||||||
'ips' => 'nullable|array',
|
|
||||||
'rate' => 'required|numeric',
|
|
||||||
'up_mbps' => 'required|numeric|min:1',
|
|
||||||
'down_mbps' => 'required|numeric|min:1',
|
|
||||||
'server_name' => 'nullable',
|
|
||||||
'insecure' => 'required|in:0,1',
|
|
||||||
'alpn' => 'nullable|in:0,1,2,3',
|
|
||||||
'version' => 'nullable|in:1,2',
|
|
||||||
'is_obfs' => 'nullable'
|
|
||||||
],[
|
|
||||||
'name.required' => '节点名称不能为空',
|
|
||||||
'group_id.required' => '权限组不能为空',
|
|
||||||
'host.required' => '节点地址不能为空',
|
|
||||||
'port.required' => '连接端口不能为空',
|
|
||||||
'server_port' => '服务端口不能为空',
|
|
||||||
'rate.required' => '倍率不能为空',
|
|
||||||
'up_mbps.required' => '上行带宽不能为空',
|
|
||||||
'down_mbps.required' => '下行带宽不能为空',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerHysteria::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500,'保存失败']);
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ServerHysteria::create($params)) {
|
|
||||||
return $this->fail([500,'创建失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function drop(Request $request)
|
|
||||||
{
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerHysteria::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'节点ID不存在']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($server->delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'show' => 'in:0,1'
|
|
||||||
], [
|
|
||||||
'show.in' => '显示状态格式不正确'
|
|
||||||
]);
|
|
||||||
$params = $request->only([
|
|
||||||
'show',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$server = ServerHysteria::find($request->input('id'));
|
|
||||||
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'该服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500,'保存失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function copy(Request $request)
|
|
||||||
{
|
|
||||||
$server = ServerHysteria::find($request->input('id'));
|
|
||||||
$server->show = 0;
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'服务器不存在']);
|
|
||||||
}
|
|
||||||
ServerHysteria::create($server->toArray());
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin\Server;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Services\ServerService;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class ManageController extends Controller
|
|
||||||
{
|
|
||||||
public function getNodes(Request $request)
|
|
||||||
{
|
|
||||||
return $this->success(ServerService::getAllServers());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function sort(Request $request)
|
|
||||||
{
|
|
||||||
ini_set('post_max_size', '1m');
|
|
||||||
$params = $request->only(
|
|
||||||
'shadowsocks',
|
|
||||||
'vmess',
|
|
||||||
'trojan',
|
|
||||||
'hysteria',
|
|
||||||
'vless'
|
|
||||||
) ?? [];
|
|
||||||
try{
|
|
||||||
DB::beginTransaction();
|
|
||||||
foreach ($params as $k => $v) {
|
|
||||||
$model = 'App\\Models\\Server' . ucfirst($k);
|
|
||||||
foreach($v as $id => $sort) {
|
|
||||||
$model::where('id', $id)->update(['sort' => $sort]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DB::commit();
|
|
||||||
}catch (\Exception $e){
|
|
||||||
DB::rollBack();
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500,'保存失败']);
|
|
||||||
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin\Server;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\ServerShadowsocksSave;
|
|
||||||
use App\Http\Requests\Admin\ServerShadowsocksUpdate;
|
|
||||||
use App\Models\ServerShadowsocks;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class ShadowsocksController extends Controller
|
|
||||||
{
|
|
||||||
public function save(ServerShadowsocksSave $request)
|
|
||||||
{
|
|
||||||
$params = $request->validated();
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerShadowsocks::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
return $this->success(true);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500,'保存失败']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
ServerShadowsocks::create($params);
|
|
||||||
return $this->success(true);
|
|
||||||
}catch(\Exception $e){
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500,'创建失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function drop(Request $request)
|
|
||||||
{
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerShadowsocks::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '节点不存在']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($server->delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(ServerShadowsocksUpdate $request)
|
|
||||||
{
|
|
||||||
$params = $request->only([
|
|
||||||
'show',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$server = ServerShadowsocks::find($request->input('id'));
|
|
||||||
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '该服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500,'保存失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function copy(Request $request)
|
|
||||||
{
|
|
||||||
$server = ServerShadowsocks::find($request->input('id'));
|
|
||||||
$server->show = 0;
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'服务器不存在']);
|
|
||||||
}
|
|
||||||
ServerShadowsocks::create($server->toArray());
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin\Server;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\ServerTrojanSave;
|
|
||||||
use App\Http\Requests\Admin\ServerTrojanUpdate;
|
|
||||||
use App\Models\ServerTrojan;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class TrojanController extends Controller
|
|
||||||
{
|
|
||||||
public function save(ServerTrojanSave $request)
|
|
||||||
{
|
|
||||||
$params = $request->validated();
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerTrojan::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500, '保存失败']);
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerTrojan::create($params);
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function drop(Request $request)
|
|
||||||
{
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerTrojan::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'节点ID不存在']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($server->delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(ServerTrojanUpdate $request)
|
|
||||||
{
|
|
||||||
$params = $request->only([
|
|
||||||
'show',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$server = ServerTrojan::find($request->input('id'));
|
|
||||||
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'该服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500,'保存失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function copy(Request $request)
|
|
||||||
{
|
|
||||||
$server = ServerTrojan::find($request->input('id'));
|
|
||||||
$server->show = 0;
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'服务器不存在']);
|
|
||||||
}
|
|
||||||
ServerTrojan::create($server->toArray());
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin\Server;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\ServerVless;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use ParagonIE_Sodium_Compat as SodiumCompat;
|
|
||||||
use App\Utils\Helper;
|
|
||||||
|
|
||||||
class VlessController extends Controller
|
|
||||||
{
|
|
||||||
public function save(Request $request)
|
|
||||||
{
|
|
||||||
$params = $request->validate([
|
|
||||||
'group_id' => 'required',
|
|
||||||
'route_id' => 'nullable|array',
|
|
||||||
'name' => 'required',
|
|
||||||
'parent_id' => 'nullable|integer',
|
|
||||||
'host' => 'required',
|
|
||||||
'port' => 'required',
|
|
||||||
'server_port' => 'required',
|
|
||||||
'tls' => 'required|in:0,1,2',
|
|
||||||
'tls_settings' => 'nullable|array',
|
|
||||||
'flow' => 'nullable|in:xtls-rprx-vision',
|
|
||||||
'network' => 'required',
|
|
||||||
'network_settings' => 'nullable|array',
|
|
||||||
'tags' => 'nullable|array',
|
|
||||||
'excludes' => 'nullable|array',
|
|
||||||
'ips' => 'nullable|array',
|
|
||||||
'rate' => 'required',
|
|
||||||
'show' => 'nullable|in:0,1',
|
|
||||||
'sort' => 'nullable'
|
|
||||||
],[
|
|
||||||
'name.required' => '节点名称不能为空',
|
|
||||||
'group_id.required' => '权限组不能为空',
|
|
||||||
'host.required' => '节点地址不能为空',
|
|
||||||
'port.required' => '连接端口不能为空',
|
|
||||||
'server_port' => '服务端口不能为空',
|
|
||||||
'rate.required' => '倍率不能为空',
|
|
||||||
'network.required' => '协议不能为空',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isset($params['tls']) && (int)$params['tls'] === 2) {
|
|
||||||
$keyPair = SodiumCompat::crypto_box_keypair();
|
|
||||||
$params['tls_settings'] = $params['tls_settings'] ?? [];
|
|
||||||
if (!isset($params['tls_settings']['public_key'])) {
|
|
||||||
$params['tls_settings']['public_key'] = Helper::base64EncodeUrlSafe(SodiumCompat::crypto_box_publickey($keyPair));
|
|
||||||
}
|
|
||||||
if (!isset($params['tls_settings']['private_key'])) {
|
|
||||||
$params['tls_settings']['private_key'] = Helper::base64EncodeUrlSafe(SodiumCompat::crypto_box_secretkey($keyPair));
|
|
||||||
}
|
|
||||||
if (!isset($params['tls_settings']['short_id'])) {
|
|
||||||
$params['tls_settings']['short_id'] = substr(sha1($params['tls_settings']['private_key']), 0, 8);
|
|
||||||
}
|
|
||||||
if (!isset($params['tls_settings']['server_port'])) {
|
|
||||||
$params['tls_settings']['server_port'] = "443";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerVless::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500, '保存失败']);
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
ServerVless::create($params);
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function drop(Request $request)
|
|
||||||
{
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerVless::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202,'节点不存在']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($server->delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(Request $request)
|
|
||||||
{
|
|
||||||
$params = $request->validate([
|
|
||||||
'show' => 'nullable|in:0,1',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$server = ServerVless::find($request->input('id'));
|
|
||||||
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '该服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500, '保存失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function copy(Request $request)
|
|
||||||
{
|
|
||||||
$server = ServerVless::find($request->input('id'));
|
|
||||||
$server->show = 0;
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '该服务器不存在']);
|
|
||||||
}
|
|
||||||
ServerVless::create($server->toArray());
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin\Server;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\ServerVmessSave;
|
|
||||||
use App\Http\Requests\Admin\ServerVmessUpdate;
|
|
||||||
use App\Models\ServerVmess;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class VmessController extends Controller
|
|
||||||
{
|
|
||||||
public function save(ServerVmessSave $request)
|
|
||||||
{
|
|
||||||
$params = $request->validated();
|
|
||||||
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerVmess::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500, '保存失败']);
|
|
||||||
}
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerVmess::create($params);
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function drop(Request $request)
|
|
||||||
{
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$server = ServerVmess::find($request->input('id'));
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '节点不存在']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($server->delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(ServerVmessUpdate $request)
|
|
||||||
{
|
|
||||||
$params = $request->only([
|
|
||||||
'show',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$server = ServerVmess::find($request->input('id'));
|
|
||||||
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '该服务器不存在']);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$server->update($params);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
return $this->fail([500, '保存失败']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function copy(Request $request)
|
|
||||||
{
|
|
||||||
$server = ServerVmess::find($request->input('id'));
|
|
||||||
$server->show = 0;
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400202, '该服务器不存在']);
|
|
||||||
}
|
|
||||||
ServerVmess::create($server->toArray());
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\CommissionLog;
|
|
||||||
use App\Models\Order;
|
|
||||||
use App\Models\ServerHysteria;
|
|
||||||
use App\Models\ServerVless;
|
|
||||||
use App\Models\ServerShadowsocks;
|
|
||||||
use App\Models\ServerTrojan;
|
|
||||||
use App\Models\ServerVmess;
|
|
||||||
use App\Models\Stat;
|
|
||||||
use App\Models\StatServer;
|
|
||||||
use App\Models\StatUser;
|
|
||||||
use App\Models\Ticket;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Services\StatisticalService;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class StatController extends Controller
|
|
||||||
{
|
|
||||||
public function getOverride(Request $request)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'data' => [
|
|
||||||
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
|
|
||||||
->where('created_at', '<', time())
|
|
||||||
->whereNotIn('status', [0, 2])
|
|
||||||
->sum('total_amount'),
|
|
||||||
'month_register_total' => User::where('created_at', '>=', strtotime(date('Y-m-1')))
|
|
||||||
->where('created_at', '<', time())
|
|
||||||
->count(),
|
|
||||||
'ticket_pending_total' => Ticket::where('status', 0)
|
|
||||||
->count(),
|
|
||||||
'commission_pending_total' => Order::where('commission_status', 0)
|
|
||||||
->where('invite_user_id', '!=', NULL)
|
|
||||||
->whereNotIn('status', [0, 2])
|
|
||||||
->where('commission_balance', '>', 0)
|
|
||||||
->count(),
|
|
||||||
'day_income' => Order::where('created_at', '>=', strtotime(date('Y-m-d')))
|
|
||||||
->where('created_at', '<', time())
|
|
||||||
->whereNotIn('status', [0, 2])
|
|
||||||
->sum('total_amount'),
|
|
||||||
'last_month_income' => Order::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
|
|
||||||
->where('created_at', '<', strtotime(date('Y-m-1')))
|
|
||||||
->whereNotIn('status', [0, 2])
|
|
||||||
->sum('total_amount'),
|
|
||||||
'commission_month_payout' => CommissionLog::where('created_at', '>=', strtotime(date('Y-m-1')))
|
|
||||||
->where('created_at', '<', time())
|
|
||||||
->sum('get_amount'),
|
|
||||||
'commission_last_month_payout' => CommissionLog::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
|
|
||||||
->where('created_at', '<', strtotime(date('Y-m-1')))
|
|
||||||
->sum('get_amount'),
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOrder(Request $request)
|
|
||||||
{
|
|
||||||
$statistics = Stat::where('record_type', 'd')
|
|
||||||
->limit(31)
|
|
||||||
->orderBy('record_at', 'DESC')
|
|
||||||
->get()
|
|
||||||
->toArray();
|
|
||||||
$result = [];
|
|
||||||
foreach ($statistics as $statistic) {
|
|
||||||
$date = date('m-d', $statistic['record_at']);
|
|
||||||
$result[] = [
|
|
||||||
'type' => '收款金额',
|
|
||||||
'date' => $date,
|
|
||||||
'value' => $statistic['paid_total'] / 100
|
|
||||||
];
|
|
||||||
$result[] = [
|
|
||||||
'type' => '收款笔数',
|
|
||||||
'date' => $date,
|
|
||||||
'value' => $statistic['paid_count']
|
|
||||||
];
|
|
||||||
$result[] = [
|
|
||||||
'type' => '佣金金额(已发放)',
|
|
||||||
'date' => $date,
|
|
||||||
'value' => $statistic['commission_total'] / 100
|
|
||||||
];
|
|
||||||
$result[] = [
|
|
||||||
'type' => '佣金笔数(已发放)',
|
|
||||||
'date' => $date,
|
|
||||||
'value' => $statistic['commission_count']
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$result = array_reverse($result);
|
|
||||||
return [
|
|
||||||
'data' => $result
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当日实时流量排行
|
|
||||||
public function getServerLastRank()
|
|
||||||
{
|
|
||||||
$servers = [
|
|
||||||
'shadowsocks' => ServerShadowsocks::with(['parent'])->get()->toArray(),
|
|
||||||
'v2ray' => ServerVmess::with(['parent'])->get()->toArray(),
|
|
||||||
'trojan' => ServerTrojan::with(['parent'])->get()->toArray(),
|
|
||||||
'vmess' => ServerVmess::with(['parent'])->get()->toArray(),
|
|
||||||
'hysteria' => ServerHysteria::with(['parent'])->get()->toArray(),
|
|
||||||
'vless' => ServerVless::with(['parent'])->get()->toArray(),
|
|
||||||
];
|
|
||||||
|
|
||||||
$recordAt = strtotime(date('Y-m-d'));
|
|
||||||
$statService = new StatisticalService();
|
|
||||||
$statService->setStartAt($recordAt);
|
|
||||||
$stats = $statService->getStatServer();
|
|
||||||
$statistics = collect($stats)->map(function ($item){
|
|
||||||
$item['total'] = $item['u'] + $item['d'];
|
|
||||||
return $item;
|
|
||||||
})->sortByDesc('total')->values()->all();
|
|
||||||
foreach ($statistics as $k => $v) {
|
|
||||||
foreach ($servers[$v['server_type']] as $server) {
|
|
||||||
if ($server['id'] === $v['server_id']) {
|
|
||||||
$statistics[$k]['server_name'] = $server['name'];
|
|
||||||
if($server['parent']) $statistics[$k]['server_name'] .= "({$server['parent']['name']})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824;
|
|
||||||
}
|
|
||||||
array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics);
|
|
||||||
return [
|
|
||||||
'data' => collect($statistics)->take(15)->all()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
// 获取昨日节点流量排行
|
|
||||||
public function getServerYesterdayRank()
|
|
||||||
{
|
|
||||||
$servers = [
|
|
||||||
'shadowsocks' => ServerShadowsocks::with(['parent'])->get()->toArray(),
|
|
||||||
'v2ray' => ServerVmess::with(['parent'])->get()->toArray(),
|
|
||||||
'trojan' => ServerTrojan::with(['parent'])->get()->toArray(),
|
|
||||||
'vmess' => ServerVmess::with(['parent'])->get()->toArray(),
|
|
||||||
'hysteria' => ServerHysteria::with(['parent'])->get()->toArray(),
|
|
||||||
'vless' => ServerVless::with(['parent'])->get()->toArray(),
|
|
||||||
];
|
|
||||||
$startAt = strtotime('-1 day', strtotime(date('Y-m-d')));
|
|
||||||
$endAt = strtotime(date('Y-m-d'));
|
|
||||||
$statistics = StatServer::select([
|
|
||||||
'server_id',
|
|
||||||
'server_type',
|
|
||||||
'u',
|
|
||||||
'd',
|
|
||||||
DB::raw('(u+d) as total')
|
|
||||||
])
|
|
||||||
->where('record_at', '>=', $startAt)
|
|
||||||
->where('record_at', '<', $endAt)
|
|
||||||
->where('record_type', 'd')
|
|
||||||
->limit(15)
|
|
||||||
->orderBy('total', 'DESC')
|
|
||||||
->get()
|
|
||||||
->toArray();
|
|
||||||
foreach ($statistics as $k => $v) {
|
|
||||||
foreach ($servers[$v['server_type']] as $server) {
|
|
||||||
if ($server['id'] === $v['server_id']) {
|
|
||||||
$statistics[$k]['server_name'] = $server['name'];
|
|
||||||
if($server['parent']) $statistics[$k]['server_name'] .= "({$server['parent']['name']})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824;
|
|
||||||
}
|
|
||||||
array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics);
|
|
||||||
return [
|
|
||||||
'data' => $statistics
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStatUser(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'user_id' => 'required|integer'
|
|
||||||
]);
|
|
||||||
$current = $request->input('current') ? $request->input('current') : 1;
|
|
||||||
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
|
|
||||||
$builder = StatUser::orderBy('record_at', 'DESC')->where('user_id', $request->input('user_id'));
|
|
||||||
|
|
||||||
$total = $builder->count();
|
|
||||||
$records = $builder->forPage($current, $pageSize)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// 追加当天流量
|
|
||||||
$recordAt = strtotime(date('Y-m-d'));
|
|
||||||
$statService = new StatisticalService();
|
|
||||||
$statService->setStartAt($recordAt);
|
|
||||||
$todayTraffics = $statService->getStatUserByUserID($request->input('user_id'));
|
|
||||||
if (($current == 1) && count($todayTraffics) > 0) {
|
|
||||||
foreach ($todayTraffics as $todayTraffic){
|
|
||||||
$todayTraffic['server_rate'] = number_format($todayTraffic['server_rate'], 2);
|
|
||||||
$records->prepend($todayTraffic);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [
|
|
||||||
'data' => $records,
|
|
||||||
'total' => $total + count($todayTraffics),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Services\ThemeService;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\File;
|
|
||||||
|
|
||||||
class ThemeController extends Controller
|
|
||||||
{
|
|
||||||
private $themes;
|
|
||||||
private $path;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->path = $path = public_path('theme/');
|
|
||||||
$this->themes = array_map(function ($item) use ($path) {
|
|
||||||
return str_replace($path, '', $item);
|
|
||||||
}, glob($path . '*'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getThemes()
|
|
||||||
{
|
|
||||||
$themeConfigs = [];
|
|
||||||
foreach ($this->themes as $theme) {
|
|
||||||
$themeConfigFile = $this->path . "{$theme}/config.json";
|
|
||||||
if (!File::exists($themeConfigFile)) continue;
|
|
||||||
$themeConfig = json_decode(File::get($themeConfigFile), true);
|
|
||||||
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) continue;
|
|
||||||
$themeConfigs[$theme] = $themeConfig;
|
|
||||||
if (admin_setting("theme_{$theme}")) continue;
|
|
||||||
$themeService = new ThemeService($theme);
|
|
||||||
$themeService->init();
|
|
||||||
}
|
|
||||||
$data = [
|
|
||||||
'themes' => $themeConfigs,
|
|
||||||
'active' => admin_setting('frontend_theme', 'Xboard')
|
|
||||||
];
|
|
||||||
return $this->success($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getThemeConfig(Request $request)
|
|
||||||
{
|
|
||||||
$payload = $request->validate([
|
|
||||||
'name' => 'required|in:' . join(',', $this->themes)
|
|
||||||
]);
|
|
||||||
return $this->success(admin_setting("theme_{$payload['name']}") ?? config("theme.{$payload['name']}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveThemeConfig(Request $request)
|
|
||||||
{
|
|
||||||
$payload = $request->validate([
|
|
||||||
'name' => 'required|in:' . join(',', $this->themes),
|
|
||||||
'config' => 'required'
|
|
||||||
]);
|
|
||||||
$payload['config'] = json_decode(base64_decode($payload['config']), true);
|
|
||||||
if (!$payload['config'] || !is_array($payload['config'])) return $this->fail([422,'参数不正确']);
|
|
||||||
$themeConfigFile = public_path("theme/{$payload['name']}/config.json");
|
|
||||||
if (!File::exists($themeConfigFile)) return $this->fail([400202,'主题不存在']);
|
|
||||||
$themeConfig = json_decode(File::get($themeConfigFile), true);
|
|
||||||
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) return $this->fail([422,'主题配置文件有误']);
|
|
||||||
$validateFields = array_column($themeConfig['configs'], 'field_name');
|
|
||||||
$config = [];
|
|
||||||
foreach ($validateFields as $validateField) {
|
|
||||||
$config[$validateField] = isset($payload['config'][$validateField]) ? $payload['config'][$validateField] : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
File::ensureDirectoryExists(base_path() . '/config/theme/');
|
|
||||||
// $data = var_export($config, 1);
|
|
||||||
|
|
||||||
try {
|
|
||||||
admin_setting(["theme_{$payload['name']}" => $config]);
|
|
||||||
// sleep(2);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->fail([200002, '保存失败']);
|
|
||||||
}
|
|
||||||
return $this->success($config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Ticket;
|
|
||||||
use App\Models\TicketMessage;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Services\TicketService;
|
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class TicketController extends Controller
|
|
||||||
{
|
|
||||||
public function fetch(Request $request)
|
|
||||||
{
|
|
||||||
if ($request->input('id')) {
|
|
||||||
$ticket = Ticket::where('id', $request->input('id'))
|
|
||||||
->first();
|
|
||||||
if (!$ticket) {
|
|
||||||
return $this->fail([400202,'工单不存在']);
|
|
||||||
}
|
|
||||||
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
|
|
||||||
for ($i = 0; $i < count($ticket['message']); $i++) {
|
|
||||||
if ($ticket['message'][$i]['user_id'] !== $ticket->user_id) {
|
|
||||||
$ticket['message'][$i]['is_me'] = true;
|
|
||||||
} else {
|
|
||||||
$ticket['message'][$i]['is_me'] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($ticket);
|
|
||||||
}
|
|
||||||
$current = $request->input('current') ? $request->input('current') : 1;
|
|
||||||
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
|
|
||||||
$model = Ticket::orderBy('updated_at', 'DESC');
|
|
||||||
if ($request->input('status') !== NULL) {
|
|
||||||
$model->where('status', $request->input('status'));
|
|
||||||
}
|
|
||||||
if ($request->input('reply_status') !== NULL) {
|
|
||||||
$model->whereIn('reply_status', $request->input('reply_status'));
|
|
||||||
}
|
|
||||||
if ($request->input('email') !== NULL) {
|
|
||||||
$user = User::where('email', $request->input('email'))->first();
|
|
||||||
if ($user) $model->where('user_id', $user->id);
|
|
||||||
}
|
|
||||||
$total = $model->count();
|
|
||||||
$res = $model->forPage($current, $pageSize)
|
|
||||||
->get();
|
|
||||||
return response([
|
|
||||||
'data' => $res,
|
|
||||||
'total' => $total
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reply(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'id' => 'required|numeric',
|
|
||||||
'message' => 'required|string'
|
|
||||||
],[
|
|
||||||
'id.required' => '工单ID不能为空',
|
|
||||||
'message.required' => '消息不能为空'
|
|
||||||
]);
|
|
||||||
$ticketService = new TicketService();
|
|
||||||
$ticketService->replyByAdmin(
|
|
||||||
$request->input('id'),
|
|
||||||
$request->input('message'),
|
|
||||||
$request->user['id']
|
|
||||||
);
|
|
||||||
return $this->success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'id' => 'required|numeric'
|
|
||||||
],[
|
|
||||||
'id.required' => '工单ID不能为空'
|
|
||||||
]);
|
|
||||||
try {
|
|
||||||
$ticket = Ticket::findOrFail($request->input('id'));
|
|
||||||
$ticket->status = Ticket::STATUS_CLOSED;
|
|
||||||
$ticket->save();
|
|
||||||
return $this->success(true);
|
|
||||||
} catch (ModelNotFoundException $e) {
|
|
||||||
return $this->fail([400202, '工单不存在']);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->fail([500101, '关闭失败']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@ class AppController extends Controller
|
|||||||
public function getConfig(Request $request)
|
public function getConfig(Request $request)
|
||||||
{
|
{
|
||||||
$servers = [];
|
$servers = [];
|
||||||
$user = $request->user;
|
$user = $request->user();
|
||||||
$userService = new UserService();
|
$userService = new UserService();
|
||||||
if ($userService->isAvailable($user)) {
|
if ($userService->isAvailable($user)) {
|
||||||
$servers = ServerService::getAvailableServers($user);
|
$servers = ServerService::getAvailableServers($user);
|
||||||
@@ -30,8 +30,9 @@ class AppController extends Controller
|
|||||||
$proxies = [];
|
$proxies = [];
|
||||||
|
|
||||||
foreach ($servers as $item) {
|
foreach ($servers as $item) {
|
||||||
|
$protocol_settings = $item['protocol_settings'];
|
||||||
if ($item['type'] === 'shadowsocks'
|
if ($item['type'] === 'shadowsocks'
|
||||||
&& in_array($item['cipher'], [
|
&& in_array(data_get($protocol_settings, 'cipher'), [
|
||||||
'aes-128-gcm',
|
'aes-128-gcm',
|
||||||
'aes-192-gcm',
|
'aes-192-gcm',
|
||||||
'aes-256-gcm',
|
'aes-256-gcm',
|
||||||
|
|||||||
@@ -8,12 +8,27 @@ use App\Services\ServerService;
|
|||||||
use App\Services\UserService;
|
use App\Services\UserService;
|
||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class ClientController extends Controller
|
class ClientController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Protocol prefix mapping for server names
|
||||||
|
*/
|
||||||
|
private const PROTOCOL_PREFIXES = [
|
||||||
|
'hysteria' => [
|
||||||
|
1 => '[Hy]',
|
||||||
|
2 => '[Hy2]'
|
||||||
|
],
|
||||||
|
'vless' => '[vless]',
|
||||||
|
'shadowsocks' => '[ss]',
|
||||||
|
'vmess' => '[vmess]',
|
||||||
|
'trojan' => '[trojan]',
|
||||||
|
];
|
||||||
|
|
||||||
// 支持hy2 的客户端版本列表
|
// 支持hy2 的客户端版本列表
|
||||||
const SupportedHy2ClientVersions = [
|
private const CLIENT_VERSIONS = [
|
||||||
'NekoBox' => '1.2.7',
|
'NekoBox' => '1.2.7',
|
||||||
'sing-box' => '1.5.0',
|
'sing-box' => '1.5.0',
|
||||||
'stash' => '2.5.0',
|
'stash' => '2.5.0',
|
||||||
@@ -26,121 +41,191 @@ class ClientController extends Controller
|
|||||||
'loon' => '637',
|
'loon' => '637',
|
||||||
'v2rayng' => '1.9.5',
|
'v2rayng' => '1.9.5',
|
||||||
'v2rayN' => '6.31',
|
'v2rayN' => '6.31',
|
||||||
'surge' => '2398'
|
'surge' => '2398',
|
||||||
|
'flclash' => '0.8.0'
|
||||||
];
|
];
|
||||||
// allowed types
|
|
||||||
const AllowedTypes = ['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks', 'hysteria2'];
|
private const ALLOWED_TYPES = ['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks', 'hysteria2'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理浏览器访问订阅的情况
|
||||||
|
*/
|
||||||
|
private function handleBrowserSubscribe($user, UserService $userService)
|
||||||
|
{
|
||||||
|
$useTraffic = $user['u'] + $user['d'];
|
||||||
|
$totalTraffic = $user['transfer_enable'];
|
||||||
|
$remainingTraffic = Helper::trafficConvert($totalTraffic - $useTraffic);
|
||||||
|
$expiredDate = $user['expired_at'] ? date('Y-m-d', $user['expired_at']) : __('Unlimited');
|
||||||
|
$resetDay = $userService->getResetDay($user);
|
||||||
|
|
||||||
|
// 获取通用订阅地址
|
||||||
|
$subscriptionUrl = Helper::getSubscribeUrl($user->token);
|
||||||
|
|
||||||
|
// 生成二维码
|
||||||
|
$writer = new \BaconQrCode\Writer(
|
||||||
|
new \BaconQrCode\Renderer\ImageRenderer(
|
||||||
|
new \BaconQrCode\Renderer\RendererStyle\RendererStyle(200),
|
||||||
|
new \BaconQrCode\Renderer\Image\SvgImageBackEnd()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$qrCode = base64_encode($writer->writeString($subscriptionUrl));
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'username' => $user->email,
|
||||||
|
'status' => $userService->isAvailable($user) ? 'active' : 'inactive',
|
||||||
|
'data_limit' => $totalTraffic ? Helper::trafficConvert($totalTraffic) : '∞',
|
||||||
|
'data_used' => Helper::trafficConvert($useTraffic),
|
||||||
|
'expired_date' => $expiredDate,
|
||||||
|
'reset_day' => $resetDay,
|
||||||
|
'subscription_url' => $subscriptionUrl,
|
||||||
|
'qr_code' => $qrCode
|
||||||
|
];
|
||||||
|
|
||||||
|
// 只有当 device_limit 不为 null 时才添加到返回数据中
|
||||||
|
if ($user->device_limit !== null) {
|
||||||
|
$data['device_limit'] = $user->device_limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->view('client.subscribe', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否是浏览器访问
|
||||||
|
*/
|
||||||
|
private function isBrowserAccess(Request $request): bool
|
||||||
|
{
|
||||||
|
$userAgent = strtolower($request->input('flag', $request->header('User-Agent')));
|
||||||
|
return str_contains($userAgent, 'mozilla')
|
||||||
|
|| str_contains($userAgent, 'chrome')
|
||||||
|
|| str_contains($userAgent, 'safari')
|
||||||
|
|| str_contains($userAgent, 'edge');
|
||||||
|
}
|
||||||
|
|
||||||
public function subscribe(Request $request)
|
public function subscribe(Request $request)
|
||||||
{
|
{
|
||||||
// filter types
|
$request->validate([
|
||||||
$types = $request->input('types', 'all');
|
'types' => ['nullable', 'string'],
|
||||||
$typesArr = $types === 'all' ? self::AllowedTypes : array_values(array_intersect(explode('|', str_replace(['|', '|', ','], "|", $types)), self::AllowedTypes));
|
'filter' => ['nullable', 'string'],
|
||||||
// filter keyword
|
'flag' => ['nullable', 'string'],
|
||||||
$filterArr = mb_strlen($filter = $request->input('filter')) > 20 ? null : explode("|", str_replace(['|', '|', ','], "|", $filter));
|
]);
|
||||||
$flag = strtolower($request->input('flag') ?? $request->header('User-Agent', ''));
|
|
||||||
$ip = $request->input('ip', $request->ip());
|
$user = $request->user();
|
||||||
// get client version
|
|
||||||
$version = preg_match('/\/v?(\d+(\.\d+){0,2})/', $flag, $matches) ? $matches[1] : null;
|
|
||||||
$supportHy2 = $version ? collect(self::SupportedHy2ClientVersions)
|
|
||||||
->contains(fn($minVersion, $client) => stripos($flag, $client) !== false && $this->versionCompare($version, $minVersion)) : true;
|
|
||||||
$user = $request->user;
|
|
||||||
// account not expired and is not banned.
|
|
||||||
$userService = new UserService();
|
$userService = new UserService();
|
||||||
if ($userService->isAvailable($user)) {
|
|
||||||
// get ip location
|
if (!$userService->isAvailable($user)) {
|
||||||
$ip2region = new \Ip2Region();
|
return response()->json(['message' => 'Account unavailable'], 403);
|
||||||
$region = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? ($ip2region->memorySearch($ip)['region'] ?? null) : null;
|
|
||||||
// get available servers
|
|
||||||
$servers = ServerService::getAvailableServers($user);
|
|
||||||
// filter servers
|
|
||||||
$serversFiltered = $this->serverFilter($servers, $typesArr, $filterArr, $region, $supportHy2);
|
|
||||||
$this->setSubscribeInfoToServers($serversFiltered, $user, count($servers) - count($serversFiltered));
|
|
||||||
$servers = $serversFiltered;
|
|
||||||
$this->addPrefixToServerName($servers);
|
|
||||||
if ($flag) {
|
|
||||||
foreach (array_reverse(glob(app_path('Protocols') . '/*.php')) as $file) {
|
|
||||||
$file = 'App\\Protocols\\' . basename($file, '.php');
|
|
||||||
$class = new $file($user, $servers);
|
|
||||||
$classFlags = explode(',', $class->flag);
|
|
||||||
foreach ($classFlags as $classFlag) {
|
|
||||||
if (stripos($flag, $classFlag) !== false) {
|
|
||||||
return $class->handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$class = new General($user, $servers);
|
|
||||||
return $class->handle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检测是否是浏览器访问
|
||||||
|
if ($this->isBrowserAccess($request)) {
|
||||||
|
return $this->handleBrowserSubscribe($user, $userService);
|
||||||
|
}
|
||||||
|
$clientInfo = $this->getClientInfo($request);
|
||||||
|
$types = $this->getFilteredTypes($request->input('types'), $clientInfo['supportHy2']);
|
||||||
|
$filterArr = $this->getFilterArray($request->input('filter'));
|
||||||
|
// Get available servers and apply filters
|
||||||
|
$servers = ServerService::getAvailableServers($user);
|
||||||
|
$serversFiltered = $this->filterServers(
|
||||||
|
servers: $servers,
|
||||||
|
types: $types,
|
||||||
|
filters: $filterArr,
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->setSubscribeInfoToServers($serversFiltered, $user, count($servers) - count($serversFiltered));
|
||||||
|
$serversFiltered = $this->addPrefixToServerName($serversFiltered);
|
||||||
|
|
||||||
|
// Handle protocol response
|
||||||
|
if ($clientInfo['flag']) {
|
||||||
|
foreach (array_reverse(glob(app_path('Protocols') . '/*.php')) as $file) {
|
||||||
|
$className = 'App\\Protocols\\' . basename($file, '.php');
|
||||||
|
$protocol = new $className($user, $serversFiltered);
|
||||||
|
if (
|
||||||
|
collect($protocol->getFlags())
|
||||||
|
->contains(fn($f) => stripos($clientInfo['flag'], $f) !== false)
|
||||||
|
) {
|
||||||
|
return $protocol->handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new General($user, $serversFiltered))->handle();
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Summary of serverFilter
|
private function getFilteredTypes(string|null $types, bool $supportHy2): array
|
||||||
* @param mixed $typesArr
|
|
||||||
* @param mixed $filterArr
|
|
||||||
* @param mixed $region
|
|
||||||
* @param mixed $supportHy2
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function serverFilter($servers, $typesArr, $filterArr, $region, $supportHy2)
|
|
||||||
{
|
{
|
||||||
return collect($servers)->reject(function ($server) use ($typesArr, $filterArr, $region, $supportHy2) {
|
if ($types === 'all') {
|
||||||
if ($server['type'] == "hysteria" && $server['version'] == 2) {
|
return self::ALLOWED_TYPES;
|
||||||
if(!in_array('hysteria2', $typesArr)){
|
}
|
||||||
|
|
||||||
|
$allowedTypes = $supportHy2
|
||||||
|
? self::ALLOWED_TYPES
|
||||||
|
: array_diff(self::ALLOWED_TYPES, ['hysteria2']);
|
||||||
|
if (!$types) {
|
||||||
|
return array_values($allowedTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userTypes = explode('|', str_replace(['|', '|', ','], '|', $types));
|
||||||
|
return array_values(array_intersect($userTypes, $allowedTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFilterArray(?string $filter): ?array
|
||||||
|
{
|
||||||
|
if ($filter === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return mb_strlen($filter) > 20 ? null :
|
||||||
|
explode('|', str_replace(['|', '|', ','], '|', $filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getClientInfo(Request $request): array
|
||||||
|
{
|
||||||
|
$flag = strtolower($request->input('flag') ?? $request->header('User-Agent', ''));
|
||||||
|
preg_match('/\/v?(\d+(\.\d+){0,2})/', $flag, $matches);
|
||||||
|
$version = $matches[1] ?? null;
|
||||||
|
|
||||||
|
$supportHy2 = $version ? $this->checkHy2Support($flag, $version) : true;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'flag' => $flag,
|
||||||
|
'version' => $version,
|
||||||
|
'supportHy2' => $supportHy2
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkHy2Support(string $flag, string $version): bool
|
||||||
|
{
|
||||||
|
$result = false;
|
||||||
|
foreach (self::CLIENT_VERSIONS as $client => $minVersion) {
|
||||||
|
if (stripos($flag, $client) !== false) {
|
||||||
|
$result = $result || version_compare($version, $minVersion, '>=');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result || !count(self::CLIENT_VERSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filterServers(array $servers, array $types, ?array $filters): array
|
||||||
|
{
|
||||||
|
return collect($servers)->reject(function ($server) use ($types, $filters) {
|
||||||
|
// Check Hysteria2 compatibility
|
||||||
|
if ($server['type'] === 'hysteria' && optional($server['protocol_settings'])['version'] === 2) {
|
||||||
|
if (!in_array('hysteria2', $types)) {
|
||||||
return true;
|
return true;
|
||||||
}elseif(false == $supportHy2){
|
}
|
||||||
|
} else {
|
||||||
|
if (!in_array($server['type'], $types)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Apply custom filters
|
||||||
if ($filterArr) {
|
if ($filters) {
|
||||||
foreach ($filterArr as $filter) {
|
return !collect($filters)->contains(function ($filter) use ($server) {
|
||||||
if (stripos($server['name'], $filter) !== false || in_array($filter, $server['tags'] ?? [])) {
|
return stripos($server['name'], $filter) !== false
|
||||||
return false;
|
|| in_array($filter, $server['tags'] ?? []);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strpos($region, '中国') !== false) {
|
|
||||||
$excludes = $server['excludes'] ?? [];
|
|
||||||
if (empty($excludes)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
foreach ($excludes as $v) {
|
|
||||||
$excludeList = explode("|", str_replace(["|", ",", " ", ","], "|", $v));
|
|
||||||
foreach ($excludeList as $needle) {
|
|
||||||
if (stripos($region, $needle) !== false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
})->values()->all();
|
})->values()->all();
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* add prefix to server name
|
|
||||||
*/
|
|
||||||
private function addPrefixToServerName(&$servers)
|
|
||||||
{
|
|
||||||
// 线路名称增加协议类型
|
|
||||||
if (admin_setting('show_protocol_to_server_enable')) {
|
|
||||||
$typePrefixes = [
|
|
||||||
'hysteria' => [1 => '[Hy]', 2 => '[Hy2]'],
|
|
||||||
'vless' => '[vless]',
|
|
||||||
'shadowsocks' => '[ss]',
|
|
||||||
'vmess' => '[vmess]',
|
|
||||||
'trojan' => '[trojan]',
|
|
||||||
];
|
|
||||||
$servers = collect($servers)->map(function ($server) use ($typePrefixes) {
|
|
||||||
if (isset($typePrefixes[$server['type']])) {
|
|
||||||
$prefix = is_array($typePrefixes[$server['type']]) ? $typePrefixes[$server['type']][$server['version']] : $typePrefixes[$server['type']];
|
|
||||||
$server['name'] = $prefix . $server['name'];
|
|
||||||
}
|
|
||||||
return $server;
|
|
||||||
})->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Summary of setSubscribeInfoToServers
|
* Summary of setSubscribeInfoToServers
|
||||||
@@ -163,7 +248,7 @@ class ClientController extends Controller
|
|||||||
$useTraffic = $user['u'] + $user['d'];
|
$useTraffic = $user['u'] + $user['d'];
|
||||||
$totalTraffic = $user['transfer_enable'];
|
$totalTraffic = $user['transfer_enable'];
|
||||||
$remainingTraffic = Helper::trafficConvert($totalTraffic - $useTraffic);
|
$remainingTraffic = Helper::trafficConvert($totalTraffic - $useTraffic);
|
||||||
$expiredDate = $user['expired_at'] ? date('Y-m-d', $user['expired_at']) : '长期有效';
|
$expiredDate = $user['expired_at'] ? date('Y-m-d', $user['expired_at']) : __('长期有效');
|
||||||
$userService = new UserService();
|
$userService = new UserService();
|
||||||
$resetDay = $userService->getResetDay($user);
|
$resetDay = $userService->getResetDay($user);
|
||||||
array_unshift($servers, array_merge($servers[0], [
|
array_unshift($servers, array_merge($servers[0], [
|
||||||
@@ -179,33 +264,41 @@ class ClientController extends Controller
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断版本号
|
* Add protocol prefix to server names if enabled in admin settings
|
||||||
|
*
|
||||||
|
* @param array<int, array<string, mixed>> $servers
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
|
private function addPrefixToServerName(array $servers): array
|
||||||
function versionCompare($version1, $version2)
|
|
||||||
{
|
{
|
||||||
if (!preg_match('/^\d+(\.\d+){0,2}/', $version1) || !preg_match('/^\d+(\.\d+){0,2}/', $version2)) {
|
if (!admin_setting('show_protocol_to_server_enable', false)) {
|
||||||
return false;
|
return $servers;
|
||||||
}
|
|
||||||
$v1Parts = explode('.', $version1);
|
|
||||||
$v2Parts = explode('.', $version2);
|
|
||||||
|
|
||||||
$maxParts = max(count($v1Parts), count($v2Parts));
|
|
||||||
|
|
||||||
for ($i = 0; $i < $maxParts; $i++) {
|
|
||||||
$part1 = isset($v1Parts[$i]) ? (int) $v1Parts[$i] : 0;
|
|
||||||
$part2 = isset($v2Parts[$i]) ? (int) $v2Parts[$i] : 0;
|
|
||||||
|
|
||||||
if ($part1 < $part2) {
|
|
||||||
return false;
|
|
||||||
} elseif ($part1 > $part2) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 版本号相等
|
return collect($servers)
|
||||||
return true;
|
->map(function (array $server): array {
|
||||||
|
$server['name'] = $this->getPrefixedServerName($server);
|
||||||
|
return $server;
|
||||||
|
})
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get server name with protocol prefix
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $server
|
||||||
|
*/
|
||||||
|
private function getPrefixedServerName(array $server): string
|
||||||
|
{
|
||||||
|
$type = $server['type'] ?? '';
|
||||||
|
if (!isset(self::PROTOCOL_PREFIXES[$type])) {
|
||||||
|
return $server['name'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefix = is_array(self::PROTOCOL_PREFIXES[$type])
|
||||||
|
? self::PROTOCOL_PREFIXES[$type][$server['protocol_settings']['version'] ?? 1] ?? ''
|
||||||
|
: self::PROTOCOL_PREFIXES[$type];
|
||||||
|
|
||||||
|
return $prefix . ($server['name'] ?? '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,23 @@
|
|||||||
namespace App\Http\Controllers\V1\Guest;
|
namespace App\Http\Controllers\V1\Guest;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Resources\PlanResource;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
|
use App\Services\PlanService;
|
||||||
|
use Auth;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class PlanController extends Controller
|
class PlanController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
protected $planService;
|
||||||
|
public function __construct(PlanService $planService)
|
||||||
|
{
|
||||||
|
$this->planService = $planService;
|
||||||
|
}
|
||||||
public function fetch(Request $request)
|
public function fetch(Request $request)
|
||||||
{
|
{
|
||||||
$plan = Plan::where('show', 1)->get();
|
$plan = $this->planService->getAvailablePlans();
|
||||||
return $this->success($plan);
|
return $this->success(PlanResource::collection($plan));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class TelegramController extends Controller
|
|||||||
if ($request->input('access_token') !== md5(admin_setting('telegram_bot_token'))) {
|
if ($request->input('access_token') !== md5(admin_setting('telegram_bot_token'))) {
|
||||||
throw new ApiException('access_token is error', 401);
|
throw new ApiException('access_token is error', 401);
|
||||||
}
|
}
|
||||||
$data = json_decode(get_request_content(),true);
|
$data = json_decode(request()->getContent(),true);
|
||||||
$this->formatMessage($data);
|
$this->formatMessage($data);
|
||||||
$this->formatChatJoinRequest($data);
|
$this->formatChatJoinRequest($data);
|
||||||
$this->handle();
|
$this->handle();
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ class AuthController extends Controller
|
|||||||
|
|
||||||
$authService = new AuthService($user);
|
$authService = new AuthService($user);
|
||||||
|
|
||||||
$data = $authService->generateAuthData($request);
|
$data = $authService->generateAuthData();
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,48 +223,70 @@ class AuthController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$authService = new AuthService($user);
|
$authService = new AuthService($user);
|
||||||
return $this->success($authService->generateAuthData($request));
|
return $this->success($authService->generateAuthData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function token2Login(Request $request)
|
public function token2Login(Request $request)
|
||||||
{
|
{
|
||||||
if ($request->input('token')) {
|
if ($token = $request->input('token')) {
|
||||||
$redirect = '/#/login?verify=' . $request->input('token') . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
|
$redirect = '/#/login?verify=' . $token . '&redirect=' . ($request->input('redirect', 'dashboard'));
|
||||||
if (admin_setting('app_url')) {
|
|
||||||
$location = admin_setting('app_url') . $redirect;
|
return redirect()->to(
|
||||||
} else {
|
admin_setting('app_url')
|
||||||
$location = url($redirect);
|
? admin_setting('app_url') . $redirect
|
||||||
}
|
: url($redirect)
|
||||||
return redirect()->to($location)->send();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->input('verify')) {
|
if ($verify = $request->input('verify')) {
|
||||||
$key = CacheKey::get('TEMP_TOKEN', $request->input('verify'));
|
$key = CacheKey::get('TEMP_TOKEN', $verify);
|
||||||
$userId = Cache::get($key);
|
$userId = Cache::get($key);
|
||||||
|
|
||||||
if (!$userId) {
|
if (!$userId) {
|
||||||
return $this->fail([400,__('Token error')]);
|
return response()->json([
|
||||||
}
|
'message' => __('Token error')
|
||||||
$user = User::find($userId);
|
], 400);
|
||||||
if (!$user) {
|
|
||||||
return $this->fail([400,__('The user does not ')]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user = User::findOrFail($userId);
|
||||||
|
|
||||||
if ($user->banned) {
|
if ($user->banned) {
|
||||||
return $this->fail([400,__('Your account has been suspended')]);
|
return response()->json([
|
||||||
|
'message' => __('Your account has been suspended')
|
||||||
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache::forget($key);
|
Cache::forget($key);
|
||||||
$authService = new AuthService($user);
|
$authService = new AuthService($user);
|
||||||
return $this->success($authService->generateAuthData($request));
|
|
||||||
|
return response()->json([
|
||||||
|
'data' => $authService->generateAuthData()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => __('Invalid request')
|
||||||
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQuickLoginUrl(Request $request)
|
public function getQuickLoginUrl(Request $request)
|
||||||
{
|
{
|
||||||
$authorization = $request->input('auth_data') ?? $request->header('authorization');
|
$authorization = $request->input('auth_data') ?? $request->header('authorization');
|
||||||
if (!$authorization) return $this->fail(ResponseEnum::CLIENT_HTTP_UNAUTHORIZED);
|
|
||||||
|
if (!$authorization) {
|
||||||
$user = AuthService::decryptAuthData($authorization);
|
return response()->json([
|
||||||
if (!$user) return $this->fail(ResponseEnum::CLIENT_HTTP_UNAUTHORIZED_EXPIRED);
|
'message' => ResponseEnum::CLIENT_HTTP_UNAUTHORIZED
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = AuthService::findUserByBearerToken($authorization);
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => ResponseEnum::CLIENT_HTTP_UNAUTHORIZED_EXPIRED
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
$code = Helper::guid();
|
$code = Helper::guid();
|
||||||
$key = CacheKey::get('TEMP_TOKEN', $code);
|
$key = CacheKey::get('TEMP_TOKEN', $code);
|
||||||
Cache::put($key, $user['id'], 60);
|
Cache::put($key, $user['id'], 60);
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Server;
|
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\ServerVmess;
|
|
||||||
use App\Services\ServerService;
|
|
||||||
use App\Services\UserService;
|
|
||||||
use App\Utils\CacheKey;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* V2ray Aurora
|
|
||||||
* Github: https://github.com/tokumeikoi/aurora
|
|
||||||
*/
|
|
||||||
class DeepbworkController extends Controller
|
|
||||||
{
|
|
||||||
CONST V2RAY_CONFIG = '{"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbounds":[{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},{"listen":"127.0.0.1","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"outbounds":[{"protocol":"freedom","settings":{}},{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"type":"field","inboundTag":"api","outboundTag":"api"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
|
|
||||||
|
|
||||||
// 后端获取用户
|
|
||||||
public function user(Request $request)
|
|
||||||
{
|
|
||||||
ini_set('memory_limit', -1);
|
|
||||||
$nodeId = $request->input('node_id');
|
|
||||||
$server = ServerVmess::find($nodeId);
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400,'节点不存在']);
|
|
||||||
}
|
|
||||||
Cache::put(CacheKey::get('SERVER_VMESS_LAST_CHECK_AT', $server->id), time(), 3600);
|
|
||||||
$users = ServerService::getAvailableUsers($server->group_id);
|
|
||||||
$result = [];
|
|
||||||
foreach ($users as $user) {
|
|
||||||
$user->v2ray_user = [
|
|
||||||
"uuid" => $user->uuid,
|
|
||||||
"email" => sprintf("%s@v2board.user", $user->uuid),
|
|
||||||
"alter_id" => 0,
|
|
||||||
"level" => 0,
|
|
||||||
];
|
|
||||||
unset($user->uuid);
|
|
||||||
array_push($result, $user);
|
|
||||||
}
|
|
||||||
$eTag = sha1(json_encode($result));
|
|
||||||
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
|
|
||||||
return response(null,304);
|
|
||||||
}
|
|
||||||
return response([
|
|
||||||
'msg' => 'ok',
|
|
||||||
'data' => $result,
|
|
||||||
])->header('ETag', "\"{$eTag}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 后端提交数据
|
|
||||||
public function submit(Request $request)
|
|
||||||
{
|
|
||||||
$server = ServerVmess::find($request->input('node_id'));
|
|
||||||
if (!$server) {
|
|
||||||
return response([
|
|
||||||
'ret' => 0,
|
|
||||||
'msg' => 'server is not found'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$data = get_request_content();
|
|
||||||
$data = json_decode($data, true);
|
|
||||||
Cache::put(CacheKey::get('SERVER_VMESS_ONLINE_USER', $server->id), count($data), 3600);
|
|
||||||
Cache::put(CacheKey::get('SERVER_VMESS_LAST_PUSH_AT', $server->id), time(), 3600);
|
|
||||||
$userService = new UserService();
|
|
||||||
$formatData = [];
|
|
||||||
|
|
||||||
foreach ($data as $item) {
|
|
||||||
$formatData[$item['user_id']] = [$item['u'], $item['d']];
|
|
||||||
}
|
|
||||||
$userService->trafficFetch($server->toArray(), 'vmess', $formatData);
|
|
||||||
|
|
||||||
return response([
|
|
||||||
'ret' => 1,
|
|
||||||
'msg' => 'ok'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 后端获取配置
|
|
||||||
public function config(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'node_id' => 'required',
|
|
||||||
'local_port' => 'required'
|
|
||||||
],[
|
|
||||||
'node_id.required' => '节点ID不能为空',
|
|
||||||
'local_port.required' => '本地端口不能为空'
|
|
||||||
]);
|
|
||||||
try {
|
|
||||||
$json = $this->getV2RayConfig($request->input('node_id'), $request->input('local_port'));
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\Log::error($e);
|
|
||||||
throw new ApiException($e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return(json_encode($json, JSON_UNESCAPED_UNICODE));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getV2RayConfig(int $nodeId, int $localPort)
|
|
||||||
{
|
|
||||||
$server = ServerVmess::find($nodeId);
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400,'节点不存在']);
|
|
||||||
}
|
|
||||||
$json = json_decode(self::V2RAY_CONFIG);
|
|
||||||
$json->log->loglevel = (int)admin_setting('server_log_enable') ? 'debug' : 'none';
|
|
||||||
$json->inbounds[1]->port = (int)$localPort;
|
|
||||||
$json->inbounds[0]->port = (int)$server->server_port;
|
|
||||||
$json->inbounds[0]->streamSettings->network = $server->network;
|
|
||||||
$this->setDns($server, $json);
|
|
||||||
$this->setNetwork($server, $json);
|
|
||||||
$this->setRule($server, $json);
|
|
||||||
$this->setTls($server, $json);
|
|
||||||
|
|
||||||
return $json;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setDns(ServerVmess $server, object $json)
|
|
||||||
{
|
|
||||||
if ($server->dnsSettings) {
|
|
||||||
$dns = $server->dnsSettings;
|
|
||||||
if (isset($dns->servers)) {
|
|
||||||
array_push($dns->servers, '1.1.1.1');
|
|
||||||
array_push($dns->servers, 'localhost');
|
|
||||||
}
|
|
||||||
$json->dns = $dns;
|
|
||||||
$json->outbounds[0]->settings->domainStrategy = 'UseIP';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setNetwork(ServerVmess $server, object $json)
|
|
||||||
{
|
|
||||||
if ($server->networkSettings) {
|
|
||||||
switch ($server->network) {
|
|
||||||
case 'tcp':
|
|
||||||
$json->inbounds[0]->streamSettings->tcpSettings = $server->networkSettings;
|
|
||||||
break;
|
|
||||||
case 'kcp':
|
|
||||||
$json->inbounds[0]->streamSettings->kcpSettings = $server->networkSettings;
|
|
||||||
break;
|
|
||||||
case 'ws':
|
|
||||||
$json->inbounds[0]->streamSettings->wsSettings = $server->networkSettings;
|
|
||||||
break;
|
|
||||||
case 'http':
|
|
||||||
$json->inbounds[0]->streamSettings->httpSettings = $server->networkSettings;
|
|
||||||
break;
|
|
||||||
case 'domainsocket':
|
|
||||||
$json->inbounds[0]->streamSettings->dsSettings = $server->networkSettings;
|
|
||||||
break;
|
|
||||||
case 'quic':
|
|
||||||
$json->inbounds[0]->streamSettings->quicSettings = $server->networkSettings;
|
|
||||||
break;
|
|
||||||
case 'grpc':
|
|
||||||
$json->inbounds[0]->streamSettings->grpcSettings = $server->networkSettings;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setRule(ServerVmess $server, object $json)
|
|
||||||
{
|
|
||||||
$domainRules = array_filter(explode(PHP_EOL, admin_setting('server_v2ray_domain')));
|
|
||||||
$protocolRules = array_filter(explode(PHP_EOL, admin_setting('server_v2ray_protocol')));
|
|
||||||
if ($server->ruleSettings) {
|
|
||||||
$ruleSettings = $server->ruleSettings;
|
|
||||||
// domain
|
|
||||||
if (isset($ruleSettings->domain)) {
|
|
||||||
$ruleSettings->domain = array_filter($ruleSettings->domain);
|
|
||||||
if (!empty($ruleSettings->domain)) {
|
|
||||||
$domainRules = array_merge($domainRules, $ruleSettings->domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// protocol
|
|
||||||
if (isset($ruleSettings->protocol)) {
|
|
||||||
$ruleSettings->protocol = array_filter($ruleSettings->protocol);
|
|
||||||
if (!empty($ruleSettings->protocol)) {
|
|
||||||
$protocolRules = array_merge($protocolRules, $ruleSettings->protocol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!empty($domainRules)) {
|
|
||||||
$domainObj = new \StdClass();
|
|
||||||
$domainObj->type = 'field';
|
|
||||||
$domainObj->domain = $domainRules;
|
|
||||||
$domainObj->outboundTag = 'block';
|
|
||||||
array_push($json->routing->rules, $domainObj);
|
|
||||||
}
|
|
||||||
if (!empty($protocolRules)) {
|
|
||||||
$protocolObj = new \StdClass();
|
|
||||||
$protocolObj->type = 'field';
|
|
||||||
$protocolObj->protocol = $protocolRules;
|
|
||||||
$protocolObj->outboundTag = 'block';
|
|
||||||
array_push($json->routing->rules, $protocolObj);
|
|
||||||
}
|
|
||||||
if (empty($domainRules) && empty($protocolRules)) {
|
|
||||||
$json->inbounds[0]->sniffing->enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setTls(ServerVMess $server, object $json)
|
|
||||||
{
|
|
||||||
if ((int)$server->tls) {
|
|
||||||
$tlsSettings = $server->tlsSettings;
|
|
||||||
$json->inbounds[0]->streamSettings->security = 'tls';
|
|
||||||
$tls = (object)[
|
|
||||||
'certificateFile' => '/root/.cert/server.crt',
|
|
||||||
'keyFile' => '/root/.cert/server.key'
|
|
||||||
];
|
|
||||||
$json->inbounds[0]->streamSettings->tlsSettings = new \StdClass();
|
|
||||||
if (isset($tlsSettings->serverName)) {
|
|
||||||
$json->inbounds[0]->streamSettings->tlsSettings->serverName = (string)$tlsSettings->serverName;
|
|
||||||
}
|
|
||||||
if (isset($tlsSettings->allowInsecure)) {
|
|
||||||
$json->inbounds[0]->streamSettings->tlsSettings->allowInsecure = (int)$tlsSettings->allowInsecure ? true : false;
|
|
||||||
}
|
|
||||||
$json->inbounds[0]->streamSettings->tlsSettings->certificates[0] = $tls;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,13 +21,9 @@ class ShadowsocksTidalabController extends Controller
|
|||||||
public function user(Request $request)
|
public function user(Request $request)
|
||||||
{
|
{
|
||||||
ini_set('memory_limit', -1);
|
ini_set('memory_limit', -1);
|
||||||
$nodeId = $request->input('node_id');
|
$server = $request->input('node_info');
|
||||||
$server = ServerShadowsocks::find($nodeId);
|
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400,'节点不存在']);
|
|
||||||
}
|
|
||||||
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600);
|
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600);
|
||||||
$users = ServerService::getAvailableUsers($server->group_id);
|
$users = ServerService::getAvailableUsers($server->group_ids);
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
array_push($result, [
|
array_push($result, [
|
||||||
@@ -49,15 +45,8 @@ class ShadowsocksTidalabController extends Controller
|
|||||||
// 后端提交数据
|
// 后端提交数据
|
||||||
public function submit(Request $request)
|
public function submit(Request $request)
|
||||||
{
|
{
|
||||||
$server = ServerShadowsocks::find($request->input('node_id'));
|
$server = $request->input('node_info');
|
||||||
if (!$server) {
|
$data = json_decode(request()->getContent(), true);
|
||||||
return response([
|
|
||||||
'ret' => 0,
|
|
||||||
'msg' => 'server is not found'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$data = get_request_content();
|
|
||||||
$data = json_decode($data, true);
|
|
||||||
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
|
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
|
||||||
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
|
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
|
||||||
$userService = new UserService();
|
$userService = new UserService();
|
||||||
@@ -73,4 +62,4 @@ class ShadowsocksTidalabController extends Controller
|
|||||||
'msg' => 'ok'
|
'msg' => 'ok'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,9 +23,8 @@ class TrojanTidalabController extends Controller
|
|||||||
public function user(Request $request)
|
public function user(Request $request)
|
||||||
{
|
{
|
||||||
ini_set('memory_limit', -1);
|
ini_set('memory_limit', -1);
|
||||||
$nodeId = $request->input('node_id');
|
$server = $request->input('node_info');
|
||||||
$server = ServerTrojan::find($nodeId);
|
if ($server->type !== 'trojan') {
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400, '节点不存在']);
|
return $this->fail([400, '节点不存在']);
|
||||||
}
|
}
|
||||||
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
|
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
|
||||||
@@ -51,15 +50,11 @@ class TrojanTidalabController extends Controller
|
|||||||
// 后端提交数据
|
// 后端提交数据
|
||||||
public function submit(Request $request)
|
public function submit(Request $request)
|
||||||
{
|
{
|
||||||
$server = ServerTrojan::find($request->input('node_id'));
|
$server = $request->input('node_info');
|
||||||
if (!$server) {
|
if ($server->type !== 'trojan') {
|
||||||
return response([
|
return $this->fail([400, '节点不存在']);
|
||||||
'ret' => 0,
|
|
||||||
'msg' => 'server is not found'
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
$data = get_request_content();
|
$data = json_decode(request()->getContent(), true);
|
||||||
$data = json_decode($data, true);
|
|
||||||
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
|
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
|
||||||
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
|
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
|
||||||
$userService = new UserService();
|
$userService = new UserService();
|
||||||
@@ -78,6 +73,10 @@ class TrojanTidalabController extends Controller
|
|||||||
// 后端获取配置
|
// 后端获取配置
|
||||||
public function config(Request $request)
|
public function config(Request $request)
|
||||||
{
|
{
|
||||||
|
$server = $request->input('node_info');
|
||||||
|
if ($server->type !== 'trojan') {
|
||||||
|
return $this->fail([400, '节点不存在']);
|
||||||
|
}
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'node_id' => 'required',
|
'node_id' => 'required',
|
||||||
'local_port' => 'required'
|
'local_port' => 'required'
|
||||||
@@ -86,7 +85,7 @@ class TrojanTidalabController extends Controller
|
|||||||
'local_port.required' => '本地端口不能为空'
|
'local_port.required' => '本地端口不能为空'
|
||||||
]);
|
]);
|
||||||
try {
|
try {
|
||||||
$json = $this->getTrojanConfig($request->input('node_id'), $request->input('local_port'));
|
$json = $this->getTrojanConfig($server, $request->input('local_port'));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error($e);
|
\Log::error($e);
|
||||||
return $this->fail([500, '配置获取失败']);
|
return $this->fail([500, '配置获取失败']);
|
||||||
@@ -95,19 +94,15 @@ class TrojanTidalabController extends Controller
|
|||||||
return (json_encode($json, JSON_UNESCAPED_UNICODE));
|
return (json_encode($json, JSON_UNESCAPED_UNICODE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getTrojanConfig(int $nodeId, int $localPort)
|
private function getTrojanConfig($server, int $localPort)
|
||||||
{
|
{
|
||||||
$server = ServerTrojan::find($nodeId);
|
$protocolSettings = $server->protocol_settings;
|
||||||
if (!$server) {
|
|
||||||
return $this->fail([400, '节点不存在']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$json = json_decode(self::TROJAN_CONFIG);
|
$json = json_decode(self::TROJAN_CONFIG);
|
||||||
$json->local_port = $server->server_port;
|
$json->local_port = $server->server_port;
|
||||||
$json->ssl->sni = $server->server_name ? $server->server_name : $server->host;
|
$json->ssl->sni = data_get($protocolSettings, 'server_name', $server->host);
|
||||||
$json->ssl->cert = "/root/.cert/server.crt";
|
$json->ssl->cert = "/root/.cert/server.crt";
|
||||||
$json->ssl->key = "/root/.cert/server.key";
|
$json->ssl->key = "/root/.cert/server.key";
|
||||||
$json->api->api_port = $localPort;
|
$json->api->api_port = $localPort;
|
||||||
return $json;
|
return $json;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,22 +9,30 @@ use App\Utils\CacheKey;
|
|||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use App\Services\UserOnlineService;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
class UniProxyController extends Controller
|
class UniProxyController extends Controller
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UserOnlineService $userOnlineService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
// 后端获取用户
|
// 后端获取用户
|
||||||
public function user(Request $request)
|
public function user(Request $request)
|
||||||
{
|
{
|
||||||
ini_set('memory_limit', -1);
|
ini_set('memory_limit', -1);
|
||||||
Cache::put(CacheKey::get('SERVER_' . strtoupper($request->input('node_type')) . '_LAST_CHECK_AT', $request->input('node_id')), time(), 3600);
|
$node = $request->input('node_info');
|
||||||
$users = ServerService::getAvailableUsers($request->input('node_info')->group_id)->toArray();
|
$nodeType = $node->type;
|
||||||
|
$nodeId = $node->id;
|
||||||
|
Cache::put(CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_CHECK_AT', $nodeId), time(), 3600);
|
||||||
|
$users = ServerService::getAvailableUsers($node->group_ids);
|
||||||
|
|
||||||
$response['users'] = $users;
|
$response['users'] = $users;
|
||||||
|
|
||||||
$eTag = sha1(json_encode($response));
|
$eTag = sha1(json_encode($response));
|
||||||
if (strpos($request->header('If-None-Match'), $eTag) !== false) {
|
if (strpos($request->header('If-None-Match', ''), $eTag) !== false) {
|
||||||
return response(null, 304);
|
return response(null, 304);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,131 +42,142 @@ class UniProxyController extends Controller
|
|||||||
// 后端提交数据
|
// 后端提交数据
|
||||||
public function push(Request $request)
|
public function push(Request $request)
|
||||||
{
|
{
|
||||||
$res = json_decode(get_request_content(), true);
|
$res = json_decode(request()->getContent(), true);
|
||||||
$data = array_filter($res, function ($item) {
|
if (!is_array($res)) {
|
||||||
return is_array($item) && count($item) === 2 && is_numeric($item[0]) && is_numeric($item[1]);
|
return $this->fail([422, 'Invalid data format']);
|
||||||
});
|
|
||||||
$nodeType = $request->input('node_type');
|
|
||||||
$nodeId = $request->input('node_id');
|
|
||||||
// 增加单节点多服务器统计在线人数
|
|
||||||
$ip = $request->ip();
|
|
||||||
$id = $request->input("id");
|
|
||||||
$time = time();
|
|
||||||
$cacheKey = CacheKey::get('MULTI_SERVER_' . strtoupper($nodeType) . '_ONLINE_USER', $nodeId);
|
|
||||||
|
|
||||||
// 1、获取节点节点在线人数缓存
|
|
||||||
$onlineUsers = Cache::get($cacheKey) ?? [];
|
|
||||||
$onlineCollection = collect($onlineUsers);
|
|
||||||
// 过滤掉超过600秒的记录
|
|
||||||
$onlineCollection = $onlineCollection->reject(function ($item) use ($time) {
|
|
||||||
return $item['time'] < ($time - 600);
|
|
||||||
});
|
|
||||||
// 定义数据
|
|
||||||
$updatedItem = [
|
|
||||||
'id' => $id ?? $ip,
|
|
||||||
'ip' => $ip,
|
|
||||||
'online_user' => count($data),
|
|
||||||
'time' => $time
|
|
||||||
];
|
|
||||||
|
|
||||||
$existingItemIndex = $onlineCollection->search(function ($item) use ($updatedItem) {
|
|
||||||
return ($item['id'] ?? '') === $updatedItem['id'];
|
|
||||||
});
|
|
||||||
if ($existingItemIndex !== false) {
|
|
||||||
$onlineCollection[$existingItemIndex] = $updatedItem;
|
|
||||||
} else {
|
|
||||||
$onlineCollection->push($updatedItem);
|
|
||||||
}
|
}
|
||||||
$onlineUsers = $onlineCollection->all();
|
$data = array_filter($res, function ($item) {
|
||||||
Cache::put($cacheKey, $onlineUsers, 3600);
|
return is_array($item)
|
||||||
|
&& count($item) === 2
|
||||||
|
&& is_numeric($item[0])
|
||||||
|
&& is_numeric($item[1]);
|
||||||
|
});
|
||||||
|
if (empty($data)) {
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
$node = $request->input('node_info');
|
||||||
|
$nodeType = $node->type;
|
||||||
|
$nodeId = $node->id;
|
||||||
|
|
||||||
|
Cache::put(
|
||||||
|
CacheKey::get('SERVER_' . strtoupper($nodeType) . '_ONLINE_USER', $nodeId),
|
||||||
|
count($data),
|
||||||
|
3600
|
||||||
|
);
|
||||||
|
Cache::put(
|
||||||
|
CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_PUSH_AT', $nodeId),
|
||||||
|
time(),
|
||||||
|
3600
|
||||||
|
);
|
||||||
|
|
||||||
$online_user = $onlineCollection->sum('online_user');
|
|
||||||
Cache::put(CacheKey::get('SERVER_' . strtoupper($nodeType) . '_ONLINE_USER', $nodeId), $online_user, 3600);
|
|
||||||
Cache::put(CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_PUSH_AT', $nodeId), time(), 3600);
|
|
||||||
$userService = new UserService();
|
$userService = new UserService();
|
||||||
$userService->trafficFetch($request->input('node_info')->toArray(), $nodeType, $data, $ip);
|
$userService->trafficFetch($node->toArray(), $nodeType, $data);
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后端获取配置
|
// 后端获取配置
|
||||||
public function config(Request $request)
|
public function config(Request $request)
|
||||||
{
|
{
|
||||||
$nodeType = $request->input('node_type');
|
$node = $request->input('node_info');
|
||||||
$nodeInfo = $request->input('node_info');
|
$nodeType = $node->type;
|
||||||
switch ($nodeType) {
|
$protocolSettings = $node->protocol_settings;
|
||||||
case 'shadowsocks':
|
|
||||||
$response = [
|
$serverPort = $node->server_port;
|
||||||
'server_port' => $nodeInfo->server_port,
|
$host = $node->host;
|
||||||
'cipher' => $nodeInfo->cipher,
|
|
||||||
'obfs' => $nodeInfo->obfs,
|
$baseConfig = [
|
||||||
'obfs_settings' => $nodeInfo->obfs_settings
|
'server_port' => (int) $serverPort,
|
||||||
];
|
'network' => data_get($protocolSettings, 'network'),
|
||||||
|
'networkSettings' => data_get($protocolSettings, 'network_settings') ?: null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = match ($nodeType) {
|
||||||
|
'shadowsocks' => [
|
||||||
|
...$baseConfig,
|
||||||
|
'cipher' => $protocolSettings['cipher'],
|
||||||
|
'obfs' => $protocolSettings['obfs'],
|
||||||
|
'obfs_settings' => $protocolSettings['obfs_settings'],
|
||||||
|
'server_key' => match ($protocolSettings['cipher']) {
|
||||||
|
'2022-blake3-aes-128-gcm' => Helper::getServerKey($node->created_at, 16),
|
||||||
|
'2022-blake3-aes-256-gcm' => Helper::getServerKey($node->created_at, 32),
|
||||||
|
default => null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'vmess' => [
|
||||||
|
...$baseConfig,
|
||||||
|
'tls' => (int) $protocolSettings['tls']
|
||||||
|
],
|
||||||
|
'trojan' => [
|
||||||
|
...$baseConfig,
|
||||||
|
'host' => $host,
|
||||||
|
'server_name' => $protocolSettings['server_name'],
|
||||||
|
],
|
||||||
|
'vless' => [
|
||||||
|
...$baseConfig,
|
||||||
|
'tls' => (int) $protocolSettings['tls'],
|
||||||
|
'flow' => $protocolSettings['flow'],
|
||||||
|
'tls_settings' =>
|
||||||
|
match ((int) $protocolSettings['tls']) {
|
||||||
|
2 => $protocolSettings['reality_settings'],
|
||||||
|
default => $protocolSettings['tls_settings']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'hysteria' => [
|
||||||
|
'server_port' => (int) $serverPort,
|
||||||
|
'version' => (int) $protocolSettings['version'],
|
||||||
|
'host' => $host,
|
||||||
|
'server_name' => $protocolSettings['tls']['server_name'],
|
||||||
|
'up_mbps' => (int) $protocolSettings['bandwidth']['up'],
|
||||||
|
'down_mbps' => (int) $protocolSettings['bandwidth']['down'],
|
||||||
|
...match ((int) $protocolSettings['version']) {
|
||||||
|
1 => ['obfs' => $protocolSettings['obfs']['password'] ?? null],
|
||||||
|
2 => [
|
||||||
|
'obfs' => $protocolSettings['obfs']['open'] ? $protocolSettings['obfs']['type'] : null,
|
||||||
|
'obfs-password' => $protocolSettings['obfs']['password'] ?? null
|
||||||
|
],
|
||||||
|
default => []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default => []
|
||||||
|
};
|
||||||
|
|
||||||
if ($nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
|
|
||||||
$response['server_key'] = Helper::getServerKey($nodeInfo->created_at, 16);
|
|
||||||
}
|
|
||||||
if ($nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
|
|
||||||
$response['server_key'] = Helper::getServerKey($nodeInfo->created_at, 32);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'vmess':
|
|
||||||
$response = [
|
|
||||||
'server_port' => $nodeInfo->server_port,
|
|
||||||
'network' => $nodeInfo->network,
|
|
||||||
'networkSettings' => $nodeInfo->networkSettings,
|
|
||||||
'tls' => $nodeInfo->tls
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
case 'trojan':
|
|
||||||
$response = [
|
|
||||||
'host' => $nodeInfo->host,
|
|
||||||
'server_port' => $nodeInfo->server_port,
|
|
||||||
'server_name' => $nodeInfo->server_name,
|
|
||||||
'network' => $nodeInfo->network,
|
|
||||||
'networkSettings' => $nodeInfo->networkSettings,
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
case 'hysteria':
|
|
||||||
$response = [
|
|
||||||
'version' => $nodeInfo->version,
|
|
||||||
'host' => $nodeInfo->host,
|
|
||||||
'server_port' => $nodeInfo->server_port,
|
|
||||||
'server_name' => $nodeInfo->server_name,
|
|
||||||
'up_mbps' => $nodeInfo->up_mbps,
|
|
||||||
'down_mbps' => $nodeInfo->down_mbps,
|
|
||||||
'obfs' => $nodeInfo->is_obfs ? Helper::getServerKey($nodeInfo->created_at, 16) : null
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
case "vless":
|
|
||||||
$response = [
|
|
||||||
'server_port' => $nodeInfo->server_port,
|
|
||||||
'network' => $nodeInfo->network,
|
|
||||||
'network_settings' => $nodeInfo->network_settings,
|
|
||||||
'networkSettings' => $nodeInfo->network_settings,
|
|
||||||
'tls' => $nodeInfo->tls,
|
|
||||||
'flow' => $nodeInfo->flow,
|
|
||||||
'tls_settings' => $nodeInfo->tls_settings
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$response['base_config'] = [
|
$response['base_config'] = [
|
||||||
'push_interval' => (int) admin_setting('server_push_interval', 60),
|
'push_interval' => (int) admin_setting('server_push_interval', 60),
|
||||||
'pull_interval' => (int) admin_setting('server_pull_interval', 60)
|
'pull_interval' => (int) admin_setting('server_pull_interval', 60)
|
||||||
];
|
];
|
||||||
if ($nodeInfo['route_id']) {
|
|
||||||
$response['routes'] = ServerService::getRoutes($nodeInfo['route_id']);
|
if (!empty($node['route_ids'])) {
|
||||||
}
|
$response['routes'] = ServerService::getRoutes($node['route_ids']);
|
||||||
$eTag = sha1(json_encode($response));
|
|
||||||
if (strpos($request->header('If-None-Match'), $eTag) !== false) {
|
|
||||||
return response(null, 304);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$eTag = sha1(json_encode($response));
|
||||||
|
if (strpos($request->header('If-None-Match', '') ?? '', $eTag) !== false) {
|
||||||
|
return response(null, 304);
|
||||||
|
}
|
||||||
return response($response)->header('ETag', "\"{$eTag}\"");
|
return response($response)->header('ETag', "\"{$eTag}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后端提交在线数据
|
// 获取在线用户数据(wyx2685
|
||||||
public function alive(Request $request)
|
public function alivelist(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
return $this->success(true);
|
$node = $request->input('node_info');
|
||||||
|
$deviceLimitUsers = ServerService::getAvailableUsers($node->group_ids)
|
||||||
|
->where('device_limit', '>', 0);
|
||||||
|
$alive = $this->userOnlineService->getAliveList($deviceLimitUsers);
|
||||||
|
return response()->json(['alive' => (object) $alive]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端提交在线数据
|
||||||
|
public function alive(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$node = $request->input('node_info');
|
||||||
|
$data = json_decode(request()->getContent(), true);
|
||||||
|
if ($data === null) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'Invalid online data'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
$this->userOnlineService->updateAliveData($data, $node->type, $node->id);
|
||||||
|
return response()->json(['data' => true]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class TicketController extends Controller
|
|||||||
$ticketService->replyByAdmin(
|
$ticketService->replyByAdmin(
|
||||||
$request->input('id'),
|
$request->input('id'),
|
||||||
$request->input('message'),
|
$request->input('message'),
|
||||||
$request->user['id']
|
$request->user()->id
|
||||||
);
|
);
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class CouponController extends Controller
|
|||||||
}
|
}
|
||||||
$couponService = new CouponService($request->input('code'));
|
$couponService = new CouponService($request->input('code'));
|
||||||
$couponService->setPlanId($request->input('plan_id'));
|
$couponService->setPlanId($request->input('plan_id'));
|
||||||
$couponService->setUserId($request->user['id']);
|
$couponService->setUserId($request->user()->id);
|
||||||
$couponService->check();
|
$couponService->check();
|
||||||
return $this->success($couponService->getCoupon());
|
return $this->success($couponService->getCoupon());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ class InviteController extends Controller
|
|||||||
{
|
{
|
||||||
public function save(Request $request)
|
public function save(Request $request)
|
||||||
{
|
{
|
||||||
if (InviteCode::where('user_id', $request->user['id'])->where('status', 0)->count() >= admin_setting('invite_gen_limit', 5)) {
|
if (InviteCode::where('user_id', $request->user()->id)->where('status', 0)->count() >= admin_setting('invite_gen_limit', 5)) {
|
||||||
return $this->fail([400,__('The maximum number of creations has been reached')]);
|
return $this->fail([400,__('The maximum number of creations has been reached')]);
|
||||||
}
|
}
|
||||||
$inviteCode = new InviteCode();
|
$inviteCode = new InviteCode();
|
||||||
$inviteCode->user_id = $request->user['id'];
|
$inviteCode->user_id = $request->user()->id;
|
||||||
$inviteCode->code = Helper::randomChar(8);
|
$inviteCode->code = Helper::randomChar(8);
|
||||||
return $this->success($inviteCode->save());
|
return $this->success($inviteCode->save());
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ class InviteController extends Controller
|
|||||||
{
|
{
|
||||||
$current = $request->input('current') ? $request->input('current') : 1;
|
$current = $request->input('current') ? $request->input('current') : 1;
|
||||||
$pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
|
$pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
|
||||||
$builder = CommissionLog::where('invite_user_id', $request->user['id'])
|
$builder = CommissionLog::where('invite_user_id', $request->user()->id)
|
||||||
->where('get_amount', '>', 0)
|
->where('get_amount', '>', 0)
|
||||||
->orderBy('created_at', 'DESC');
|
->orderBy('created_at', 'DESC');
|
||||||
$total = $builder->count();
|
$total = $builder->count();
|
||||||
@@ -45,7 +45,7 @@ class InviteController extends Controller
|
|||||||
public function fetch(Request $request)
|
public function fetch(Request $request)
|
||||||
{
|
{
|
||||||
$commission_rate = admin_setting('invite_commission', 10);
|
$commission_rate = admin_setting('invite_commission', 10);
|
||||||
$user = User::find($request->user['id'])
|
$user = User::find($request->user()->id)
|
||||||
->load(['codes' => fn($query) => $query->where('status', 0)]);
|
->load(['codes' => fn($query) => $query->where('status', 0)]);
|
||||||
if ($user->commission_rate) {
|
if ($user->commission_rate) {
|
||||||
$commission_rate = $user->commission_rate;
|
$commission_rate = $user->commission_rate;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class KnowledgeController extends Controller
|
|||||||
->first()
|
->first()
|
||||||
->toArray();
|
->toArray();
|
||||||
if (!$knowledge) return $this->fail([500, __('Article does not exist')]);
|
if (!$knowledge) return $this->fail([500, __('Article does not exist')]);
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
$userService = new UserService();
|
$userService = new UserService();
|
||||||
if (!$userService->isAvailable($user)) {
|
if (!$userService->isAvailable($user)) {
|
||||||
$this->formatAccessData($knowledge['body']);
|
$this->formatAccessData($knowledge['body']);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ class NoticeController extends Controller
|
|||||||
{
|
{
|
||||||
$current = $request->input('current') ? $request->input('current') : 1;
|
$current = $request->input('current') ? $request->input('current') : 1;
|
||||||
$pageSize = 5;
|
$pageSize = 5;
|
||||||
$model = Notice::orderBy('created_at', 'DESC')
|
$model = Notice::orderBy('sort', 'ASC')
|
||||||
->where('show', 1);
|
->where('show', true);
|
||||||
$total = $model->count();
|
$total = $model->count();
|
||||||
$res = $model->forPage($current, $pageSize)
|
$res = $model->forPage($current, $pageSize)
|
||||||
->get();
|
->get();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\V1\User;
|
|||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\User\OrderSave;
|
use App\Http\Requests\User\OrderSave;
|
||||||
|
use App\Http\Resources\OrderResource;
|
||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
@@ -13,6 +14,7 @@ use App\Services\CouponService;
|
|||||||
use App\Services\OrderService;
|
use App\Services\OrderService;
|
||||||
use App\Services\PaymentService;
|
use App\Services\PaymentService;
|
||||||
use App\Services\PlanService;
|
use App\Services\PlanService;
|
||||||
|
use App\Services\Plugin\HookManager;
|
||||||
use App\Services\UserService;
|
use App\Services\UserService;
|
||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -22,138 +24,127 @@ class OrderController extends Controller
|
|||||||
{
|
{
|
||||||
public function fetch(Request $request)
|
public function fetch(Request $request)
|
||||||
{
|
{
|
||||||
$model = Order::where('user_id', $request->user['id'])
|
$request->validate([
|
||||||
->orderBy('created_at', 'DESC');
|
'status' => 'nullable|integer|in:0,1,2,3',
|
||||||
if ($request->input('status') !== null) {
|
]);
|
||||||
$model->where('status', $request->input('status'));
|
$orders = Order::with('plan')
|
||||||
}
|
->where('user_id', $request->user()->id)
|
||||||
$order = $model->get();
|
->when($request->input('status') !== null, function ($query) use ($request) {
|
||||||
$plan = Plan::get();
|
$query->where('status', $request->input('status'));
|
||||||
for ($i = 0; $i < count($order); $i++) {
|
})
|
||||||
for ($x = 0; $x < count($plan); $x++) {
|
->orderBy('created_at', 'DESC')
|
||||||
if ($order[$i]['plan_id'] === $plan[$x]['id']) {
|
->get();
|
||||||
$order[$i]['plan'] = $plan[$x];
|
|
||||||
}
|
return $this->success(OrderResource::collection($orders));
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($order->makeHidden(['id', 'user_id']));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function detail(Request $request)
|
public function detail(Request $request)
|
||||||
{
|
{
|
||||||
$order = Order::where('user_id', $request->user['id'])
|
$request->validate([
|
||||||
|
'trade_no' => 'required|string',
|
||||||
|
]);
|
||||||
|
$order = Order::with('payment')
|
||||||
|
->where('user_id', $request->user()->id)
|
||||||
->where('trade_no', $request->input('trade_no'))
|
->where('trade_no', $request->input('trade_no'))
|
||||||
->first();
|
->first();
|
||||||
if (!$order) {
|
if (!$order) {
|
||||||
return $this->fail([400, __('Order does not exist or has been paid')]);
|
return $this->fail([400, __('Order does not exist or has been paid')]);
|
||||||
}
|
}
|
||||||
$order['plan'] = Plan::find($order->plan_id);
|
$order['plan'] = Plan::find($order->plan_id);
|
||||||
$order['try_out_plan_id'] = (int)admin_setting('try_out_plan_id');
|
$order['try_out_plan_id'] = (int) admin_setting('try_out_plan_id');
|
||||||
if (!$order['plan']) {
|
if (!$order['plan']) {
|
||||||
return $this->fail([400, __('Subscription plan does not exist')]);
|
return $this->fail([400, __('Subscription plan does not exist')]);
|
||||||
}
|
}
|
||||||
if ($order->surplus_order_ids) {
|
if ($order->surplus_order_ids) {
|
||||||
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
|
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
|
||||||
}
|
}
|
||||||
return $this->success($order);
|
return $this->success(OrderResource::make($order));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(OrderSave $request)
|
public function save(OrderSave $request)
|
||||||
{
|
{
|
||||||
$userService = new UserService();
|
$request->validate([
|
||||||
if ($userService->isNotCompleteOrderByUserId($request->user['id'])) {
|
'plan_id' => 'required|exists:App\Models\Plan,id',
|
||||||
return $this->fail([400, __('You have an unpaid or pending order, please try again later or cancel it')]);
|
'period' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::findOrFail($request->user()->id);
|
||||||
|
$userService = app(UserService::class);
|
||||||
|
|
||||||
|
if ($userService->isNotCompleteOrderByUserId($user->id)) {
|
||||||
|
throw new ApiException(__('You have an unpaid or pending order, please try again later or cancel it'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$planService = new PlanService($request->input('plan_id'));
|
$plan = Plan::findOrFail($request->input('plan_id'));
|
||||||
|
$planService = new PlanService($plan);
|
||||||
|
|
||||||
$plan = $planService->plan;
|
// Validate plan purchase
|
||||||
$user = User::find($request->user['id']);
|
$planService->validatePurchase($user, $request->input('period'));
|
||||||
|
|
||||||
if (!$plan) {
|
return DB::transaction(function () use ($request, $plan, $user, $userService, $planService) {
|
||||||
return $this->fail([400, __('Subscription plan does not exist')]);
|
$period = $request->input('period');
|
||||||
}
|
$newPeriod = PlanService::getPeriodKey($period);
|
||||||
|
|
||||||
if ($user->plan_id !== $plan->id && !$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
|
// Create order
|
||||||
throw new ApiException(__('Current product is sold out'));
|
$order = new Order([
|
||||||
}
|
'user_id' => $user->id,
|
||||||
|
'plan_id' => $plan->id,
|
||||||
if ($plan[$request->input('period')] === NULL) {
|
'period' => $newPeriod,
|
||||||
return $this->fail([400, __('This payment period cannot be purchased, please choose another period')]);
|
'trade_no' => Helper::generateOrderNo(),
|
||||||
}
|
'total_amount' => optional($plan->prices)[$newPeriod] * 100
|
||||||
|
]);
|
||||||
if ($request->input('period') === 'reset_price') {
|
|
||||||
if (!$userService->isAvailable($user) || $plan->id !== $user->plan_id) {
|
|
||||||
return $this->fail([400, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
|
|
||||||
if ($request->input('period') !== 'reset_price') {
|
|
||||||
return $this->fail([400, __('This subscription has been sold out, please choose another subscription')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$plan->renew && $user->plan_id == $plan->id && $request->input('period') !== 'reset_price') {
|
|
||||||
return $this->fail([400, __('This subscription cannot be renewed, please change to another subscription')]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!$plan->show && $plan->renew && !$userService->isAvailable($user)) {
|
|
||||||
return $this->fail([400, __('This subscription has expired, please change to another subscription')]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
DB::beginTransaction();
|
|
||||||
$order = new Order();
|
|
||||||
$orderService = new OrderService($order);
|
|
||||||
$order->user_id = $request->user['id'];
|
|
||||||
$order->plan_id = $plan->id;
|
|
||||||
$order->period = $request->input('period');
|
|
||||||
$order->trade_no = Helper::generateOrderNo();
|
|
||||||
$order->total_amount = $plan[$request->input('period')];
|
|
||||||
|
|
||||||
|
// Apply coupon if provided
|
||||||
if ($request->input('coupon_code')) {
|
if ($request->input('coupon_code')) {
|
||||||
$couponService = new CouponService($request->input('coupon_code'));
|
$this->applyCoupon($order, $request->input('coupon_code'));
|
||||||
if (!$couponService->use($order)) {
|
|
||||||
return $this->fail([400, __('Coupon failed')]);
|
|
||||||
}
|
|
||||||
$order->coupon_id = $couponService->getId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set order attributes
|
||||||
|
$orderService = new OrderService($order);
|
||||||
$orderService->setVipDiscount($user);
|
$orderService->setVipDiscount($user);
|
||||||
$orderService->setOrderType($user);
|
$orderService->setOrderType($user);
|
||||||
$orderService->setInvite($user);
|
$orderService->setInvite($user);
|
||||||
|
|
||||||
|
// Handle user balance
|
||||||
if ($user->balance && $order->total_amount > 0) {
|
if ($user->balance && $order->total_amount > 0) {
|
||||||
$remainingBalance = $user->balance - $order->total_amount;
|
$this->handleUserBalance($order, $user, $userService);
|
||||||
$userService = new UserService();
|
|
||||||
if ($remainingBalance > 0) {
|
|
||||||
if (!$userService->addBalance($order->user_id, - $order->total_amount)) {
|
|
||||||
return $this->fail([400, __('Insufficient balance')]);
|
|
||||||
}
|
|
||||||
$order->balance_amount = $order->total_amount;
|
|
||||||
$order->total_amount = 0;
|
|
||||||
} else {
|
|
||||||
if (!$userService->addBalance($order->user_id, - $user->balance)) {
|
|
||||||
return $this->fail([400, __('Insufficient balance')]);
|
|
||||||
}
|
|
||||||
$order->balance_amount = $user->balance;
|
|
||||||
$order->total_amount = $order->total_amount - $user->balance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$order->save()) {
|
if (!$order->save()) {
|
||||||
DB::rollBack();
|
throw new ApiException(__('Failed to create order'));
|
||||||
return $this->fail([400, __('Failed to create order')]);
|
|
||||||
}
|
}
|
||||||
DB::commit();
|
HookManager::call('order.after_create', $order);
|
||||||
}catch (\Exception $e){
|
|
||||||
DB::rollBack();
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success($order->trade_no);
|
return $this->success($order->trade_no);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyCoupon(Order $order, string $couponCode): void
|
||||||
|
{
|
||||||
|
$couponService = new CouponService($couponCode);
|
||||||
|
if (!$couponService->use($order)) {
|
||||||
|
throw new ApiException(__('Coupon failed'));
|
||||||
|
}
|
||||||
|
$order->coupon_id = $couponService->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handleUserBalance(Order $order, User $user, UserService $userService): void
|
||||||
|
{
|
||||||
|
$remainingBalance = $user->balance - $order->total_amount;
|
||||||
|
|
||||||
|
if ($remainingBalance > 0) {
|
||||||
|
if (!$userService->addBalance($order->user_id, -$order->total_amount)) {
|
||||||
|
throw new ApiException(__('Insufficient balance'));
|
||||||
|
}
|
||||||
|
$order->balance_amount = $order->total_amount;
|
||||||
|
$order->total_amount = 0;
|
||||||
|
} else {
|
||||||
|
if (!$userService->addBalance($order->user_id, -$user->balance)) {
|
||||||
|
throw new ApiException(__('Insufficient balance'));
|
||||||
|
}
|
||||||
|
$order->balance_amount = $user->balance;
|
||||||
|
$order->total_amount = $order->total_amount - $user->balance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkout(Request $request)
|
public function checkout(Request $request)
|
||||||
@@ -161,7 +152,7 @@ class OrderController extends Controller
|
|||||||
$tradeNo = $request->input('trade_no');
|
$tradeNo = $request->input('trade_no');
|
||||||
$method = $request->input('method');
|
$method = $request->input('method');
|
||||||
$order = Order::where('trade_no', $tradeNo)
|
$order = Order::where('trade_no', $tradeNo)
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->where('status', 0)
|
->where('status', 0)
|
||||||
->first();
|
->first();
|
||||||
if (!$order) {
|
if (!$order) {
|
||||||
@@ -170,21 +161,24 @@ class OrderController extends Controller
|
|||||||
// free process
|
// free process
|
||||||
if ($order->total_amount <= 0) {
|
if ($order->total_amount <= 0) {
|
||||||
$orderService = new OrderService($order);
|
$orderService = new OrderService($order);
|
||||||
if (!$orderService->paid($order->trade_no)) return $this->fail([400, '支付失败']);
|
if (!$orderService->paid($order->trade_no))
|
||||||
|
return $this->fail([400, '支付失败']);
|
||||||
return response([
|
return response([
|
||||||
'type' => -1,
|
'type' => -1,
|
||||||
'data' => true
|
'data' => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$payment = Payment::find($method);
|
$payment = Payment::find($method);
|
||||||
if (!$payment || $payment->enable !== 1) return $this->fail([400, __('Payment method is not available')]);
|
if (!$payment || $payment->enable !== 1)
|
||||||
|
return $this->fail([400, __('Payment method is not available')]);
|
||||||
$paymentService = new PaymentService($payment->payment, $payment->id);
|
$paymentService = new PaymentService($payment->payment, $payment->id);
|
||||||
$order->handling_amount = NULL;
|
$order->handling_amount = NULL;
|
||||||
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) {
|
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) {
|
||||||
$order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
|
$order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
|
||||||
}
|
}
|
||||||
$order->payment_id = $method;
|
$order->payment_id = $method;
|
||||||
if (!$order->save()) return $this->fail([400, __('Request failed, please try again later')]);
|
if (!$order->save())
|
||||||
|
return $this->fail([400, __('Request failed, please try again later')]);
|
||||||
$result = $paymentService->pay([
|
$result = $paymentService->pay([
|
||||||
'trade_no' => $tradeNo,
|
'trade_no' => $tradeNo,
|
||||||
'total_amount' => isset($order->handling_amount) ? ($order->total_amount + $order->handling_amount) : $order->total_amount,
|
'total_amount' => isset($order->handling_amount) ? ($order->total_amount + $order->handling_amount) : $order->total_amount,
|
||||||
@@ -201,7 +195,7 @@ class OrderController extends Controller
|
|||||||
{
|
{
|
||||||
$tradeNo = $request->input('trade_no');
|
$tradeNo = $request->input('trade_no');
|
||||||
$order = Order::where('trade_no', $tradeNo)
|
$order = Order::where('trade_no', $tradeNo)
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->first();
|
->first();
|
||||||
if (!$order) {
|
if (!$order) {
|
||||||
return $this->fail([400, __('Order does not exist')]);
|
return $this->fail([400, __('Order does not exist')]);
|
||||||
@@ -232,7 +226,7 @@ class OrderController extends Controller
|
|||||||
return $this->fail([422, __('Invalid parameter')]);
|
return $this->fail([422, __('Invalid parameter')]);
|
||||||
}
|
}
|
||||||
$order = Order::where('trade_no', $request->input('trade_no'))
|
$order = Order::where('trade_no', $request->input('trade_no'))
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->first();
|
->first();
|
||||||
if (!$order) {
|
if (!$order) {
|
||||||
return $this->fail([400, __('Order does not exist')]);
|
return $this->fail([400, __('Order does not exist')]);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\V1\User;
|
|||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Resources\PlanResource;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\PlanService;
|
use App\Services\PlanService;
|
||||||
@@ -11,29 +12,27 @@ use Illuminate\Http\Request;
|
|||||||
|
|
||||||
class PlanController extends Controller
|
class PlanController extends Controller
|
||||||
{
|
{
|
||||||
|
protected PlanService $planService;
|
||||||
|
|
||||||
|
public function __construct(PlanService $planService)
|
||||||
|
{
|
||||||
|
$this->planService = $planService;
|
||||||
|
}
|
||||||
public function fetch(Request $request)
|
public function fetch(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
if ($request->input('id')) {
|
if ($request->input('id')) {
|
||||||
$plan = Plan::where('id', $request->input('id'))->first();
|
$plan = Plan::where('id', $request->input('id'))->first();
|
||||||
if (!$plan) {
|
if (!$plan) {
|
||||||
return $this->fail([400, __('Subscription plan does not exist')]);
|
return $this->fail([400, __('Subscription plan does not exist')]);
|
||||||
}
|
}
|
||||||
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
|
if (!$this->planService->isPlanAvailableForUser($plan, $user)) {
|
||||||
return $this->fail([400, __('Subscription plan does not exist')]);
|
return $this->fail([400, __('Subscription plan does not exist')]);
|
||||||
}
|
}
|
||||||
return $this->success($plan);
|
return $this->success(PlanResource::make($plan));
|
||||||
}
|
}
|
||||||
|
|
||||||
$counts = PlanService::countActiveUsers();
|
$plans = $this->planService->getAvailablePlans();
|
||||||
$plans = Plan::where('show', 1)
|
return $this->success(PlanResource::collection($plans));
|
||||||
->orderBy('sort', 'ASC')
|
|
||||||
->get();
|
|
||||||
foreach ($plans as $k => $v) {
|
|
||||||
if ($plans[$k]->capacity_limit === NULL) continue;
|
|
||||||
if (!isset($counts[$plans[$k]->id])) continue;
|
|
||||||
$plans[$k]->capacity_limit = $plans[$k]->capacity_limit - $counts[$plans[$k]->id]->count;
|
|
||||||
}
|
|
||||||
return $this->success($plans);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ class ServerController extends Controller
|
|||||||
{
|
{
|
||||||
public function fetch(Request $request)
|
public function fetch(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
$servers = [];
|
$servers = [];
|
||||||
$userService = new UserService();
|
$userService = new UserService();
|
||||||
if ($userService->isAvailable($user)) {
|
if ($userService->isAvailable($user)) {
|
||||||
$servers = ServerService::getAvailableServers($user);
|
$servers = ServerService::getAvailableServers($user);
|
||||||
}
|
}
|
||||||
$eTag = sha1(json_encode(array_column($servers, 'cache_key')));
|
$eTag = sha1(json_encode(array_column($servers, 'cache_key')));
|
||||||
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
|
if (strpos($request->header('If-None-Match', ''), $eTag) !== false ) {
|
||||||
return response(null,304);
|
return response(null,304);
|
||||||
}
|
}
|
||||||
$data = NodeResource::collection($servers);
|
$data = NodeResource::collection($servers);
|
||||||
|
|||||||
@@ -15,23 +15,11 @@ class StatController extends Controller
|
|||||||
{
|
{
|
||||||
$startDate = now()->startOfMonth()->timestamp;
|
$startDate = now()->startOfMonth()->timestamp;
|
||||||
$records = StatUser::query()
|
$records = StatUser::query()
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->where('record_at', '>=', $startDate)
|
->where('record_at', '>=', $startDate)
|
||||||
->orderBy('record_at', 'DESC')
|
->orderBy('record_at', 'DESC')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
// 追加当天流量
|
|
||||||
$recordAt = strtotime(date('Y-m-d'));
|
|
||||||
$statService = new StatisticalService();
|
|
||||||
$statService->setStartAt($recordAt);
|
|
||||||
$todayTraffics = $statService->getStatUserByUserID($request->user['id']);
|
|
||||||
if (count($todayTraffics) > 0) {
|
|
||||||
$todayTraffics = collect($todayTraffics)->map(function ($todayTraffic) {
|
|
||||||
$todayTraffic['server_rate'] = number_format($todayTraffic['server_rate'], 2);
|
|
||||||
return $todayTraffic;
|
|
||||||
});
|
|
||||||
$records = $todayTraffics->merge($records);
|
|
||||||
}
|
|
||||||
$data = TrafficLogResource::collection(collect($records));
|
$data = TrafficLogResource::collection(collect($records));
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ class TelegramController extends Controller
|
|||||||
|
|
||||||
public function unbind(Request $request)
|
public function unbind(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::where('user_id', $request->user['id'])->first();
|
$user = User::where('user_id', $request->user()->id)->first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class TicketController extends Controller
|
|||||||
{
|
{
|
||||||
if ($request->input('id')) {
|
if ($request->input('id')) {
|
||||||
$ticket = Ticket::where('id', $request->input('id'))
|
$ticket = Ticket::where('id', $request->input('id'))
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->first()
|
->first()
|
||||||
->load('message');
|
->load('message');
|
||||||
if (!$ticket) {
|
if (!$ticket) {
|
||||||
@@ -33,7 +33,7 @@ class TicketController extends Controller
|
|||||||
});
|
});
|
||||||
return $this->success(TicketResource::make($ticket)->additional(['message' => true]));
|
return $this->success(TicketResource::make($ticket)->additional(['message' => true]));
|
||||||
}
|
}
|
||||||
$ticket = Ticket::where('user_id', $request->user['id'])
|
$ticket = Ticket::where('user_id', $request->user()->id)
|
||||||
->orderBy('created_at', 'DESC')
|
->orderBy('created_at', 'DESC')
|
||||||
->get();
|
->get();
|
||||||
return $this->success(TicketResource::collection($ticket));
|
return $this->success(TicketResource::collection($ticket));
|
||||||
@@ -43,20 +43,20 @@ class TicketController extends Controller
|
|||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
if ((int)Ticket::where('status', 0)->where('user_id', $request->user['id'])->lockForUpdate()->count()) {
|
if ((int)Ticket::where('status', 0)->where('user_id', $request->user()->id)->lockForUpdate()->count()) {
|
||||||
throw new \Exception(__('There are other unresolved tickets'));
|
throw new \Exception(__('There are other unresolved tickets'));
|
||||||
}
|
}
|
||||||
$ticket = Ticket::create(array_merge($request->only([
|
$ticket = Ticket::create(array_merge($request->only([
|
||||||
'subject',
|
'subject',
|
||||||
'level'
|
'level'
|
||||||
]), [
|
]), [
|
||||||
'user_id' => $request->user['id']
|
'user_id' => $request->user()->id
|
||||||
]));
|
]));
|
||||||
if (!$ticket) {
|
if (!$ticket) {
|
||||||
throw new \Exception(__('There are other unresolved tickets'));
|
throw new \Exception(__('There are other unresolved tickets'));
|
||||||
}
|
}
|
||||||
$ticketMessage = TicketMessage::create([
|
$ticketMessage = TicketMessage::create([
|
||||||
'user_id' => $request->user['id'],
|
'user_id' => $request->user()->id,
|
||||||
'ticket_id' => $ticket->id,
|
'ticket_id' => $ticket->id,
|
||||||
'message' => $request->input('message')
|
'message' => $request->input('message')
|
||||||
]);
|
]);
|
||||||
@@ -64,7 +64,7 @@ class TicketController extends Controller
|
|||||||
throw new \Exception(__('Failed to open ticket'));
|
throw new \Exception(__('Failed to open ticket'));
|
||||||
}
|
}
|
||||||
DB::commit();
|
DB::commit();
|
||||||
$this->sendNotify($ticket, $request->input('message'), $request->user['id']);
|
$this->sendNotify($ticket, $request->input('message'), $request->user()->id);
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}catch(\Exception $e){
|
}catch(\Exception $e){
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
@@ -83,7 +83,7 @@ class TicketController extends Controller
|
|||||||
return $this->fail([400, __('Message cannot be empty')]);
|
return $this->fail([400, __('Message cannot be empty')]);
|
||||||
}
|
}
|
||||||
$ticket = Ticket::where('id', $request->input('id'))
|
$ticket = Ticket::where('id', $request->input('id'))
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->first();
|
->first();
|
||||||
if (!$ticket) {
|
if (!$ticket) {
|
||||||
return $this->fail([400, __('Ticket does not exist')]);
|
return $this->fail([400, __('Ticket does not exist')]);
|
||||||
@@ -91,18 +91,18 @@ class TicketController extends Controller
|
|||||||
if ($ticket->status) {
|
if ($ticket->status) {
|
||||||
return $this->fail([400, __('The ticket is closed and cannot be replied')]);
|
return $this->fail([400, __('The ticket is closed and cannot be replied')]);
|
||||||
}
|
}
|
||||||
if ($request->user['id'] == $this->getLastMessage($ticket->id)->user_id) {
|
if ($request->user()->id == $this->getLastMessage($ticket->id)->user_id) {
|
||||||
return $this->fail([400, __('Please wait for the technical enginneer to reply')]);
|
return $this->fail([400, __('Please wait for the technical enginneer to reply')]);
|
||||||
}
|
}
|
||||||
$ticketService = new TicketService();
|
$ticketService = new TicketService();
|
||||||
if (!$ticketService->reply(
|
if (!$ticketService->reply(
|
||||||
$ticket,
|
$ticket,
|
||||||
$request->input('message'),
|
$request->input('message'),
|
||||||
$request->user['id']
|
$request->user()->id
|
||||||
)) {
|
)) {
|
||||||
return $this->fail([400, __('Ticket reply failed')]);
|
return $this->fail([400, __('Ticket reply failed')]);
|
||||||
}
|
}
|
||||||
$this->sendNotify($ticket, $request->input('message'), $request->user['id']);
|
$this->sendNotify($ticket, $request->input('message'), $request->user()->id);
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class TicketController extends Controller
|
|||||||
return $this->fail([422, __('Invalid parameter')]);
|
return $this->fail([422, __('Invalid parameter')]);
|
||||||
}
|
}
|
||||||
$ticket = Ticket::where('id', $request->input('id'))
|
$ticket = Ticket::where('id', $request->input('id'))
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->first();
|
->first();
|
||||||
if (!$ticket) {
|
if (!$ticket) {
|
||||||
return $this->fail([400, __('Ticket does not exist')]);
|
return $this->fail([400, __('Ticket does not exist')]);
|
||||||
@@ -143,7 +143,7 @@ class TicketController extends Controller
|
|||||||
)) {
|
)) {
|
||||||
return $this->fail([422, __('Unsupported withdrawal method')]);
|
return $this->fail([422, __('Unsupported withdrawal method')]);
|
||||||
}
|
}
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
$limit = admin_setting('commission_withdraw_limit', 100);
|
$limit = admin_setting('commission_withdraw_limit', 100);
|
||||||
if ($limit > ($user->commission_balance / 100)) {
|
if ($limit > ($user->commission_balance / 100)) {
|
||||||
return $this->fail([422, __('The current required minimum withdrawal commission is :limit', ['limit' => $limit])]);
|
return $this->fail([422, __('The current required minimum withdrawal commission is :limit', ['limit' => $limit])]);
|
||||||
@@ -154,7 +154,7 @@ class TicketController extends Controller
|
|||||||
$ticket = Ticket::create([
|
$ticket = Ticket::create([
|
||||||
'subject' => $subject,
|
'subject' => $subject,
|
||||||
'level' => 2,
|
'level' => 2,
|
||||||
'user_id' => $request->user['id']
|
'user_id' => $request->user()->id
|
||||||
]);
|
]);
|
||||||
if (!$ticket) {
|
if (!$ticket) {
|
||||||
return $this->fail([400, __('Failed to open ticket')]);
|
return $this->fail([400, __('Failed to open ticket')]);
|
||||||
@@ -164,7 +164,7 @@ class TicketController extends Controller
|
|||||||
__('Withdrawal account') . ":" . $request->input('withdraw_account')
|
__('Withdrawal account') . ":" . $request->input('withdraw_account')
|
||||||
);
|
);
|
||||||
$ticketMessage = TicketMessage::create([
|
$ticketMessage = TicketMessage::create([
|
||||||
'user_id' => $request->user['id'],
|
'user_id' => $request->user()->id,
|
||||||
'ticket_id' => $ticket->id,
|
'ticket_id' => $ticket->id,
|
||||||
'message' => $message
|
'message' => $message
|
||||||
]);
|
]);
|
||||||
@@ -177,7 +177,7 @@ class TicketController extends Controller
|
|||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
$this->sendNotify($ticket, $message, $request->user['id']);
|
$this->sendNotify($ticket, $message, $request->user()->id);
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use App\Services\AuthService;
|
|||||||
use App\Services\UserService;
|
use App\Services\UserService;
|
||||||
use App\Utils\CacheKey;
|
use App\Utils\CacheKey;
|
||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
|
use Auth;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ class UserController extends Controller
|
|||||||
{
|
{
|
||||||
public function getActiveSession(Request $request)
|
public function getActiveSession(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return $this->fail([400, __('The user does not exist')]);
|
return $this->fail([400, __('The user does not exist')]);
|
||||||
}
|
}
|
||||||
@@ -32,7 +33,7 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function removeActiveSession(Request $request)
|
public function removeActiveSession(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return $this->fail([400, __('The user does not exist')]);
|
return $this->fail([400, __('The user does not exist')]);
|
||||||
}
|
}
|
||||||
@@ -43,9 +44,9 @@ class UserController extends Controller
|
|||||||
public function checkLogin(Request $request)
|
public function checkLogin(Request $request)
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
'is_login' => $request->user['id'] ? true : false
|
'is_login' => $request->user()?->id ? true : false
|
||||||
];
|
];
|
||||||
if ($request->user['is_admin']) {
|
if ($request->user()?->is_admin) {
|
||||||
$data['is_admin'] = true;
|
$data['is_admin'] = true;
|
||||||
}
|
}
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
@@ -53,7 +54,7 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function changePassword(UserChangePassword $request)
|
public function changePassword(UserChangePassword $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return $this->fail([400, __('The user does not exist')]);
|
return $this->fail([400, __('The user does not exist')]);
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,7 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function info(Request $request)
|
public function info(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::where('id', $request->user['id'])
|
$user = User::where('id', $request->user()->id)
|
||||||
->select([
|
->select([
|
||||||
'email',
|
'email',
|
||||||
'transfer_enable',
|
'transfer_enable',
|
||||||
@@ -106,12 +107,12 @@ class UserController extends Controller
|
|||||||
{
|
{
|
||||||
$stat = [
|
$stat = [
|
||||||
Order::where('status', 0)
|
Order::where('status', 0)
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->count(),
|
->count(),
|
||||||
Ticket::where('status', 0)
|
Ticket::where('status', 0)
|
||||||
->where('user_id', $request->user['id'])
|
->where('user_id', $request->user()->id)
|
||||||
->count(),
|
->count(),
|
||||||
User::where('invite_user_id', $request->user['id'])
|
User::where('invite_user_id', $request->user()->id)
|
||||||
->count()
|
->count()
|
||||||
];
|
];
|
||||||
return $this->success($stat);
|
return $this->success($stat);
|
||||||
@@ -119,7 +120,7 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function getSubscribe(Request $request)
|
public function getSubscribe(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::where('id', $request->user['id'])
|
$user = User::where('id', $request->user()->id)
|
||||||
->select([
|
->select([
|
||||||
'plan_id',
|
'plan_id',
|
||||||
'token',
|
'token',
|
||||||
@@ -148,7 +149,7 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function resetSecurity(Request $request)
|
public function resetSecurity(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return $this->fail([400, __('The user does not exist')]);
|
return $this->fail([400, __('The user does not exist')]);
|
||||||
}
|
}
|
||||||
@@ -167,7 +168,7 @@ class UserController extends Controller
|
|||||||
'remind_traffic'
|
'remind_traffic'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return $this->fail([400, __('The user does not exist')]);
|
return $this->fail([400, __('The user does not exist')]);
|
||||||
}
|
}
|
||||||
@@ -182,7 +183,7 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function transfer(UserTransfer $request)
|
public function transfer(UserTransfer $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return $this->fail([400, __('The user does not exist')]);
|
return $this->fail([400, __('The user does not exist')]);
|
||||||
}
|
}
|
||||||
@@ -199,7 +200,7 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function getQuickLoginUrl(Request $request)
|
public function getQuickLoginUrl(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->user['id']);
|
$user = User::find($request->user()->id);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return $this->fail([400, __('The user does not exist')]);
|
return $this->fail([400, __('The user does not exist')]);
|
||||||
}
|
}
|
||||||
|
|||||||
117
app/Http/Controllers/V1/Admin/ConfigController.php → app/Http/Controllers/V2/Admin/ConfigController.php
Executable file → Normal file
117
app/Http/Controllers/V1/Admin/ConfigController.php → app/Http/Controllers/V2/Admin/ConfigController.php
Executable file → Normal file
@@ -1,15 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Admin\ConfigSave;
|
use App\Http\Requests\Admin\ConfigSave;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Services\MailService;
|
use App\Services\MailService;
|
||||||
use App\Services\TelegramService;
|
use App\Services\TelegramService;
|
||||||
|
use App\Services\ThemeService;
|
||||||
use App\Utils\Dict;
|
use App\Utils\Dict;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
|
|
||||||
class ConfigController extends Controller
|
class ConfigController extends Controller
|
||||||
{
|
{
|
||||||
@@ -34,7 +34,7 @@ class ConfigController extends Controller
|
|||||||
public function testSendMail(Request $request)
|
public function testSendMail(Request $request)
|
||||||
{
|
{
|
||||||
$mailLog = MailService::sendEmail([
|
$mailLog = MailService::sendEmail([
|
||||||
'email' => $request->user['email'],
|
'email' => $request->user()->email,
|
||||||
'subject' => 'This is xboard test email',
|
'subject' => 'This is xboard test email',
|
||||||
'template_name' => 'notify',
|
'template_name' => 'notify',
|
||||||
'template_value' => [
|
'template_value' => [
|
||||||
@@ -44,8 +44,7 @@ class ConfigController extends Controller
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
return response([
|
return response([
|
||||||
'data' => true,
|
'data' => $mailLog,
|
||||||
'log' => $mailLog
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,8 +52,9 @@ class ConfigController extends Controller
|
|||||||
{
|
{
|
||||||
// 判断站点网址
|
// 判断站点网址
|
||||||
$app_url = admin_setting('app_url');
|
$app_url = admin_setting('app_url');
|
||||||
if(blank($app_url)) return $this->fail([422, '请先设置站点网址']);
|
if (blank($app_url))
|
||||||
$hookUrl = $app_url .'/api/v1/guest/telegram/webhook?' . http_build_query([
|
return $this->fail([422, '请先设置站点网址']);
|
||||||
|
$hookUrl = $app_url . '/api/v1/guest/telegram/webhook?' . http_build_query([
|
||||||
'access_token' => md5(admin_setting('telegram_bot_token', $request->input('telegram_bot_token')))
|
'access_token' => md5(admin_setting('telegram_bot_token', $request->input('telegram_bot_token')))
|
||||||
]);
|
]);
|
||||||
$telegramService = new TelegramService($request->input('telegram_bot_token'));
|
$telegramService = new TelegramService($request->input('telegram_bot_token'));
|
||||||
@@ -68,45 +68,46 @@ class ConfigController extends Controller
|
|||||||
$key = $request->input('key');
|
$key = $request->input('key');
|
||||||
$data = [
|
$data = [
|
||||||
'invite' => [
|
'invite' => [
|
||||||
'invite_force' => (int)admin_setting('invite_force', 0),
|
'invite_force' => (bool) admin_setting('invite_force', 0),
|
||||||
'invite_commission' => admin_setting('invite_commission', 10),
|
'invite_commission' => admin_setting('invite_commission', 10),
|
||||||
'invite_gen_limit' => admin_setting('invite_gen_limit', 5),
|
'invite_gen_limit' => admin_setting('invite_gen_limit', 5),
|
||||||
'invite_never_expire' => admin_setting('invite_never_expire', 0),
|
'invite_never_expire' => (bool) admin_setting('invite_never_expire', 0),
|
||||||
'commission_first_time_enable' => admin_setting('commission_first_time_enable', 1),
|
'commission_first_time_enable' => (bool) admin_setting('commission_first_time_enable', 1),
|
||||||
'commission_auto_check_enable' => admin_setting('commission_auto_check_enable', 1),
|
'commission_auto_check_enable' => (bool) admin_setting('commission_auto_check_enable', 1),
|
||||||
'commission_withdraw_limit' => admin_setting('commission_withdraw_limit', 100),
|
'commission_withdraw_limit' => admin_setting('commission_withdraw_limit', 100),
|
||||||
'commission_withdraw_method' => admin_setting('commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
|
'commission_withdraw_method' => admin_setting('commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
|
||||||
'withdraw_close_enable' => admin_setting('withdraw_close_enable', 0),
|
'withdraw_close_enable' => (bool) admin_setting('withdraw_close_enable', 0),
|
||||||
'commission_distribution_enable' => admin_setting('commission_distribution_enable', 0),
|
'commission_distribution_enable' => (bool) admin_setting('commission_distribution_enable', 0),
|
||||||
'commission_distribution_l1' => admin_setting('commission_distribution_l1'),
|
'commission_distribution_l1' => admin_setting('commission_distribution_l1'),
|
||||||
'commission_distribution_l2' => admin_setting('commission_distribution_l2'),
|
'commission_distribution_l2' => admin_setting('commission_distribution_l2'),
|
||||||
'commission_distribution_l3' => admin_setting('commission_distribution_l3')
|
'commission_distribution_l3' => admin_setting('commission_distribution_l3')
|
||||||
],
|
],
|
||||||
'site' => [
|
'site' => [
|
||||||
'logo' => admin_setting('logo'),
|
'logo' => admin_setting('logo'),
|
||||||
'force_https' => (int)admin_setting('force_https', 0),
|
'force_https' => (int) admin_setting('force_https', 0),
|
||||||
'stop_register' => (int)admin_setting('stop_register', 0),
|
'stop_register' => (int) admin_setting('stop_register', 0),
|
||||||
'app_name' => admin_setting('app_name', 'XBoard'),
|
'app_name' => admin_setting('app_name', 'XBoard'),
|
||||||
'app_description' => admin_setting('app_description', 'XBoard is best!'),
|
'app_description' => admin_setting('app_description', 'XBoard is best!'),
|
||||||
'app_url' => admin_setting('app_url'),
|
'app_url' => admin_setting('app_url'),
|
||||||
'subscribe_url' => admin_setting('subscribe_url'),
|
'subscribe_url' => admin_setting('subscribe_url'),
|
||||||
'try_out_plan_id' => (int)admin_setting('try_out_plan_id', 0),
|
'try_out_plan_id' => (int) admin_setting('try_out_plan_id', 0),
|
||||||
'try_out_hour' => (int)admin_setting('try_out_hour', 1),
|
'try_out_hour' => (int) admin_setting('try_out_hour', 1),
|
||||||
'tos_url' => admin_setting('tos_url'),
|
'tos_url' => admin_setting('tos_url'),
|
||||||
'currency' => admin_setting('currency', 'CNY'),
|
'currency' => admin_setting('currency', 'CNY'),
|
||||||
'currency_symbol' => admin_setting('currency_symbol', '¥'),
|
'currency_symbol' => admin_setting('currency_symbol', '¥'),
|
||||||
],
|
],
|
||||||
'subscribe' => [
|
'subscribe' => [
|
||||||
'plan_change_enable' => (int)admin_setting('plan_change_enable', 1),
|
'plan_change_enable' => (bool) admin_setting('plan_change_enable', 1),
|
||||||
'reset_traffic_method' => (int)admin_setting('reset_traffic_method', 0),
|
'reset_traffic_method' => (int) admin_setting('reset_traffic_method', 0),
|
||||||
'surplus_enable' => (int)admin_setting('surplus_enable', 1),
|
'surplus_enable' => (bool) admin_setting('surplus_enable', 1),
|
||||||
'new_order_event_id' => (int)admin_setting('new_order_event_id', 0),
|
'new_order_event_id' => (int) admin_setting('new_order_event_id', 0),
|
||||||
'renew_order_event_id' => (int)admin_setting('renew_order_event_id', 0),
|
'renew_order_event_id' => (int) admin_setting('renew_order_event_id', 0),
|
||||||
'change_order_event_id' => (int)admin_setting('change_order_event_id', 0),
|
'change_order_event_id' => (int) admin_setting('change_order_event_id', 0),
|
||||||
'show_info_to_server_enable' => (int)admin_setting('show_info_to_server_enable', 0),
|
'show_info_to_server_enable' => (bool) admin_setting('show_info_to_server_enable', 0),
|
||||||
'show_protocol_to_server_enable' => (int)admin_setting('show_protocol_to_server_enable', 0),
|
'show_protocol_to_server_enable' => (bool) admin_setting('show_protocol_to_server_enable', 0),
|
||||||
'default_remind_expire' => (int)admin_setting('default_remind_expire',1),
|
'default_remind_expire' => (bool) admin_setting('default_remind_expire', 1),
|
||||||
'default_remind_traffic' => (int)admin_setting('default_remind_traffic',1),
|
'default_remind_traffic' => (bool) admin_setting('default_remind_traffic', 1),
|
||||||
|
'subscribe_path' => admin_setting('subscribe_path', 's'),
|
||||||
|
|
||||||
],
|
],
|
||||||
'frontend' => [
|
'frontend' => [
|
||||||
@@ -120,6 +121,7 @@ class ConfigController extends Controller
|
|||||||
'server_token' => admin_setting('server_token'),
|
'server_token' => admin_setting('server_token'),
|
||||||
'server_pull_interval' => admin_setting('server_pull_interval', 60),
|
'server_pull_interval' => admin_setting('server_pull_interval', 60),
|
||||||
'server_push_interval' => admin_setting('server_push_interval', 60),
|
'server_push_interval' => admin_setting('server_push_interval', 60),
|
||||||
|
'device_limit_mode' => (int) admin_setting('device_limit_mode', 0),
|
||||||
],
|
],
|
||||||
'email' => [
|
'email' => [
|
||||||
'email_template' => admin_setting('email_template', 'default'),
|
'email_template' => admin_setting('email_template', 'default'),
|
||||||
@@ -128,35 +130,36 @@ class ConfigController extends Controller
|
|||||||
'email_username' => admin_setting('email_username'),
|
'email_username' => admin_setting('email_username'),
|
||||||
'email_password' => admin_setting('email_password'),
|
'email_password' => admin_setting('email_password'),
|
||||||
'email_encryption' => admin_setting('email_encryption'),
|
'email_encryption' => admin_setting('email_encryption'),
|
||||||
'email_from_address' => admin_setting('email_from_address')
|
'email_from_address' => admin_setting('email_from_address'),
|
||||||
|
'remind_mail_enable' => (bool) admin_setting('remind_mail_enable', false),
|
||||||
],
|
],
|
||||||
'telegram' => [
|
'telegram' => [
|
||||||
'telegram_bot_enable' => admin_setting('telegram_bot_enable', 0),
|
'telegram_bot_enable' => (bool) admin_setting('telegram_bot_enable', 0),
|
||||||
'telegram_bot_token' => admin_setting('telegram_bot_token'),
|
'telegram_bot_token' => admin_setting('telegram_bot_token'),
|
||||||
'telegram_discuss_link' => admin_setting('telegram_discuss_link')
|
'telegram_discuss_link' => admin_setting('telegram_discuss_link')
|
||||||
],
|
],
|
||||||
'app' => [
|
'app' => [
|
||||||
'windows_version' => admin_setting('windows_version'),
|
'windows_version' => admin_setting('windows_version', ''),
|
||||||
'windows_download_url' => admin_setting('windows_download_url'),
|
'windows_download_url' => admin_setting('windows_download_url', ''),
|
||||||
'macos_version' => admin_setting('macos_version'),
|
'macos_version' => admin_setting('macos_version', ''),
|
||||||
'macos_download_url' => admin_setting('macos_download_url'),
|
'macos_download_url' => admin_setting('macos_download_url', ''),
|
||||||
'android_version' => admin_setting('android_version'),
|
'android_version' => admin_setting('android_version', ''),
|
||||||
'android_download_url' => admin_setting('android_download_url')
|
'android_download_url' => admin_setting('android_download_url', '')
|
||||||
],
|
],
|
||||||
'safe' => [
|
'safe' => [
|
||||||
'email_verify' => (int)admin_setting('email_verify', 0),
|
'email_verify' => (bool) admin_setting('email_verify', 0),
|
||||||
'safe_mode_enable' => (int)admin_setting('safe_mode_enable', 0),
|
'safe_mode_enable' => (bool) admin_setting('safe_mode_enable', 0),
|
||||||
'secure_path' => admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))),
|
'secure_path' => admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))),
|
||||||
'email_whitelist_enable' => (int)admin_setting('email_whitelist_enable', 0),
|
'email_whitelist_enable' => (bool) admin_setting('email_whitelist_enable', 0),
|
||||||
'email_whitelist_suffix' => admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
|
'email_whitelist_suffix' => admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
|
||||||
'email_gmail_limit_enable' => admin_setting('email_gmail_limit_enable', 0),
|
'email_gmail_limit_enable' => (bool) admin_setting('email_gmail_limit_enable', 0),
|
||||||
'recaptcha_enable' => (int)admin_setting('recaptcha_enable', 0),
|
'recaptcha_enable' => (bool) admin_setting('recaptcha_enable', 0),
|
||||||
'recaptcha_key' => admin_setting('recaptcha_key'),
|
'recaptcha_key' => admin_setting('recaptcha_key', ''),
|
||||||
'recaptcha_site_key' => admin_setting('recaptcha_site_key'),
|
'recaptcha_site_key' => admin_setting('recaptcha_site_key', ''),
|
||||||
'register_limit_by_ip_enable' => (int)admin_setting('register_limit_by_ip_enable', 0),
|
'register_limit_by_ip_enable' => (bool) admin_setting('register_limit_by_ip_enable', 0),
|
||||||
'register_limit_count' => admin_setting('register_limit_count', 3),
|
'register_limit_count' => admin_setting('register_limit_count', 3),
|
||||||
'register_limit_expire' => admin_setting('register_limit_expire', 60),
|
'register_limit_expire' => admin_setting('register_limit_expire', 60),
|
||||||
'password_limit_enable' => (int)admin_setting('password_limit_enable', 1),
|
'password_limit_enable' => (bool) admin_setting('password_limit_enable', 1),
|
||||||
'password_limit_count' => admin_setting('password_limit_count', 5),
|
'password_limit_count' => admin_setting('password_limit_count', 5),
|
||||||
'password_limit_expire' => admin_setting('password_limit_expire', 60)
|
'password_limit_expire' => admin_setting('password_limit_expire', 60)
|
||||||
]
|
]
|
||||||
@@ -165,7 +168,8 @@ class ConfigController extends Controller
|
|||||||
return $this->success([
|
return $this->success([
|
||||||
$key => $data[$key]
|
$key => $data[$key]
|
||||||
]);
|
]);
|
||||||
};
|
}
|
||||||
|
;
|
||||||
// TODO: default should be in Dict
|
// TODO: default should be in Dict
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
}
|
}
|
||||||
@@ -173,26 +177,13 @@ class ConfigController extends Controller
|
|||||||
public function save(ConfigSave $request)
|
public function save(ConfigSave $request)
|
||||||
{
|
{
|
||||||
$data = $request->validated();
|
$data = $request->validated();
|
||||||
$config = config('v2board');
|
foreach ($data as $k => $v) {
|
||||||
foreach (ConfigSave::RULES as $k => $v) {
|
if ($k == 'frontend_theme') {
|
||||||
if (!in_array($k, array_keys(ConfigSave::RULES))) {
|
$themeService = new ThemeService();
|
||||||
unset($config[$k]);
|
$themeService->switch($v);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (array_key_exists($k, $data)) {
|
|
||||||
$value = $data[$k];
|
|
||||||
if (is_array($value)) $value = json_encode($value);
|
|
||||||
Setting::updateOrCreate(
|
|
||||||
['name' => $k],
|
|
||||||
['name' => $k, 'value' => $value]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
admin_setting([$k => $v]);
|
||||||
}
|
}
|
||||||
// 如果是workerman环境,则触发reload
|
|
||||||
if(isset(get_defined_constants(true)['user']['Workerman'])){
|
|
||||||
posix_kill(posix_getppid(), SIGUSR1);
|
|
||||||
}
|
|
||||||
Cache::forget('admin_settings');
|
|
||||||
// \Artisan::call('horizon:terminate'); //重启队列使配置生效
|
// \Artisan::call('horizon:terminate'); //重启队列使配置生效
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
189
app/Http/Controllers/V2/Admin/CouponController.php
Normal file
189
app/Http/Controllers/V2/Admin/CouponController.php
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
|
use App\Exceptions\ApiException;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Admin\CouponGenerate;
|
||||||
|
use App\Http\Requests\Admin\CouponSave;
|
||||||
|
use App\Models\Coupon;
|
||||||
|
use App\Utils\Helper;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class CouponController extends Controller
|
||||||
|
{
|
||||||
|
private function applyFiltersAndSorts(Request $request, $builder)
|
||||||
|
{
|
||||||
|
if ($request->has('filter')) {
|
||||||
|
collect($request->input('filter'))->each(function ($filter) use ($builder) {
|
||||||
|
$key = $filter['id'];
|
||||||
|
$value = $filter['value'];
|
||||||
|
$builder->where(function ($query) use ($key, $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$query->whereIn($key, $value);
|
||||||
|
} else {
|
||||||
|
$query->where($key, 'like', "%{$value}%");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('sort')) {
|
||||||
|
collect($request->input('sort'))->each(function ($sort) use ($builder) {
|
||||||
|
$key = $sort['id'];
|
||||||
|
$value = $sort['desc'] ? 'DESC' : 'ASC';
|
||||||
|
$builder->orderBy($key, $value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function fetch(Request $request)
|
||||||
|
{
|
||||||
|
$current = $request->input('current', 1);
|
||||||
|
$pageSize = $request->input('pageSize', 10);
|
||||||
|
$builder = Coupon::query();
|
||||||
|
$this->applyFiltersAndSorts($request, $builder);
|
||||||
|
$coupons = $builder
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->paginate($pageSize, ["*"], 'page', $current);
|
||||||
|
return response([
|
||||||
|
'data' => $coupons->items(),
|
||||||
|
'total' => $coupons->total()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request)
|
||||||
|
{
|
||||||
|
$params = $request->validate([
|
||||||
|
'id' => 'required|numeric',
|
||||||
|
'show' => 'nullable|boolean'
|
||||||
|
], [
|
||||||
|
'id.required' => '优惠券ID不能为空',
|
||||||
|
'id.numeric' => '优惠券ID必须为数字'
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
$coupon = Coupon::find($request->input('id'));
|
||||||
|
if (!$coupon) {
|
||||||
|
throw new ApiException(400201, '优惠券不存在');
|
||||||
|
}
|
||||||
|
$coupon->update($params);
|
||||||
|
DB::commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error($e);
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|numeric'
|
||||||
|
], [
|
||||||
|
'id.required' => '优惠券ID不能为空',
|
||||||
|
'id.numeric' => '优惠券ID必须为数字'
|
||||||
|
]);
|
||||||
|
$coupon = Coupon::find($request->input('id'));
|
||||||
|
if (!$coupon) {
|
||||||
|
return $this->fail([400202, '优惠券不存在']);
|
||||||
|
}
|
||||||
|
$coupon->show = !$coupon->show;
|
||||||
|
if (!$coupon->save()) {
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(CouponGenerate $request)
|
||||||
|
{
|
||||||
|
if ($request->input('generate_count')) {
|
||||||
|
$this->multiGenerate($request);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = $request->validated();
|
||||||
|
if (!$request->input('id')) {
|
||||||
|
if (!isset($params['code'])) {
|
||||||
|
$params['code'] = Helper::randomChar(8);
|
||||||
|
}
|
||||||
|
if (!Coupon::create($params)) {
|
||||||
|
return $this->fail([500, '创建失败']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Coupon::find($request->input('id'))->update($params);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error($e);
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function multiGenerate(CouponGenerate $request)
|
||||||
|
{
|
||||||
|
$coupons = [];
|
||||||
|
$coupon = $request->validated();
|
||||||
|
$coupon['created_at'] = $coupon['updated_at'] = time();
|
||||||
|
$coupon['show'] = 1;
|
||||||
|
unset($coupon['generate_count']);
|
||||||
|
for ($i = 0; $i < $request->input('generate_count'); $i++) {
|
||||||
|
$coupon['code'] = Helper::randomChar(8);
|
||||||
|
array_push($coupons, $coupon);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
if (
|
||||||
|
!Coupon::insert(array_map(function ($item) use ($coupon) {
|
||||||
|
// format data
|
||||||
|
if (isset($item['limit_plan_ids']) && is_array($item['limit_plan_ids'])) {
|
||||||
|
$item['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
|
||||||
|
}
|
||||||
|
if (isset($item['limit_period']) && is_array($item['limit_period'])) {
|
||||||
|
$item['limit_period'] = json_encode($coupon['limit_period']);
|
||||||
|
}
|
||||||
|
return $item;
|
||||||
|
}, $coupons))
|
||||||
|
) {
|
||||||
|
throw new \Exception();
|
||||||
|
}
|
||||||
|
DB::commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return $this->fail([500, '生成失败']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = "名称,类型,金额或比例,开始时间,结束时间,可用次数,可用于订阅,券码,生成时间\r\n";
|
||||||
|
foreach ($coupons as $coupon) {
|
||||||
|
$type = ['', '金额', '比例'][$coupon['type']];
|
||||||
|
$value = ['', ($coupon['value'] / 100), $coupon['value']][$coupon['type']];
|
||||||
|
$startTime = date('Y-m-d H:i:s', $coupon['started_at']);
|
||||||
|
$endTime = date('Y-m-d H:i:s', $coupon['ended_at']);
|
||||||
|
$limitUse = $coupon['limit_use'] ?? '不限制';
|
||||||
|
$createTime = date('Y-m-d H:i:s', $coupon['created_at']);
|
||||||
|
$limitPlanIds = isset($coupon['limit_plan_ids']) ? implode("/", $coupon['limit_plan_ids']) : '不限制';
|
||||||
|
$data .= "{$coupon['name']},{$type},{$value},{$startTime},{$endTime},{$limitUse},{$limitPlanIds},{$coupon['code']},{$createTime}\r\n";
|
||||||
|
}
|
||||||
|
echo $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function drop(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|numeric'
|
||||||
|
], [
|
||||||
|
'id.required' => '优惠券ID不能为空',
|
||||||
|
'id.numeric' => '优惠券ID必须为数字'
|
||||||
|
]);
|
||||||
|
$coupon = Coupon::find($request->input('id'));
|
||||||
|
if (!$coupon) {
|
||||||
|
return $this->fail([400202, '优惠券不存在']);
|
||||||
|
}
|
||||||
|
if (!$coupon->delete()) {
|
||||||
|
return $this->fail([500, '删除失败']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@@ -16,12 +16,13 @@ class KnowledgeController extends Controller
|
|||||||
{
|
{
|
||||||
if ($request->input('id')) {
|
if ($request->input('id')) {
|
||||||
$knowledge = Knowledge::find($request->input('id'))->toArray();
|
$knowledge = Knowledge::find($request->input('id'))->toArray();
|
||||||
if (!$knowledge) return $this->fail([400202,'知识不存在']);
|
if (!$knowledge)
|
||||||
|
return $this->fail([400202, '知识不存在']);
|
||||||
return $this->success($knowledge);
|
return $this->success($knowledge);
|
||||||
}
|
}
|
||||||
$data = Knowledge::select(['title', 'id', 'updated_at', 'category', 'show'])
|
$data = Knowledge::select(['title', 'id', 'updated_at', 'category', 'show'])
|
||||||
->orderBy('sort', 'ASC')
|
->orderBy('sort', 'ASC')
|
||||||
->get();
|
->get();
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,14 +37,14 @@ class KnowledgeController extends Controller
|
|||||||
|
|
||||||
if (!$request->input('id')) {
|
if (!$request->input('id')) {
|
||||||
if (!Knowledge::create($params)) {
|
if (!Knowledge::create($params)) {
|
||||||
return $this->fail([500,'创建失败']);
|
return $this->fail([500, '创建失败']);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
Knowledge::find($request->input('id'))->update($params);
|
Knowledge::find($request->input('id'))->update($params);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error($e);
|
\Log::error($e);
|
||||||
return $this->fail([500,'创建失败']);
|
return $this->fail([500, '创建失败']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +55,8 @@ class KnowledgeController extends Controller
|
|||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'id' => 'required|numeric'
|
'id' => 'required|numeric'
|
||||||
],[
|
], [
|
||||||
'id.required' => '知识库ID不能为空'
|
'id.required' => '知识库ID不能为空'
|
||||||
]);
|
]);
|
||||||
$knowledge = Knowledge::find($request->input('id'));
|
$knowledge = Knowledge::find($request->input('id'));
|
||||||
if (!$knowledge) {
|
if (!$knowledge) {
|
||||||
@@ -69,11 +70,17 @@ class KnowledgeController extends Controller
|
|||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sort(KnowledgeSort $request)
|
public function sort(Request $request)
|
||||||
{
|
{
|
||||||
|
$request->validate([
|
||||||
|
'ids' => 'required|array'
|
||||||
|
], [
|
||||||
|
'ids.required' => '参数有误',
|
||||||
|
'ids.array' => '参数有误'
|
||||||
|
]);
|
||||||
try {
|
try {
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
foreach ($request->input('knowledge_ids') as $k => $v) {
|
foreach ($request->input('ids') as $k => $v) {
|
||||||
$knowledge = Knowledge::find($v);
|
$knowledge = Knowledge::find($v);
|
||||||
$knowledge->timestamps = false;
|
$knowledge->timestamps = false;
|
||||||
$knowledge->update(['sort' => $k + 1]);
|
$knowledge->update(['sort' => $k + 1]);
|
||||||
@@ -90,15 +97,15 @@ class KnowledgeController extends Controller
|
|||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'id' => 'required|numeric'
|
'id' => 'required|numeric'
|
||||||
],[
|
], [
|
||||||
'id.required' => '知识库ID不能为空'
|
'id.required' => '知识库ID不能为空'
|
||||||
]);
|
]);
|
||||||
$knowledge = Knowledge::find($request->input('id'));
|
$knowledge = Knowledge::find($request->input('id'));
|
||||||
if (!$knowledge) {
|
if (!$knowledge) {
|
||||||
return $this->fail([400202,'知识不存在']);
|
return $this->fail([400202, '知识不存在']);
|
||||||
}
|
}
|
||||||
if (!$knowledge->delete()) {
|
if (!$knowledge->delete()) {
|
||||||
return $this->fail([500,'删除失败']);
|
return $this->fail([500, '删除失败']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
101
app/Http/Controllers/V2/Admin/NoticeController.php
Normal file
101
app/Http/Controllers/V2/Admin/NoticeController.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
|
use App\Exceptions\ApiException;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Admin\NoticeSave;
|
||||||
|
use App\Models\Notice;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class NoticeController extends Controller
|
||||||
|
{
|
||||||
|
public function fetch(Request $request)
|
||||||
|
{
|
||||||
|
return $this->success(
|
||||||
|
Notice::orderBy('sort', 'ASC')
|
||||||
|
->orderBy('id', 'DESC')
|
||||||
|
->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(NoticeSave $request)
|
||||||
|
{
|
||||||
|
$data = $request->only([
|
||||||
|
'title',
|
||||||
|
'content',
|
||||||
|
'img_url',
|
||||||
|
'tags',
|
||||||
|
'show',
|
||||||
|
'popup'
|
||||||
|
]);
|
||||||
|
if (!$request->input('id')) {
|
||||||
|
if (!Notice::create($data)) {
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Notice::find($request->input('id'))->update($data);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
if (empty($request->input('id'))) {
|
||||||
|
return $this->fail([500, '公告ID不能为空']);
|
||||||
|
}
|
||||||
|
$notice = Notice::find($request->input('id'));
|
||||||
|
if (!$notice) {
|
||||||
|
return $this->fail([400202, '公告不存在']);
|
||||||
|
}
|
||||||
|
$notice->show = $notice->show ? 0 : 1;
|
||||||
|
if (!$notice->save()) {
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function drop(Request $request)
|
||||||
|
{
|
||||||
|
if (empty($request->input('id'))) {
|
||||||
|
return $this->fail([422, '公告ID不能为空']);
|
||||||
|
}
|
||||||
|
$notice = Notice::find($request->input('id'));
|
||||||
|
if (!$notice) {
|
||||||
|
return $this->fail([400202, '公告不存在']);
|
||||||
|
}
|
||||||
|
if (!$notice->delete()) {
|
||||||
|
return $this->fail([500, '删除失败']);
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sort(Request $request)
|
||||||
|
{
|
||||||
|
$params = $request->validate([
|
||||||
|
'ids' => 'required|array'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
foreach ($params['ids'] as $k => $v) {
|
||||||
|
$notice = Notice::findOrFail($v);
|
||||||
|
$notice->update(['sort' => $k + 1]);
|
||||||
|
}
|
||||||
|
DB::commit();
|
||||||
|
return $this->success(true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
\Log::error($e);
|
||||||
|
return $this->fail([500, '排序保存失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
246
app/Http/Controllers/V2/Admin/OrderController.php
Normal file
246
app/Http/Controllers/V2/Admin/OrderController.php
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Admin\OrderAssign;
|
||||||
|
use App\Http\Requests\Admin\OrderUpdate;
|
||||||
|
use App\Models\Order;
|
||||||
|
use App\Models\Plan;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\OrderService;
|
||||||
|
use App\Services\PlanService;
|
||||||
|
use App\Services\UserService;
|
||||||
|
use App\Utils\Helper;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
class OrderController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function detail(Request $request)
|
||||||
|
{
|
||||||
|
$order = Order::with(['user', 'plan', 'commission_log'])->find($request->input('id'));
|
||||||
|
if (!$order)
|
||||||
|
return $this->fail([400202, '订单不存在']);
|
||||||
|
if ($order->surplus_order_ids) {
|
||||||
|
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
|
||||||
|
}
|
||||||
|
$order['period'] = PlanService::getLegacyPeriod($order->period);
|
||||||
|
return $this->success($order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetch(Request $request)
|
||||||
|
{
|
||||||
|
$current = $request->input('current', 1);
|
||||||
|
$pageSize = $request->input('pageSize', 10);
|
||||||
|
$orderModel = Order::with('plan:id,name');
|
||||||
|
|
||||||
|
if ($request->boolean('is_commission')) {
|
||||||
|
$orderModel->whereNotNull('invite_user_id')
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->where('commission_balance', '>', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->applyFiltersAndSorts($request, $orderModel);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
$orderModel
|
||||||
|
->latest('created_at')
|
||||||
|
->paginate(
|
||||||
|
perPage: $pageSize,
|
||||||
|
page: $current
|
||||||
|
)->through(fn($order) => [
|
||||||
|
...$order->toArray(),
|
||||||
|
'period' => PlanService::getLegacyPeriod($order->period)
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyFiltersAndSorts(Request $request, Builder $builder): void
|
||||||
|
{
|
||||||
|
$this->applyFilters($request, $builder);
|
||||||
|
$this->applySorting($request, $builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyFilters(Request $request, Builder $builder): void
|
||||||
|
{
|
||||||
|
if (!$request->has('filter')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
collect($request->input('filter'))->each(function ($filter) use ($builder) {
|
||||||
|
$field = $filter['id'];
|
||||||
|
$value = $filter['value'];
|
||||||
|
|
||||||
|
$builder->where(function ($query) use ($field, $value) {
|
||||||
|
$this->buildFilterQuery($query, $field, $value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFilterQuery(Builder $query, string $field, mixed $value): void
|
||||||
|
{
|
||||||
|
// Handle array values for 'in' operations
|
||||||
|
if (is_array($value)) {
|
||||||
|
$query->whereIn($field, $value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle operator-based filtering
|
||||||
|
if (!is_string($value) || !str_contains($value, ':')) {
|
||||||
|
$query->where($field, 'like', "%{$value}%");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$operator, $filterValue] = explode(':', $value, 2);
|
||||||
|
|
||||||
|
// Convert numeric strings to appropriate type
|
||||||
|
if (is_numeric($filterValue)) {
|
||||||
|
$filterValue = strpos($filterValue, '.') !== false
|
||||||
|
? (float) $filterValue
|
||||||
|
: (int) $filterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply operator
|
||||||
|
$query->where($field, match (strtolower($operator)) {
|
||||||
|
'eq' => '=',
|
||||||
|
'gt' => '>',
|
||||||
|
'gte' => '>=',
|
||||||
|
'lt' => '<',
|
||||||
|
'lte' => '<=',
|
||||||
|
'like' => 'like',
|
||||||
|
'notlike' => 'not like',
|
||||||
|
'null' => static fn($q) => $q->whereNull($queryField),
|
||||||
|
'notnull' => static fn($q) => $q->whereNotNull($queryField),
|
||||||
|
default => 'like'
|
||||||
|
}, match (strtolower($operator)) {
|
||||||
|
'like', 'notlike' => "%{$filterValue}%",
|
||||||
|
'null', 'notnull' => null,
|
||||||
|
default => $filterValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applySorting(Request $request, Builder $builder): void
|
||||||
|
{
|
||||||
|
if (!$request->has('sort')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
collect($request->input('sort'))->each(function ($sort) use ($builder) {
|
||||||
|
$field = $sort['id'];
|
||||||
|
$direction = $sort['desc'] ? 'DESC' : 'ASC';
|
||||||
|
$builder->orderBy($field, $direction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paid(Request $request)
|
||||||
|
{
|
||||||
|
$order = Order::where('trade_no', $request->input('trade_no'))
|
||||||
|
->first();
|
||||||
|
if (!$order) {
|
||||||
|
return $this->fail([400202, '订单不存在']);
|
||||||
|
}
|
||||||
|
if ($order->status !== 0)
|
||||||
|
return $this->fail([400, '只能对待支付的订单进行操作']);
|
||||||
|
|
||||||
|
$orderService = new OrderService($order);
|
||||||
|
if (!$orderService->paid('manual_operation')) {
|
||||||
|
return $this->fail([500, '更新失败']);
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel(Request $request)
|
||||||
|
{
|
||||||
|
$order = Order::where('trade_no', $request->input('trade_no'))
|
||||||
|
->first();
|
||||||
|
if (!$order) {
|
||||||
|
return $this->fail([400202, '订单不存在']);
|
||||||
|
}
|
||||||
|
if ($order->status !== 0)
|
||||||
|
return $this->fail([400, '只能对待支付的订单进行操作']);
|
||||||
|
|
||||||
|
$orderService = new OrderService($order);
|
||||||
|
if (!$orderService->cancel()) {
|
||||||
|
return $this->fail([400, '更新失败']);
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(OrderUpdate $request)
|
||||||
|
{
|
||||||
|
$params = $request->only([
|
||||||
|
'commission_status'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$order = Order::where('trade_no', $request->input('trade_no'))
|
||||||
|
->first();
|
||||||
|
if (!$order) {
|
||||||
|
return $this->fail([400202, '订单不存在']);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$order->update($params);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error($e);
|
||||||
|
return $this->fail([500, '更新失败']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assign(OrderAssign $request)
|
||||||
|
{
|
||||||
|
$plan = Plan::find($request->input('plan_id'));
|
||||||
|
$user = User::where('email', $request->input('email'))->first();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return $this->fail([400202, '该用户不存在']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$plan) {
|
||||||
|
return $this->fail([400202, '该订阅不存在']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userService = new UserService();
|
||||||
|
if ($userService->isNotCompleteOrderByUserId($user->id)) {
|
||||||
|
return $this->fail([400, '该用户还有待支付的订单,无法分配']);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
$order = new Order();
|
||||||
|
$orderService = new OrderService($order);
|
||||||
|
$order->user_id = $user->id;
|
||||||
|
$order->plan_id = $plan->id;
|
||||||
|
$order->period = PlanService::getPeriodKey($request->input('period'));
|
||||||
|
$order->trade_no = Helper::guid();
|
||||||
|
$order->total_amount = $request->input('total_amount');
|
||||||
|
|
||||||
|
if (PlanService::getPeriodKey($order->period) === Plan::PERIOD_RESET_TRAFFIC) {
|
||||||
|
$order->type = Order::TYPE_RESET_TRAFFIC;
|
||||||
|
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
|
||||||
|
$order->type = Order::TYPE_UPGRADE;
|
||||||
|
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
|
||||||
|
$order->type = Order::TYPE_RENEWAL;
|
||||||
|
} else {
|
||||||
|
$order->type = Order::TYPE_NEW_PURCHASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderService->setInvite($user);
|
||||||
|
|
||||||
|
if (!$order->save()) {
|
||||||
|
DB::rollBack();
|
||||||
|
return $this->fail([500, '订单创建失败']);
|
||||||
|
}
|
||||||
|
DB::commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success($order->trade_no);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@@ -38,7 +38,7 @@ class PaymentController extends Controller
|
|||||||
public function getPaymentForm(Request $request)
|
public function getPaymentForm(Request $request)
|
||||||
{
|
{
|
||||||
$paymentService = new PaymentService($request->input('payment'), $request->input('id'));
|
$paymentService = new PaymentService($request->input('payment'), $request->input('id'));
|
||||||
return $this->success($paymentService->form());
|
return $this->success(collect($paymentService->form())->values());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Request $request)
|
public function show(Request $request)
|
||||||
78
app/Http/Controllers/V1/Admin/PlanController.php → app/Http/Controllers/V2/Admin/PlanController.php
Executable file → Normal file
78
app/Http/Controllers/V1/Admin/PlanController.php → app/Http/Controllers/V2/Admin/PlanController.php
Executable file → Normal file
@@ -1,16 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Admin\PlanSave;
|
|
||||||
use App\Http\Requests\Admin\PlanSort;
|
|
||||||
use App\Http\Requests\Admin\PlanUpdate;
|
|
||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\PlanService;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
@@ -18,24 +13,34 @@ class PlanController extends Controller
|
|||||||
{
|
{
|
||||||
public function fetch(Request $request)
|
public function fetch(Request $request)
|
||||||
{
|
{
|
||||||
$counts = PlanService::countActiveUsers();
|
$plans = Plan::orderBy('sort', 'ASC')
|
||||||
$plans = Plan::orderBy('sort', 'ASC')->get();
|
->with([
|
||||||
foreach ($plans as $k => $v) {
|
'group:id,name'
|
||||||
$plans[$k]->count = 0;
|
])
|
||||||
foreach ($counts as $kk => $vv) {
|
->withCount('users')
|
||||||
if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count;
|
->get();
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->success($plans);
|
return $this->success($plans);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(PlanSave $request)
|
public function save(Request $request)
|
||||||
{
|
{
|
||||||
$params = $request->validated();
|
$params = $request->validate([
|
||||||
|
'id' => 'nullable|integer',
|
||||||
|
'name' => 'required|string',
|
||||||
|
'content' => 'nullable|string',
|
||||||
|
'reset_traffic_method' => 'integer|nullable',
|
||||||
|
'transfer_enable' => 'integer|required',
|
||||||
|
'prices' => 'array|nullable',
|
||||||
|
'group_id' => 'integer|nullable',
|
||||||
|
'speed_limit' => 'integer|nullable',
|
||||||
|
'device_limit' => 'integer|nullable',
|
||||||
|
'capacity_limit' => 'integer|nullable',
|
||||||
|
]);
|
||||||
if ($request->input('id')) {
|
if ($request->input('id')) {
|
||||||
$plan = Plan::find($request->input('id'));
|
$plan = Plan::find($request->input('id'));
|
||||||
if (!$plan) {
|
if (!$plan) {
|
||||||
return $this->fail([400202 ,'该订阅不存在']);
|
return $this->fail([400202, '该订阅不存在']);
|
||||||
}
|
}
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
// update user group id and transfer
|
// update user group id and transfer
|
||||||
@@ -44,7 +49,8 @@ class PlanController extends Controller
|
|||||||
User::where('plan_id', $plan->id)->update([
|
User::where('plan_id', $plan->id)->update([
|
||||||
'group_id' => $params['group_id'],
|
'group_id' => $params['group_id'],
|
||||||
'transfer_enable' => $params['transfer_enable'] * 1073741824,
|
'transfer_enable' => $params['transfer_enable'] * 1073741824,
|
||||||
'speed_limit' => $params['speed_limit']
|
'speed_limit' => $params['speed_limit'],
|
||||||
|
'device_limit' => $params['device_limit'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$plan->update($params);
|
$plan->update($params);
|
||||||
@@ -53,11 +59,11 @@ class PlanController extends Controller
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
\Log::error($e);
|
\Log::error($e);
|
||||||
return $this->fail([500 ,'保存失败']);
|
return $this->fail([500, '保存失败']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!Plan::create($params)) {
|
if (!Plan::create($params)) {
|
||||||
return $this->fail([500 ,'创建失败']);
|
return $this->fail([500, '创建失败']);
|
||||||
}
|
}
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
@@ -65,57 +71,61 @@ class PlanController extends Controller
|
|||||||
public function drop(Request $request)
|
public function drop(Request $request)
|
||||||
{
|
{
|
||||||
if (Order::where('plan_id', $request->input('id'))->first()) {
|
if (Order::where('plan_id', $request->input('id'))->first()) {
|
||||||
return $this->fail([400201 ,'该订阅下存在订单无法删除']);
|
return $this->fail([400201, '该订阅下存在订单无法删除']);
|
||||||
}
|
}
|
||||||
if (User::where('plan_id', $request->input('id'))->first()) {
|
if (User::where('plan_id', $request->input('id'))->first()) {
|
||||||
return $this->fail([400201 ,'该订阅下存在用户无法删除']);
|
return $this->fail([400201, '该订阅下存在用户无法删除']);
|
||||||
}
|
}
|
||||||
if ($request->input('id')) {
|
if ($request->input('id')) {
|
||||||
$plan = Plan::find($request->input('id'));
|
$plan = Plan::find($request->input('id'));
|
||||||
if (!$plan) {
|
if (!$plan) {
|
||||||
return $this->fail([400202 ,'该订阅不存在']);
|
return $this->fail([400202, '该订阅不存在']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $this->success($plan->delete());
|
return $this->success($plan->delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(PlanUpdate $request)
|
public function update(Request $request)
|
||||||
{
|
{
|
||||||
$updateData = $request->only([
|
$updateData = $request->only([
|
||||||
'show',
|
'show',
|
||||||
'renew'
|
'renew',
|
||||||
|
'sell'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$plan = Plan::find($request->input('id'));
|
$plan = Plan::find($request->input('id'));
|
||||||
if (!$plan) {
|
if (!$plan) {
|
||||||
return $this->fail([400202 ,'该订阅不存在']);
|
return $this->fail([400202, '该订阅不存在']);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$plan->update($updateData);
|
$plan->update($updateData);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error($e);
|
\Log::error($e);
|
||||||
return $this->fail([500 ,'保存失败']);
|
return $this->fail([500, '保存失败']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success();
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sort(PlanSort $request)
|
public function sort(Request $request)
|
||||||
{
|
{
|
||||||
|
$params = $request->validate([
|
||||||
try{
|
'ids' => 'required|array'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
foreach ($request->input('plan_ids') as $k => $v) {
|
foreach ($params['ids'] as $k => $v) {
|
||||||
if (!Plan::find($v)->update(['sort' => $k + 1])) {
|
if (!Plan::find($v)->update(['sort' => $k + 1])) {
|
||||||
throw new \Exception();
|
throw new \Exception();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DB::commit();
|
DB::commit();
|
||||||
}catch (\Exception $e){
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
\Log::error($e);
|
\Log::error($e);
|
||||||
return $this->fail([500 ,'保存失败']);
|
return $this->fail([500, '保存失败']);
|
||||||
}
|
}
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
195
app/Http/Controllers/V2/Admin/PluginController.php
Normal file
195
app/Http/Controllers/V2/Admin/PluginController.php
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use App\Services\Plugin\PluginManager;
|
||||||
|
use App\Services\Plugin\PluginConfigService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
class PluginController extends Controller
|
||||||
|
{
|
||||||
|
protected PluginManager $pluginManager;
|
||||||
|
protected PluginConfigService $configService;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
PluginManager $pluginManager,
|
||||||
|
PluginConfigService $configService
|
||||||
|
) {
|
||||||
|
$this->pluginManager = $pluginManager;
|
||||||
|
$this->configService = $configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件列表
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$installedPlugins = Plugin::get()
|
||||||
|
->keyBy('code')
|
||||||
|
->toArray();
|
||||||
|
$pluginPath = base_path('plugins');
|
||||||
|
$plugins = [];
|
||||||
|
|
||||||
|
if (File::exists($pluginPath)) {
|
||||||
|
$directories = File::directories($pluginPath);
|
||||||
|
foreach ($directories as $directory) {
|
||||||
|
$pluginName = basename($directory);
|
||||||
|
$configFile = $directory . '/config.json';
|
||||||
|
if (File::exists($configFile)) {
|
||||||
|
$config = json_decode(File::get($configFile), true);
|
||||||
|
$installed = isset($installedPlugins[$pluginName]);
|
||||||
|
// 使用配置服务获取配置
|
||||||
|
$pluginConfig = $installed ? $this->configService->getConfig($pluginName) : ($config['config'] ?? []);
|
||||||
|
$plugins[] = [
|
||||||
|
'code' => $config['code'],
|
||||||
|
'name' => $config['name'],
|
||||||
|
'version' => $config['version'],
|
||||||
|
'description' => $config['description'],
|
||||||
|
'author' => $config['author'],
|
||||||
|
'is_installed' => $installed,
|
||||||
|
'is_enabled' => $installed ? $installedPlugins[$pluginName]['is_enabled'] : false,
|
||||||
|
'config' => $pluginConfig,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'data' => $plugins
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安装插件
|
||||||
|
*/
|
||||||
|
public function install(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->install($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件安装成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件安装失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卸载插件
|
||||||
|
*/
|
||||||
|
public function uninstall(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->uninstall($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件卸载成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件卸载失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用插件
|
||||||
|
*/
|
||||||
|
public function enable(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->enable($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件启用成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件启用失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用插件
|
||||||
|
*/
|
||||||
|
public function disable(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->disable($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件禁用成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件禁用失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件配置
|
||||||
|
*/
|
||||||
|
public function getConfig(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config = $this->configService->getConfig($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'data' => $config
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '获取配置失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新插件配置
|
||||||
|
*/
|
||||||
|
public function updateConfig(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string',
|
||||||
|
'config' => 'required|array'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->configService->updateConfig(
|
||||||
|
$request->input('code'),
|
||||||
|
$request->input('config')
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => '配置更新成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '配置更新失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
app/Http/Controllers/V2/Admin/Server/GroupController.php
Normal file
66
app/Http/Controllers/V2/Admin/Server/GroupController.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin\Server;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Plan;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServerGroup;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
class GroupController extends Controller
|
||||||
|
{
|
||||||
|
public function fetch(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
|
||||||
|
$serverGroups = ServerGroup::query()
|
||||||
|
->orderByDesc('id')
|
||||||
|
->withCount('users')
|
||||||
|
->get()
|
||||||
|
->transform(function ($group) {
|
||||||
|
$group->server_count = $group->servers()->count();
|
||||||
|
return $group;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->success($serverGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(Request $request)
|
||||||
|
{
|
||||||
|
if (empty($request->input('name'))) {
|
||||||
|
return $this->fail([422, '组名不能为空']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->input('id')) {
|
||||||
|
$serverGroup = ServerGroup::find($request->input('id'));
|
||||||
|
} else {
|
||||||
|
$serverGroup = new ServerGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
$serverGroup->name = $request->input('name');
|
||||||
|
return $this->success($serverGroup->save());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function drop(Request $request)
|
||||||
|
{
|
||||||
|
$groupId = $request->input('id');
|
||||||
|
|
||||||
|
$serverGroup = ServerGroup::find($groupId);
|
||||||
|
if (!$serverGroup) {
|
||||||
|
return $this->fail([400202, '组不存在']);
|
||||||
|
}
|
||||||
|
if (Server::whereJsonContains('group_ids', $groupId)->exists()) {
|
||||||
|
return $this->fail([400, '该组已被节点所使用,无法删除']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Plan::where('group_id', $groupId)->exists()) {
|
||||||
|
return $this->fail([400, '该组已被订阅所使用,无法删除']);
|
||||||
|
}
|
||||||
|
if (User::where('group_id', $groupId)->exists()) {
|
||||||
|
return $this->fail([400, '该组已被用户所使用,无法删除']);
|
||||||
|
}
|
||||||
|
return $this->success($serverGroup->delete());
|
||||||
|
}
|
||||||
|
}
|
||||||
125
app/Http/Controllers/V2/Admin/Server/ManageController.php
Normal file
125
app/Http/Controllers/V2/Admin/Server/ManageController.php
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin\Server;
|
||||||
|
|
||||||
|
use App\Exceptions\ApiException;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Admin\ServerSave;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServerGroup;
|
||||||
|
use App\Services\ServerService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class ManageController extends Controller
|
||||||
|
{
|
||||||
|
public function getNodes(Request $request)
|
||||||
|
{
|
||||||
|
$servers = collect(ServerService::getAllServers())->map(function ($item) {
|
||||||
|
$item['groups'] = ServerGroup::whereIn('id', $item['group_ids'])->get(['name', 'id']);
|
||||||
|
$item['parent'] = $item->parent;
|
||||||
|
return $item;
|
||||||
|
});
|
||||||
|
return $this->success($servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sort(Request $request)
|
||||||
|
{
|
||||||
|
ini_set('post_max_size', '1m');
|
||||||
|
$params = $request->validate([
|
||||||
|
'*.id' => 'numeric',
|
||||||
|
'*.order' => 'numeric'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
collect($params)->each(function ($item) {
|
||||||
|
if (isset($item['id']) && isset($item['order'])) {
|
||||||
|
Server::where('id', $item['id'])->update(['sort' => $item['order']]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
DB::commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
\Log::error($e);
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(ServerSave $request)
|
||||||
|
{
|
||||||
|
$params = $request->validated();
|
||||||
|
if ($request->input('id')) {
|
||||||
|
$server = Server::find($request->input('id'));
|
||||||
|
if (!$server) {
|
||||||
|
return $this->fail([400202, '服务器不存在']);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$server->update($params);
|
||||||
|
return $this->success(true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error($e);
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Server::create($params);
|
||||||
|
return $this->success(true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error($e);
|
||||||
|
return $this->fail([500, '创建失败']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|integer',
|
||||||
|
'show' => 'integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (Server::where('id', $request->id)->update(['show' => $request->show]) === false) {
|
||||||
|
return $this->fail([500, '保存失败']);
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function drop(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|integer',
|
||||||
|
]);
|
||||||
|
if (Server::where('id', $request->id)->delete() === false) {
|
||||||
|
return $this->fail([500, '删除失败']);
|
||||||
|
}
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制节点
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function copy(Request $request)
|
||||||
|
{
|
||||||
|
$server = Server::find($request->input('id'));
|
||||||
|
$server->show = 0;
|
||||||
|
$server->code = null;
|
||||||
|
if (!$server) {
|
||||||
|
return $this->fail([400202, '服务器不存在']);
|
||||||
|
}
|
||||||
|
Server::create($server->toArray());
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin\Server;
|
namespace App\Http\Controllers\V2\Admin\Server;
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@@ -5,9 +5,7 @@ namespace App\Http\Controllers\V2\Admin;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\CommissionLog;
|
use App\Models\CommissionLog;
|
||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
use App\Models\ServerShadowsocks;
|
use App\Models\Server;
|
||||||
use App\Models\ServerTrojan;
|
|
||||||
use App\Models\ServerVmess;
|
|
||||||
use App\Models\Stat;
|
use App\Models\Stat;
|
||||||
use App\Models\StatServer;
|
use App\Models\StatServer;
|
||||||
use App\Models\StatUser;
|
use App\Models\StatUser;
|
||||||
@@ -15,78 +13,497 @@ use App\Models\Ticket;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\StatisticalService;
|
use App\Services\StatisticalService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class StatController extends Controller
|
class StatController extends Controller
|
||||||
{
|
{
|
||||||
public function override(Request $request)
|
private $service;
|
||||||
|
public function __construct(StatisticalService $service)
|
||||||
{
|
{
|
||||||
$params = $request->validate([
|
$this->service = $service;
|
||||||
'start_at' => '',
|
}
|
||||||
'end_at' => ''
|
public function getOverride(Request $request)
|
||||||
|
{
|
||||||
|
// 获取在线节点数
|
||||||
|
$onlineNodes = Server::all()->filter(function ($server) {
|
||||||
|
$server->loadServerStatus();
|
||||||
|
return $server->is_online;
|
||||||
|
})->count();
|
||||||
|
// 获取在线设备数和在线用户数
|
||||||
|
$onlineDevices = User::where('t', '>=', time() - 600)
|
||||||
|
->sum('online_count');
|
||||||
|
$onlineUsers = User::where('t', '>=', time() - 600)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// 获取今日流量统计
|
||||||
|
$todayStart = strtotime('today');
|
||||||
|
$todayTraffic = StatServer::where('record_at', '>=', $todayStart)
|
||||||
|
->where('record_at', '<', time())
|
||||||
|
->selectRaw('SUM(u) as upload, SUM(d) as download, SUM(u + d) as total')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
// 获取本月流量统计
|
||||||
|
$monthStart = strtotime(date('Y-m-1'));
|
||||||
|
$monthTraffic = StatServer::where('record_at', '>=', $monthStart)
|
||||||
|
->where('record_at', '<', time())
|
||||||
|
->selectRaw('SUM(u) as upload, SUM(d) as download, SUM(u + d) as total')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
// 获取总流量统计
|
||||||
|
$totalTraffic = StatServer::selectRaw('SUM(u) as upload, SUM(d) as download, SUM(u + d) as total')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'data' => [
|
||||||
|
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
|
||||||
|
->where('created_at', '<', time())
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->sum('total_amount'),
|
||||||
|
'month_register_total' => User::where('created_at', '>=', strtotime(date('Y-m-1')))
|
||||||
|
->where('created_at', '<', time())
|
||||||
|
->count(),
|
||||||
|
'ticket_pending_total' => Ticket::where('status', 0)
|
||||||
|
->count(),
|
||||||
|
'commission_pending_total' => Order::where('commission_status', 0)
|
||||||
|
->where('invite_user_id', '!=', NULL)
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->where('commission_balance', '>', 0)
|
||||||
|
->count(),
|
||||||
|
'day_income' => Order::where('created_at', '>=', strtotime(date('Y-m-d')))
|
||||||
|
->where('created_at', '<', time())
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->sum('total_amount'),
|
||||||
|
'last_month_income' => Order::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
|
||||||
|
->where('created_at', '<', strtotime(date('Y-m-1')))
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->sum('total_amount'),
|
||||||
|
'commission_month_payout' => CommissionLog::where('created_at', '>=', strtotime(date('Y-m-1')))
|
||||||
|
->where('created_at', '<', time())
|
||||||
|
->sum('get_amount'),
|
||||||
|
'commission_last_month_payout' => CommissionLog::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
|
||||||
|
->where('created_at', '<', strtotime(date('Y-m-1')))
|
||||||
|
->sum('get_amount'),
|
||||||
|
// 新增统计数据
|
||||||
|
'online_nodes' => $onlineNodes,
|
||||||
|
'online_devices' => $onlineDevices,
|
||||||
|
'online_users' => $onlineUsers,
|
||||||
|
'today_traffic' => [
|
||||||
|
'upload' => $todayTraffic->upload ?? 0,
|
||||||
|
'download' => $todayTraffic->download ?? 0,
|
||||||
|
'total' => $todayTraffic->total ?? 0
|
||||||
|
],
|
||||||
|
'month_traffic' => [
|
||||||
|
'upload' => $monthTraffic->upload ?? 0,
|
||||||
|
'download' => $monthTraffic->download ?? 0,
|
||||||
|
'total' => $monthTraffic->total ?? 0
|
||||||
|
],
|
||||||
|
'total_traffic' => [
|
||||||
|
'upload' => $totalTraffic->upload ?? 0,
|
||||||
|
'download' => $totalTraffic->download ?? 0,
|
||||||
|
'total' => $totalTraffic->total ?? 0
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get order statistics with filtering and pagination
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getOrder(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'start_date' => 'nullable|date_format:Y-m-d',
|
||||||
|
'end_date' => 'nullable|date_format:Y-m-d',
|
||||||
|
'type' => 'nullable|in:paid_total,paid_count,commission_total,commission_count',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (isset($params['start_at']) && isset($params['end_at'])) {
|
$query = Stat::where('record_type', 'd');
|
||||||
$stats = Stat::where('record_at', '>=', $params['start_at'])
|
|
||||||
->where('record_at', '<', $params['end_at'])
|
// Apply date filters
|
||||||
|
if ($request->input('start_date')) {
|
||||||
|
$query->where('record_at', '>=', strtotime($request->input('start_date')));
|
||||||
|
}
|
||||||
|
if ($request->input('end_date')) {
|
||||||
|
$query->where('record_at', '<=', strtotime($request->input('end_date') . ' 23:59:59'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$statistics = $query->orderBy('record_at', 'DESC')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$summary = [
|
||||||
|
'paid_total' => 0,
|
||||||
|
'paid_count' => 0,
|
||||||
|
'commission_total' => 0,
|
||||||
|
'commission_count' => 0,
|
||||||
|
'start_date' => $request->input('start_date', date('Y-m-d', $statistics->last()?->record_at)),
|
||||||
|
'end_date' => $request->input('end_date', date('Y-m-d', $statistics->first()?->record_at)),
|
||||||
|
'avg_paid_amount' => 0,
|
||||||
|
'avg_commission_amount' => 0
|
||||||
|
];
|
||||||
|
|
||||||
|
$dailyStats = [];
|
||||||
|
foreach ($statistics as $statistic) {
|
||||||
|
$date = date('Y-m-d', $statistic['record_at']);
|
||||||
|
|
||||||
|
// Update summary
|
||||||
|
$summary['paid_total'] += $statistic['paid_total'];
|
||||||
|
$summary['paid_count'] += $statistic['paid_count'];
|
||||||
|
$summary['commission_total'] += $statistic['commission_total'];
|
||||||
|
$summary['commission_count'] += $statistic['commission_count'];
|
||||||
|
|
||||||
|
// Calculate daily stats
|
||||||
|
$dailyData = [
|
||||||
|
'date' => $date,
|
||||||
|
'paid_total' => $statistic['paid_total'],
|
||||||
|
'paid_count' => $statistic['paid_count'],
|
||||||
|
'commission_total' => $statistic['commission_total'],
|
||||||
|
'commission_count' => $statistic['commission_count'],
|
||||||
|
'avg_order_amount' => $statistic['paid_count'] > 0 ? round($statistic['paid_total'] / $statistic['paid_count'], 2) : 0,
|
||||||
|
'avg_commission_amount' => $statistic['commission_count'] > 0 ? round($statistic['commission_total'] / $statistic['commission_count'], 2) : 0
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->input('type')) {
|
||||||
|
$dailyStats[] = [
|
||||||
|
'date' => $date,
|
||||||
|
'value' => $statistic[$request->input('type')],
|
||||||
|
'type' => $this->getTypeLabel($request->input('type'))
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$dailyStats[] = $dailyData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate averages for summary
|
||||||
|
if ($summary['paid_count'] > 0) {
|
||||||
|
$summary['avg_paid_amount'] = round($summary['paid_total'] / $summary['paid_count'], 2);
|
||||||
|
}
|
||||||
|
if ($summary['commission_count'] > 0) {
|
||||||
|
$summary['avg_commission_amount'] = round($summary['commission_total'] / $summary['commission_count'], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add percentage calculations to summary
|
||||||
|
$summary['commission_rate'] = $summary['paid_total'] > 0
|
||||||
|
? round(($summary['commission_total'] / $summary['paid_total']) * 100, 2)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => 0,
|
||||||
|
'message' => 'success',
|
||||||
|
'data' => [
|
||||||
|
'list' => array_reverse($dailyStats),
|
||||||
|
'summary' => $summary,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get human readable label for statistic type
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getTypeLabel(string $type): string
|
||||||
|
{
|
||||||
|
return match ($type) {
|
||||||
|
'paid_total' => '收款金额',
|
||||||
|
'paid_count' => '收款笔数',
|
||||||
|
'commission_total' => '佣金金额(已发放)',
|
||||||
|
'commission_count' => '佣金笔数(已发放)',
|
||||||
|
default => $type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当日实时流量排行
|
||||||
|
public function getServerLastRank()
|
||||||
|
{
|
||||||
|
$data = $this->service->getServerRank();
|
||||||
|
return $this->success(data: $data);
|
||||||
|
}
|
||||||
|
// 获取昨日节点流量排行
|
||||||
|
public function getServerYesterdayRank()
|
||||||
|
{
|
||||||
|
$data = $this->service->getServerRank('yesterday');
|
||||||
|
return $this->success($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatUser(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'user_id' => 'required|integer'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pageSize = $request->input('pageSize', 10);
|
||||||
|
$records = StatUser::orderBy('record_at', 'DESC')
|
||||||
|
->where('user_id', $request->input('user_id'))
|
||||||
|
->paginate($pageSize);
|
||||||
|
|
||||||
|
$data = $records->items();
|
||||||
|
return [
|
||||||
|
'data' => $data,
|
||||||
|
'total' => $records->total(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatRecord(Request $request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->service->getStatRecord($request->input('type'))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get comprehensive statistics data including income, users, and growth rates
|
||||||
|
*/
|
||||||
|
public function getStats()
|
||||||
|
{
|
||||||
|
$currentMonthStart = strtotime(date('Y-m-01'));
|
||||||
|
$lastMonthStart = strtotime('-1 month', $currentMonthStart);
|
||||||
|
$twoMonthsAgoStart = strtotime('-2 month', $currentMonthStart);
|
||||||
|
|
||||||
|
// Today's start timestamp
|
||||||
|
$todayStart = strtotime('today');
|
||||||
|
$yesterdayStart = strtotime('-1 day', $todayStart);
|
||||||
|
|
||||||
|
// 获取在线节点数
|
||||||
|
$onlineNodes = Server::all()->filter(function ($server) {
|
||||||
|
$server->loadServerStatus();
|
||||||
|
return $server->is_online;
|
||||||
|
})->count();
|
||||||
|
|
||||||
|
// 获取在线设备数和在线用户数
|
||||||
|
$onlineDevices = User::where('t', '>=', time() - 600)
|
||||||
|
->sum('online_count');
|
||||||
|
$onlineUsers = User::where('t', '>=', time() - 600)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// 获取今日流量统计
|
||||||
|
$todayTraffic = StatServer::where('record_at', '>=', $todayStart)
|
||||||
|
->where('record_at', '<', time())
|
||||||
|
->selectRaw('SUM(u) as upload, SUM(d) as download, SUM(u + d) as total')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
// 获取本月流量统计
|
||||||
|
$monthTraffic = StatServer::where('record_at', '>=', $currentMonthStart)
|
||||||
|
->where('record_at', '<', time())
|
||||||
|
->selectRaw('SUM(u) as upload, SUM(d) as download, SUM(u + d) as total')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
// 获取总流量统计
|
||||||
|
$totalTraffic = StatServer::selectRaw('SUM(u) as upload, SUM(d) as download, SUM(u + d) as total')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
// Today's income
|
||||||
|
$todayIncome = Order::where('created_at', '>=', $todayStart)
|
||||||
|
->where('created_at', '<', time())
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->sum('total_amount');
|
||||||
|
|
||||||
|
// Yesterday's income for day growth calculation
|
||||||
|
$yesterdayIncome = Order::where('created_at', '>=', $yesterdayStart)
|
||||||
|
->where('created_at', '<', $todayStart)
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->sum('total_amount');
|
||||||
|
|
||||||
|
// Current month income
|
||||||
|
$currentMonthIncome = Order::where('created_at', '>=', $currentMonthStart)
|
||||||
|
->where('created_at', '<', time())
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->sum('total_amount');
|
||||||
|
|
||||||
|
// Last month income
|
||||||
|
$lastMonthIncome = Order::where('created_at', '>=', $lastMonthStart)
|
||||||
|
->where('created_at', '<', $currentMonthStart)
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->sum('total_amount');
|
||||||
|
|
||||||
|
// Last month commission payout
|
||||||
|
$lastMonthCommissionPayout = CommissionLog::where('created_at', '>=', $lastMonthStart)
|
||||||
|
->where('created_at', '<', $currentMonthStart)
|
||||||
|
->sum('get_amount');
|
||||||
|
|
||||||
|
// Current month commission payout
|
||||||
|
$currentMonthCommissionPayout = CommissionLog::where('created_at', '>=', $currentMonthStart)
|
||||||
|
->where('created_at', '<', time())
|
||||||
|
->sum('get_amount');
|
||||||
|
|
||||||
|
// Current month new users
|
||||||
|
$currentMonthNewUsers = User::where('created_at', '>=', $currentMonthStart)
|
||||||
|
->where('created_at', '<', time())
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// Total users
|
||||||
|
$totalUsers = User::count();
|
||||||
|
|
||||||
|
// Active users (users with valid subscription)
|
||||||
|
$activeUsers = User::where(function ($query) {
|
||||||
|
$query->where('expired_at', '>=', time())
|
||||||
|
->orWhere('expired_at', NULL);
|
||||||
|
})->count();
|
||||||
|
|
||||||
|
// Previous month income for growth calculation
|
||||||
|
$twoMonthsAgoIncome = Order::where('created_at', '>=', $twoMonthsAgoStart)
|
||||||
|
->where('created_at', '<', $lastMonthStart)
|
||||||
|
->whereNotIn('status', [0, 2])
|
||||||
|
->sum('total_amount');
|
||||||
|
|
||||||
|
// Previous month commission for growth calculation
|
||||||
|
$twoMonthsAgoCommission = CommissionLog::where('created_at', '>=', $twoMonthsAgoStart)
|
||||||
|
->where('created_at', '<', $lastMonthStart)
|
||||||
|
->sum('get_amount');
|
||||||
|
|
||||||
|
// Previous month users for growth calculation
|
||||||
|
$lastMonthNewUsers = User::where('created_at', '>=', $lastMonthStart)
|
||||||
|
->where('created_at', '<', $currentMonthStart)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// Calculate growth rates
|
||||||
|
$monthIncomeGrowth = $lastMonthIncome > 0 ? round(($currentMonthIncome - $lastMonthIncome) / $lastMonthIncome * 100, 1) : 0;
|
||||||
|
$lastMonthIncomeGrowth = $twoMonthsAgoIncome > 0 ? round(($lastMonthIncome - $twoMonthsAgoIncome) / $twoMonthsAgoIncome * 100, 1) : 0;
|
||||||
|
$commissionGrowth = $twoMonthsAgoCommission > 0 ? round(($lastMonthCommissionPayout - $twoMonthsAgoCommission) / $twoMonthsAgoCommission * 100, 1) : 0;
|
||||||
|
$userGrowth = $lastMonthNewUsers > 0 ? round(($currentMonthNewUsers - $lastMonthNewUsers) / $lastMonthNewUsers * 100, 1) : 0;
|
||||||
|
$dayIncomeGrowth = $yesterdayIncome > 0 ? round(($todayIncome - $yesterdayIncome) / $yesterdayIncome * 100, 1) : 0;
|
||||||
|
|
||||||
|
// 获取待处理工单和佣金数据
|
||||||
|
$ticketPendingTotal = Ticket::where('status', 0)->count();
|
||||||
|
$commissionPendingTotal = Order::where('commission_status', 0)
|
||||||
|
->where('invite_user_id', '!=', NULL)
|
||||||
|
->whereIn('status', [Order::STATUS_COMPLETED])
|
||||||
|
->where('commission_balance', '>', 0)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'data' => [
|
||||||
|
// 收入相关
|
||||||
|
'todayIncome' => $todayIncome,
|
||||||
|
'dayIncomeGrowth' => $dayIncomeGrowth,
|
||||||
|
'currentMonthIncome' => $currentMonthIncome,
|
||||||
|
'lastMonthIncome' => $lastMonthIncome,
|
||||||
|
'monthIncomeGrowth' => $monthIncomeGrowth,
|
||||||
|
'lastMonthIncomeGrowth' => $lastMonthIncomeGrowth,
|
||||||
|
|
||||||
|
// 佣金相关
|
||||||
|
'currentMonthCommissionPayout' => $currentMonthCommissionPayout,
|
||||||
|
'lastMonthCommissionPayout' => $lastMonthCommissionPayout,
|
||||||
|
'commissionGrowth' => $commissionGrowth,
|
||||||
|
'commissionPendingTotal' => $commissionPendingTotal,
|
||||||
|
|
||||||
|
// 用户相关
|
||||||
|
'currentMonthNewUsers' => $currentMonthNewUsers,
|
||||||
|
'totalUsers' => $totalUsers,
|
||||||
|
'activeUsers' => $activeUsers,
|
||||||
|
'userGrowth' => $userGrowth,
|
||||||
|
'onlineUsers' => $onlineUsers,
|
||||||
|
'onlineDevices' => $onlineDevices,
|
||||||
|
|
||||||
|
// 工单相关
|
||||||
|
'ticketPendingTotal' => $ticketPendingTotal,
|
||||||
|
|
||||||
|
// 节点相关
|
||||||
|
'onlineNodes' => $onlineNodes,
|
||||||
|
|
||||||
|
// 流量统计
|
||||||
|
'todayTraffic' => [
|
||||||
|
'upload' => $todayTraffic->upload ?? 0,
|
||||||
|
'download' => $todayTraffic->download ?? 0,
|
||||||
|
'total' => $todayTraffic->total ?? 0
|
||||||
|
],
|
||||||
|
'monthTraffic' => [
|
||||||
|
'upload' => $monthTraffic->upload ?? 0,
|
||||||
|
'download' => $monthTraffic->download ?? 0,
|
||||||
|
'total' => $monthTraffic->total ?? 0
|
||||||
|
],
|
||||||
|
'totalTraffic' => [
|
||||||
|
'upload' => $totalTraffic->upload ?? 0,
|
||||||
|
'download' => $totalTraffic->download ?? 0,
|
||||||
|
'total' => $totalTraffic->total ?? 0
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get traffic ranking data for nodes or users
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getTrafficRank(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'type' => 'required|in:node,user',
|
||||||
|
'start_time' => 'nullable|integer|min:1000000000|max:9999999999',
|
||||||
|
'end_time' => 'nullable|integer|min:1000000000|max:9999999999'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$type = $request->input('type');
|
||||||
|
$startDate = $request->input('start_time', strtotime('-7 days'));
|
||||||
|
$endDate = $request->input('end_time', time());
|
||||||
|
$previousStartDate = $startDate - ($endDate - $startDate);
|
||||||
|
$previousEndDate = $startDate;
|
||||||
|
|
||||||
|
if ($type === 'node') {
|
||||||
|
// Get node traffic data
|
||||||
|
$currentData = StatServer::selectRaw('server_id as id, SUM(u + d) as value')
|
||||||
|
->where('record_at', '>=', $startDate)
|
||||||
|
->where('record_at', '<=', $endDate)
|
||||||
|
->groupBy('server_id')
|
||||||
|
->orderBy('value', 'DESC')
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// Get previous period data for comparison
|
||||||
|
$previousData = StatServer::selectRaw('server_id as id, SUM(u + d) as value')
|
||||||
|
->where('record_at', '>=', $previousStartDate)
|
||||||
|
->where('record_at', '<', $previousEndDate)
|
||||||
|
->whereIn('server_id', $currentData->pluck('id'))
|
||||||
|
->groupBy('server_id')
|
||||||
->get()
|
->get()
|
||||||
->makeHidden(['record_at', 'created_at', 'updated_at', 'id', 'record_type'])
|
->keyBy('id');
|
||||||
->toArray();
|
|
||||||
} else {
|
} else {
|
||||||
$statisticalService = new StatisticalService();
|
// Get user traffic data
|
||||||
return [
|
$currentData = StatUser::selectRaw('user_id as id, SUM(u + d) as value')
|
||||||
'data' => $statisticalService->generateStatData()
|
->where('record_at', '>=', $startDate)
|
||||||
|
->where('record_at', '<=', $endDate)
|
||||||
|
->groupBy('user_id')
|
||||||
|
->orderBy('value', 'DESC')
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// Get previous period data for comparison
|
||||||
|
$previousData = StatUser::selectRaw('user_id as id, SUM(u + d) as value')
|
||||||
|
->where('record_at', '>=', $previousStartDate)
|
||||||
|
->where('record_at', '<', $previousEndDate)
|
||||||
|
->whereIn('user_id', $currentData->pluck('id'))
|
||||||
|
->groupBy('user_id')
|
||||||
|
->get()
|
||||||
|
->keyBy('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($currentData as $data) {
|
||||||
|
$previousValue = isset($previousData[$data->id]) ? $previousData[$data->id]->value : 0;
|
||||||
|
$change = $previousValue > 0 ? round(($data->value - $previousValue) / $previousValue * 100, 1) : 0;
|
||||||
|
|
||||||
|
$name = $type === 'node'
|
||||||
|
? optional(Server::find($data->id))->name ?? "Node {$data->id}"
|
||||||
|
: optional(User::find($data->id))->email ?? "User {$data->id}";
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'id' => (string) $data->id,
|
||||||
|
'name' => $name,
|
||||||
|
'value' => $data->value, // Convert to GB
|
||||||
|
'previousValue' => $previousValue, // Convert to GB
|
||||||
|
'change' => $change,
|
||||||
|
'timestamp' => date('c', $endDate)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$stats = array_reduce($stats, function($carry, $item) {
|
|
||||||
foreach($item as $key => $value) {
|
|
||||||
if(isset($carry[$key]) && $carry[$key]) {
|
|
||||||
$carry[$key] += $value;
|
|
||||||
} else {
|
|
||||||
$carry[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $carry;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'data' => $stats
|
'timestamp' => date('c'),
|
||||||
];
|
'data' => $result
|
||||||
}
|
|
||||||
|
|
||||||
public function record(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'type' => 'required|in:paid_total,commission_total,register_count',
|
|
||||||
'start_at' => '',
|
|
||||||
'end_at' => ''
|
|
||||||
]);
|
|
||||||
|
|
||||||
$statisticalService = new StatisticalService();
|
|
||||||
$statisticalService->setStartAt($request->input('start_at'));
|
|
||||||
$statisticalService->setEndAt($request->input('end_at'));
|
|
||||||
return [
|
|
||||||
'data' => $statisticalService->getStatRecord($request->input('type'))
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ranking(Request $request)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'type' => 'required|in:server_traffic_rank,user_consumption_rank,invite_rank',
|
|
||||||
'start_at' => '',
|
|
||||||
'end_at' => '',
|
|
||||||
'limit' => 'nullable|integer'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$statisticalService = new StatisticalService();
|
|
||||||
$statisticalService->setStartAt($request->input('start_at'));
|
|
||||||
$statisticalService->setEndAt($request->input('end_at'));
|
|
||||||
return [
|
|
||||||
'data' => $statisticalService->getRanking($request->input('type'), $request->input('limit') ?? 20)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Log as LogModel;
|
use App\Models\Log as LogModel;
|
||||||
use App\Utils\CacheKey;
|
use App\Utils\CacheKey;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Laravel\Horizon\Contracts\JobRepository;
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
use Laravel\Horizon\Contracts\MasterSupervisorRepository;
|
use Laravel\Horizon\Contracts\MasterSupervisorRepository;
|
||||||
use Laravel\Horizon\Contracts\MetricsRepository;
|
use Laravel\Horizon\Contracts\MetricsRepository;
|
||||||
149
app/Http/Controllers/V2/Admin/ThemeController.php
Normal file
149
app/Http/Controllers/V2/Admin/ThemeController.php
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
|
use App\Exceptions\ApiException;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\ThemeService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
class ThemeController extends Controller
|
||||||
|
{
|
||||||
|
private $themeService;
|
||||||
|
|
||||||
|
public function __construct(ThemeService $themeService)
|
||||||
|
{
|
||||||
|
$this->themeService = $themeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传新主题
|
||||||
|
*
|
||||||
|
* @throws ApiException
|
||||||
|
*/
|
||||||
|
public function upload(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'file' => [
|
||||||
|
'required',
|
||||||
|
'file',
|
||||||
|
'mimes:zip',
|
||||||
|
'max:10240', // 最大10MB
|
||||||
|
]
|
||||||
|
], [
|
||||||
|
'file.required' => '请选择主题包文件',
|
||||||
|
'file.file' => '无效的文件类型',
|
||||||
|
'file.mimes' => '主题包必须是zip格式',
|
||||||
|
'file.max' => '主题包大小不能超过10MB'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查上传目录权限
|
||||||
|
$uploadPath = storage_path('tmp');
|
||||||
|
if (!File::exists($uploadPath)) {
|
||||||
|
File::makeDirectory($uploadPath, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_writable($uploadPath)) {
|
||||||
|
throw new ApiException('上传目录无写入权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查主题目录权限
|
||||||
|
$themePath = base_path('theme');
|
||||||
|
if (!is_writable($themePath)) {
|
||||||
|
throw new ApiException('主题目录无写入权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $request->file('file');
|
||||||
|
|
||||||
|
// 检查文件MIME类型
|
||||||
|
$mimeType = $file->getMimeType();
|
||||||
|
if (!in_array($mimeType, ['application/zip', 'application/x-zip-compressed'])) {
|
||||||
|
throw new ApiException('无效的文件类型,仅支持ZIP格式');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件名安全性
|
||||||
|
$originalName = $file->getClientOriginalName();
|
||||||
|
if (!preg_match('/^[a-zA-Z0-9\-\_\.]+\.zip$/', $originalName)) {
|
||||||
|
throw new ApiException('主题包文件名只能包含字母、数字、下划线、中划线和点');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->themeService->upload($file);
|
||||||
|
return $this->success(true);
|
||||||
|
|
||||||
|
} catch (ApiException $e) {
|
||||||
|
throw $e;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Theme upload failed', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'file' => $request->file('file')?->getClientOriginalName()
|
||||||
|
]);
|
||||||
|
throw new ApiException('主题上传失败:' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除主题
|
||||||
|
*/
|
||||||
|
public function delete(Request $request)
|
||||||
|
{
|
||||||
|
$payload = $request->validate([
|
||||||
|
'name' => 'required'
|
||||||
|
]);
|
||||||
|
$this->themeService->delete($payload['name']);
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有主题和其配置列
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function getThemes()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'themes' => $this->themeService->getList(),
|
||||||
|
'active' => admin_setting('frontend_theme', 'Xboard')
|
||||||
|
];
|
||||||
|
return $this->success($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换主题
|
||||||
|
*/
|
||||||
|
public function switchTheme(Request $request)
|
||||||
|
{
|
||||||
|
$payload = $request->validate([
|
||||||
|
'name' => 'required'
|
||||||
|
]);
|
||||||
|
$this->themeService->switch($payload['name']);
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主题配置
|
||||||
|
*/
|
||||||
|
public function getThemeConfig(Request $request)
|
||||||
|
{
|
||||||
|
$payload = $request->validate([
|
||||||
|
'name' => 'required'
|
||||||
|
]);
|
||||||
|
$data = $this->themeService->getConfig($payload['name']);
|
||||||
|
return $this->success($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存主题配置
|
||||||
|
*/
|
||||||
|
public function saveThemeConfig(Request $request)
|
||||||
|
{
|
||||||
|
$payload = $request->validate([
|
||||||
|
'name' => 'required',
|
||||||
|
'config' => 'required'
|
||||||
|
]);
|
||||||
|
$this->themeService->updateConfig($payload['name'], $payload['config']);
|
||||||
|
$config = $this->themeService->getConfig($payload['name']);
|
||||||
|
return $this->success($config);
|
||||||
|
}
|
||||||
|
}
|
||||||
132
app/Http/Controllers/V2/Admin/TicketController.php
Normal file
132
app/Http/Controllers/V2/Admin/TicketController.php
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Services\TicketService;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TicketController extends Controller
|
||||||
|
{
|
||||||
|
private function applyFiltersAndSorts(Request $request, $builder)
|
||||||
|
{
|
||||||
|
if ($request->has('filter')) {
|
||||||
|
collect($request->input('filter'))->each(function ($filter) use ($builder) {
|
||||||
|
$key = $filter['id'];
|
||||||
|
$value = $filter['value'];
|
||||||
|
$builder->where(function ($query) use ($key, $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$query->whereIn($key, $value);
|
||||||
|
} else {
|
||||||
|
$query->where($key, 'like', "%{$value}%");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('sort')) {
|
||||||
|
collect($request->input('sort'))->each(function ($sort) use ($builder) {
|
||||||
|
$key = $sort['id'];
|
||||||
|
$value = $sort['desc'] ? 'DESC' : 'ASC';
|
||||||
|
$builder->orderBy($key, $value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function fetch(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->input('id')) {
|
||||||
|
return $this->fetchTicketById($request);
|
||||||
|
} else {
|
||||||
|
return $this->fetchTickets($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary of fetchTicketById
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
private function fetchTicketById(Request $request)
|
||||||
|
{
|
||||||
|
$ticket = Ticket::with('messages', 'user')->find($request->input('id'));
|
||||||
|
|
||||||
|
if (!$ticket) {
|
||||||
|
return $this->fail([400202, '工单不存在']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ticket->messages->each(function ($message) use ($ticket) {
|
||||||
|
$message->is_me = $message->user_id !== $ticket->user_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->success($ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary of fetchTickets
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
private function fetchTickets(Request $request)
|
||||||
|
{
|
||||||
|
$ticketModel = Ticket::query()
|
||||||
|
->when($request->has('status'), function ($query) use ($request) {
|
||||||
|
$query->where('status', $request->input('status'));
|
||||||
|
})
|
||||||
|
->when($request->has('reply_status'), function ($query) use ($request) {
|
||||||
|
$query->whereIn('reply_status', $request->input('reply_status'));
|
||||||
|
})
|
||||||
|
->when($request->has('email'), function ($query) use ($request) {
|
||||||
|
$query->whereHas('user', function ($q) use ($request) {
|
||||||
|
$q->where('email', $request->input('email'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->applyFiltersAndSorts($request, $ticketModel);
|
||||||
|
|
||||||
|
return response()->json($ticketModel
|
||||||
|
->latest('updated_at')
|
||||||
|
->paginate(
|
||||||
|
perPage: $request->integer('pageSize', 10),
|
||||||
|
page: $request->integer('current', 1)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reply(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|numeric',
|
||||||
|
'message' => 'required|string'
|
||||||
|
], [
|
||||||
|
'id.required' => '工单ID不能为空',
|
||||||
|
'message.required' => '消息不能为空'
|
||||||
|
]);
|
||||||
|
$ticketService = new TicketService();
|
||||||
|
$ticketService->replyByAdmin(
|
||||||
|
$request->input('id'),
|
||||||
|
$request->input('message'),
|
||||||
|
$request->user()->id
|
||||||
|
);
|
||||||
|
return $this->success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|numeric'
|
||||||
|
], [
|
||||||
|
'id.required' => '工单ID不能为空'
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
$ticket = Ticket::findOrFail($request->input('id'));
|
||||||
|
$ticket->status = Ticket::STATUS_CLOSED;
|
||||||
|
$ticket->save();
|
||||||
|
return $this->success(true);
|
||||||
|
} catch (ModelNotFoundException $e) {
|
||||||
|
return $this->fail([400202, '工单不存在']);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->fail([500101, '关闭失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V1\Admin;
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Admin\UserFetch;
|
|
||||||
use App\Http\Requests\Admin\UserGenerate;
|
use App\Http\Requests\Admin\UserGenerate;
|
||||||
use App\Http\Requests\Admin\UserSendMail;
|
use App\Http\Requests\Admin\UserSendMail;
|
||||||
use App\Http\Requests\Admin\UserUpdate;
|
use App\Http\Requests\Admin\UserUpdate;
|
||||||
@@ -13,6 +11,7 @@ use App\Models\Plan;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\AuthService;
|
use App\Services\AuthService;
|
||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
@@ -21,71 +20,173 @@ class UserController extends Controller
|
|||||||
public function resetSecret(Request $request)
|
public function resetSecret(Request $request)
|
||||||
{
|
{
|
||||||
$user = User::find($request->input('id'));
|
$user = User::find($request->input('id'));
|
||||||
if (!$user) return $this->fail([400202,'用户不存在']);
|
if (!$user)
|
||||||
|
return $this->fail([400202, '用户不存在']);
|
||||||
$user->token = Helper::guid();
|
$user->token = Helper::guid();
|
||||||
$user->uuid = Helper::guid(true);
|
$user->uuid = Helper::guid(true);
|
||||||
return $this->success($user->save());
|
return $this->success($user->save());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function filter(Request $request, $builder)
|
/**
|
||||||
|
* Apply filters and sorts to the query builder
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Builder $builder
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function applyFiltersAndSorts(Request $request, Builder $builder): void
|
||||||
{
|
{
|
||||||
$filters = $request->input('filter');
|
$this->applyFilters($request, $builder);
|
||||||
if ($filters) {
|
$this->applySorting($request, $builder);
|
||||||
foreach ($filters as $k => $filter) {
|
|
||||||
if ($filter['condition'] === '模糊') {
|
|
||||||
$filter['condition'] = 'like';
|
|
||||||
$filter['value'] = "%{$filter['value']}%";
|
|
||||||
}
|
|
||||||
if ($filter['key'] === 'd' || $filter['key'] === 'transfer_enable') {
|
|
||||||
$filter['value'] = $filter['value'] * 1073741824;
|
|
||||||
}
|
|
||||||
if ($filter['key'] === 'invite_by_email') {
|
|
||||||
$user = User::where('email', $filter['condition'], $filter['value'])->first();
|
|
||||||
$inviteUserId = isset($user->id) ? $user->id : 0;
|
|
||||||
$builder->where('invite_user_id', $inviteUserId);
|
|
||||||
unset($filters[$k]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$builder->where($filter['key'], $filter['condition'], $filter['value']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fetch(UserFetch $request)
|
/**
|
||||||
|
* Apply filters to the query builder
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Builder $builder
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function applyFilters(Request $request, Builder $builder): void
|
||||||
{
|
{
|
||||||
$current = $request->input('current') ? $request->input('current') : 1;
|
if (!$request->has('filter')) {
|
||||||
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
|
return;
|
||||||
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
|
|
||||||
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
|
|
||||||
$userModel = User::select(
|
|
||||||
DB::raw('*'),
|
|
||||||
DB::raw('(u+d) as total_used')
|
|
||||||
)
|
|
||||||
->orderBy($sort, $sortType);
|
|
||||||
$this->filter($request, $userModel);
|
|
||||||
$total = $userModel->count();
|
|
||||||
$res = $userModel->forPage($current, $pageSize)
|
|
||||||
->get();
|
|
||||||
$plan = Plan::get();
|
|
||||||
for ($i = 0; $i < count($res); $i++) {
|
|
||||||
for ($k = 0; $k < count($plan); $k++) {
|
|
||||||
if ($plan[$k]['id'] == $res[$i]['plan_id']) {
|
|
||||||
$res[$i]['plan_name'] = $plan[$k]['name'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$res[$i]['subscribe_url'] = Helper::getSubscribeUrl( $res[$i]['token']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collect($request->input('filter'))->each(function ($filter) use ($builder) {
|
||||||
|
$field = $filter['id'];
|
||||||
|
$value = $filter['value'];
|
||||||
|
|
||||||
|
$builder->where(function ($query) use ($field, $value) {
|
||||||
|
$this->buildFilterQuery($query, $field, $value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the filter query based on field and value
|
||||||
|
*
|
||||||
|
* @param Builder $query
|
||||||
|
* @param string $field
|
||||||
|
* @param mixed $value
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function buildFilterQuery(Builder $query, string $field, mixed $value): void
|
||||||
|
{
|
||||||
|
// Handle array values for 'in' operations
|
||||||
|
if (is_array($value)) {
|
||||||
|
$query->whereIn($field === 'group_ids' ? 'group_id' : $field, $value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle operator-based filtering
|
||||||
|
if (!is_string($value) || !str_contains($value, ':')) {
|
||||||
|
$query->where($field, 'like', "%{$value}%");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$operator, $filterValue] = explode(':', $value, 2);
|
||||||
|
|
||||||
|
// Convert numeric strings to appropriate type
|
||||||
|
if (is_numeric($filterValue)) {
|
||||||
|
$filterValue = strpos($filterValue, '.') !== false
|
||||||
|
? (float) $filterValue
|
||||||
|
: (int) $filterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle computed fields
|
||||||
|
$queryField = match ($field) {
|
||||||
|
'total_used' => DB::raw('(u + d)'),
|
||||||
|
default => $field
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply operator
|
||||||
|
$query->where($queryField, match (strtolower($operator)) {
|
||||||
|
'eq' => '=',
|
||||||
|
'gt' => '>',
|
||||||
|
'gte' => '>=',
|
||||||
|
'lt' => '<',
|
||||||
|
'lte' => '<=',
|
||||||
|
'like' => 'like',
|
||||||
|
'notlike' => 'not like',
|
||||||
|
'null' => static fn($q) => $q->whereNull($queryField),
|
||||||
|
'notnull' => static fn($q) => $q->whereNotNull($queryField),
|
||||||
|
default => 'like'
|
||||||
|
}, match (strtolower($operator)) {
|
||||||
|
'like', 'notlike' => "%{$filterValue}%",
|
||||||
|
'null', 'notnull' => null,
|
||||||
|
default => $filterValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply sorting to the query builder
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Builder $builder
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function applySorting(Request $request, Builder $builder): void
|
||||||
|
{
|
||||||
|
if (!$request->has('sort')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
collect($request->input('sort'))->each(function ($sort) use ($builder) {
|
||||||
|
$field = $sort['id'];
|
||||||
|
$direction = $sort['desc'] ? 'DESC' : 'ASC';
|
||||||
|
$builder->orderBy($field, $direction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch paginated user list with filters and sorting
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function fetch(Request $request)
|
||||||
|
{
|
||||||
|
$current = $request->input('current', 1);
|
||||||
|
$pageSize = $request->input('pageSize', 10);
|
||||||
|
|
||||||
|
$userModel = User::with(['plan:id,name', 'invite_user:id,email', 'group:id,name'])
|
||||||
|
->select(DB::raw('*, (u+d) as total_used'));
|
||||||
|
|
||||||
|
$this->applyFiltersAndSorts($request, $userModel);
|
||||||
|
|
||||||
|
$users = $userModel->orderBy('id', 'desc')
|
||||||
|
->paginate($pageSize, ['*'], 'page', $current);
|
||||||
|
|
||||||
|
$users->getCollection()->transform(function ($user) {
|
||||||
|
return $this->transformUserData($user);
|
||||||
|
});
|
||||||
|
|
||||||
return response([
|
return response([
|
||||||
'data' => $res,
|
'data' => $users->items(),
|
||||||
'total' => $total
|
'total' => $users->total()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform user data for response
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
* @return User
|
||||||
|
*/
|
||||||
|
private function transformUserData(User $user): User
|
||||||
|
{
|
||||||
|
$user->subscribe_url = Helper::getSubscribeUrl($user->token);
|
||||||
|
$user->balance = $user->balance / 100;
|
||||||
|
$user->commission_balance = $user->commission_balance / 100;
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
public function getUserInfoById(Request $request)
|
public function getUserInfoById(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'id'=> 'required|numeric'
|
'id' => 'required|numeric'
|
||||||
],[
|
], [
|
||||||
'id.required' => '用户ID不能为空'
|
'id.required' => '用户ID不能为空'
|
||||||
]);
|
]);
|
||||||
$user = User::find($request->input('id'))->load('invite_user');
|
$user = User::find($request->input('id'))->load('invite_user');
|
||||||
@@ -117,6 +218,7 @@ class UserController extends Controller
|
|||||||
if (!$plan) {
|
if (!$plan) {
|
||||||
return $this->fail([400202, '订阅计划不存在']);
|
return $this->fail([400202, '订阅计划不存在']);
|
||||||
}
|
}
|
||||||
|
// return json_encode($plan);
|
||||||
$params['group_id'] = $plan->group_id;
|
$params['group_id'] = $plan->group_id;
|
||||||
}
|
}
|
||||||
// 处理邀请用户
|
// 处理邀请用户
|
||||||
@@ -126,16 +228,22 @@ class UserController extends Controller
|
|||||||
$params['invite_user_id'] = null;
|
$params['invite_user_id'] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($params['banned']) && (int)$params['banned'] === 1) {
|
if (isset($params['banned']) && (int) $params['banned'] === 1) {
|
||||||
$authService = new AuthService($user);
|
$authService = new AuthService($user);
|
||||||
$authService->removeAllSession();
|
$authService->removeSession();
|
||||||
|
}
|
||||||
|
if (isset($params['balance'])) {
|
||||||
|
$params['balance'] = $params['balance'] * 100;
|
||||||
|
}
|
||||||
|
if (isset($params['commission_balance'])) {
|
||||||
|
$params['commission_balance'] = $params['commission_balance'] * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$user->update($params);
|
$user->update($params);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error($e);
|
\Log::error($e);
|
||||||
return $this->fail([500,'保存失败']);
|
return $this->fail([500, '保存失败']);
|
||||||
}
|
}
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
@@ -155,14 +263,14 @@ class UserController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$data = "邮箱,余额,推广佣金,总流量,剩余流量,套餐到期时间,订阅计划,订阅地址\r\n";
|
$data = "邮箱,余额,推广佣金,总流量,剩余流量,套餐到期时间,订阅计划,订阅地址\r\n";
|
||||||
foreach($res as $user) {
|
foreach ($res as $user) {
|
||||||
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
||||||
$balance = $user['balance'] / 100;
|
$balance = $user['balance'] / 100;
|
||||||
$commissionBalance = $user['commission_balance'] / 100;
|
$commissionBalance = $user['commission_balance'] / 100;
|
||||||
$transferEnable = $user['transfer_enable'] ? $user['transfer_enable'] / 1073741824 : 0;
|
$transferEnable = $user['transfer_enable'] ? $user['transfer_enable'] / 1073741824 : 0;
|
||||||
$notUseFlow = (($user['transfer_enable'] - ($user['u'] + $user['d'])) / 1073741824) ?? 0;
|
$notUseFlow = (($user['transfer_enable'] - ($user['u'] + $user['d'])) / 1073741824) ?? 0;
|
||||||
$planName = $user['plan_name'] ?? '无订阅';
|
$planName = $user['plan_name'] ?? '无订阅';
|
||||||
$subscribeUrl = Helper::getSubscribeUrl($user['token']);
|
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
|
||||||
$data .= "{$user['email']},{$balance},{$commissionBalance},{$transferEnable},{$notUseFlow},{$expireDate},{$planName},{$subscribeUrl}\r\n";
|
$data .= "{$user['email']},{$balance},{$commissionBalance},{$transferEnable},{$notUseFlow},{$expireDate},{$planName},{$subscribeUrl}\r\n";
|
||||||
}
|
}
|
||||||
echo "\xEF\xBB\xBF" . $data;
|
echo "\xEF\xBB\xBF" . $data;
|
||||||
@@ -174,7 +282,7 @@ class UserController extends Controller
|
|||||||
if ($request->input('plan_id')) {
|
if ($request->input('plan_id')) {
|
||||||
$plan = Plan::find($request->input('plan_id'));
|
$plan = Plan::find($request->input('plan_id'));
|
||||||
if (!$plan) {
|
if (!$plan) {
|
||||||
return $this->fail([400202,'订阅计划不存在']);
|
return $this->fail([400202, '订阅计划不存在']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$user = [
|
$user = [
|
||||||
@@ -187,11 +295,11 @@ class UserController extends Controller
|
|||||||
'token' => Helper::guid()
|
'token' => Helper::guid()
|
||||||
];
|
];
|
||||||
if (User::where('email', $user['email'])->first()) {
|
if (User::where('email', $user['email'])->first()) {
|
||||||
return $this->fail([400201,'邮箱已存在于系统中']);
|
return $this->fail([400201, '邮箱已存在于系统中']);
|
||||||
}
|
}
|
||||||
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
|
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
|
||||||
if (!User::create($user)) {
|
if (!User::create($user)) {
|
||||||
return $this->fail([500,'生成失败']);
|
return $this->fail([500, '生成失败']);
|
||||||
}
|
}
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
@@ -205,11 +313,11 @@ class UserController extends Controller
|
|||||||
if ($request->input('plan_id')) {
|
if ($request->input('plan_id')) {
|
||||||
$plan = Plan::find($request->input('plan_id'));
|
$plan = Plan::find($request->input('plan_id'));
|
||||||
if (!$plan) {
|
if (!$plan) {
|
||||||
return $this->fail([400202,'订阅计划不存在']);
|
return $this->fail([400202, '订阅计划不存在']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$users = [];
|
$users = [];
|
||||||
for ($i = 0;$i < $request->input('generate_count');$i++) {
|
for ($i = 0; $i < $request->input('generate_count'); $i++) {
|
||||||
$user = [
|
$user = [
|
||||||
'email' => Helper::randomChar(6) . '@' . $request->input('email_suffix'),
|
'email' => Helper::randomChar(6) . '@' . $request->input('email_suffix'),
|
||||||
'plan_id' => isset($plan->id) ? $plan->id : NULL,
|
'plan_id' => isset($plan->id) ? $plan->id : NULL,
|
||||||
@@ -224,23 +332,23 @@ class UserController extends Controller
|
|||||||
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
|
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
|
||||||
array_push($users, $user);
|
array_push($users, $user);
|
||||||
}
|
}
|
||||||
try{
|
try {
|
||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
if (!User::insert($users)) {
|
if (!User::insert($users)) {
|
||||||
throw new \Exception();
|
throw new \Exception();
|
||||||
}
|
}
|
||||||
DB::commit();
|
DB::commit();
|
||||||
}catch(\Exception $e){
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
\Log::error($e);
|
\Log::error($e);
|
||||||
return $this->fail([500,'生成失败']);
|
return $this->fail([500, '生成失败']);
|
||||||
}
|
}
|
||||||
$data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n";
|
$data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n";
|
||||||
foreach($users as $user) {
|
foreach ($users as $user) {
|
||||||
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
||||||
$createDate = date('Y-m-d H:i:s', $user['created_at']);
|
$createDate = date('Y-m-d H:i:s', $user['created_at']);
|
||||||
$password = $request->input('password') ?? $user['email'];
|
$password = $request->input('password') ?? $user['email'];
|
||||||
$subscribeUrl = Helper::getSubscribeUrl($user['token']);
|
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
|
||||||
$data .= "{$user['email']},{$password},{$expireDate},{$user['uuid']},{$createDate},{$subscribeUrl}\r\n";
|
$data .= "{$user['email']},{$password},{$expireDate},{$user['uuid']},{$createDate},{$subscribeUrl}\r\n";
|
||||||
}
|
}
|
||||||
echo $data;
|
echo $data;
|
||||||
@@ -254,17 +362,19 @@ class UserController extends Controller
|
|||||||
$this->filter($request, $builder);
|
$this->filter($request, $builder);
|
||||||
$users = $builder->get();
|
$users = $builder->get();
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
SendEmailJob::dispatch([
|
SendEmailJob::dispatch(
|
||||||
'email' => $user->email,
|
[
|
||||||
'subject' => $request->input('subject'),
|
'email' => $user->email,
|
||||||
'template_name' => 'notify',
|
'subject' => $request->input('subject'),
|
||||||
'template_value' => [
|
'template_name' => 'notify',
|
||||||
'name' => admin_setting('app_name', 'XBoard'),
|
'template_value' => [
|
||||||
'url' => admin_setting('app_url'),
|
'name' => admin_setting('app_name', 'XBoard'),
|
||||||
'content' => $request->input('content')
|
'url' => admin_setting('app_url'),
|
||||||
]
|
'content' => $request->input('content')
|
||||||
],
|
]
|
||||||
'send_email_mass');
|
],
|
||||||
|
'send_email_mass'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
@@ -282,7 +392,7 @@ class UserController extends Controller
|
|||||||
]);
|
]);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error($e);
|
\Log::error($e);
|
||||||
return $this->fail([500,'处理失败']);
|
return $this->fail([500, '处理失败']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
@@ -29,7 +29,7 @@ class Kernel extends HttpKernel
|
|||||||
*/
|
*/
|
||||||
protected $middlewareGroups = [
|
protected $middlewareGroups = [
|
||||||
'web' => [
|
'web' => [
|
||||||
// \App\Http\Middleware\EncryptCookies::class,
|
// \App\Http\Middleware\EncryptCookies::class,
|
||||||
// \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
// \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
// \Illuminate\Session\Middleware\StartSession::class,
|
// \Illuminate\Session\Middleware\StartSession::class,
|
||||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||||
@@ -39,9 +39,12 @@ class Kernel extends HttpKernel
|
|||||||
],
|
],
|
||||||
|
|
||||||
'api' => [
|
'api' => [
|
||||||
// \App\Http\Middleware\EncryptCookies::class,
|
// \App\Http\Middleware\EncryptCookies::class,
|
||||||
// \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
// \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
// \Illuminate\Session\Middleware\StartSession::class,
|
// \Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||||
|
// \Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
||||||
|
// \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
\App\Http\Middleware\ForceJson::class,
|
\App\Http\Middleware\ForceJson::class,
|
||||||
\App\Http\Middleware\Language::class,
|
\App\Http\Middleware\Language::class,
|
||||||
'bindings',
|
'bindings',
|
||||||
@@ -56,6 +59,7 @@ class Kernel extends HttpKernel
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $middlewareAliases = [
|
protected $middlewareAliases = [
|
||||||
|
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||||
@@ -70,6 +74,8 @@ class Kernel extends HttpKernel
|
|||||||
'staff' => \App\Http\Middleware\Staff::class,
|
'staff' => \App\Http\Middleware\Staff::class,
|
||||||
'log' => \App\Http\Middleware\RequestLog::class,
|
'log' => \App\Http\Middleware\RequestLog::class,
|
||||||
'server' => \App\Http\Middleware\Server::class,
|
'server' => \App\Http\Middleware\Server::class,
|
||||||
|
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
||||||
|
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Services\AuthService;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
|
|
||||||
class Admin
|
class Admin
|
||||||
{
|
{
|
||||||
@@ -18,14 +17,15 @@ class Admin
|
|||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
$authorization = $request->input('auth_data') ?? $request->header('authorization');
|
if (!Auth::guard('sanctum')->check()) {
|
||||||
if (!$authorization) throw new ApiException('未登录或登陆已过期', 403);
|
throw new ApiException('未登录或登陆已过期', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = Auth::guard('sanctum')->user();
|
||||||
|
if (!$user->is_admin) {
|
||||||
|
throw new ApiException('无管理员权限', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$user = AuthService::decryptAuthData($authorization);
|
|
||||||
if (!$user || !$user['is_admin']) throw new ApiException('未登录或登陆已过期',403);
|
|
||||||
$request->merge([
|
|
||||||
'user' => $user
|
|
||||||
]);
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
app/Http/Middleware/Authenticate.php
Normal file
17
app/Http/Middleware/Authenticate.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class Authenticate extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the path the user should be redirected to when they are not authenticated.
|
||||||
|
*/
|
||||||
|
protected function redirectTo(Request $request): ?string
|
||||||
|
{
|
||||||
|
return $request->expectsJson() ? null : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Utils\CacheKey;
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class Client
|
class Client
|
||||||
{
|
{
|
||||||
@@ -19,7 +18,7 @@ class Client
|
|||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
$token = $request->input('token');
|
$token = $request->input('token', $request->route('token'));
|
||||||
if (empty($token)) {
|
if (empty($token)) {
|
||||||
throw new ApiException('token is null',403);
|
throw new ApiException('token is null',403);
|
||||||
}
|
}
|
||||||
@@ -27,9 +26,8 @@ class Client
|
|||||||
if (!$user) {
|
if (!$user) {
|
||||||
throw new ApiException('token is error',403);
|
throw new ApiException('token is error',403);
|
||||||
}
|
}
|
||||||
$request->merge([
|
|
||||||
'user' => $user
|
Auth::setUser($user);
|
||||||
]);
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,55 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
|
use App\Models\Server as ServerModel;
|
||||||
use App\Services\ServerService;
|
use App\Services\ServerService;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class Server
|
class Server
|
||||||
{
|
{
|
||||||
/**
|
public function handle(Request $request, Closure $next, ?string $nodeType = null)
|
||||||
* Handle an incoming request.
|
{
|
||||||
*
|
$this->validateRequest($request);
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
$serverInfo = ServerService::getServer(
|
||||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
$request->input('node_id'),
|
||||||
*/
|
$request->input('node_type') ?? $nodeType
|
||||||
public function handle(Request $request, Closure $next, $node_type = null)
|
);
|
||||||
|
if (!$serverInfo) {
|
||||||
|
throw new ApiException('Server does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->merge(['node_info' => $serverInfo]);
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateRequest(Request $request): void
|
||||||
{
|
{
|
||||||
// alias
|
|
||||||
$aliasTypes = [
|
|
||||||
'v2ray' => 'vmess',
|
|
||||||
'hysteria2' => 'hysteria'
|
|
||||||
];
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'token' => [
|
'token' => [
|
||||||
"string",
|
'string',
|
||||||
"required",
|
'required',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
if ($value !== admin_setting('server_token')) {
|
if ($value !== admin_setting('server_token')) {
|
||||||
$fail('The ' . $attribute . ' is invalid.');
|
$fail("Invalid {$attribute}");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'node_id' => 'required',
|
'node_id' => 'required',
|
||||||
'node_type' => [
|
'node_type' => [
|
||||||
'required',
|
|
||||||
'nullable',
|
'nullable',
|
||||||
'regex:/^(?i)(hysteria|hysteria2|vless|trojan|vmess|v2ray|tuic|shadowsocks|shadowsocks-plugin)$/',
|
function ($attribute, $value, $fail) use ($request) {
|
||||||
function ($attribute, $value, $fail) use ($aliasTypes, $request) {
|
if (!ServerModel::isValidType($value)) {
|
||||||
$request->merge([$attribute => strtolower(isset($aliasTypes[$value]) ? $aliasTypes[$value] : $value)]);
|
$fail("Invalid node type specified");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$request->merge([$attribute => ServerModel::normalizeType($value)]);
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
], [
|
|
||||||
'node_type.regex' => 'node_type is error!'
|
|
||||||
]);
|
]);
|
||||||
$nodeInfo = ServerService::getServer($request->input('node_id'), $request->input('node_type') ?? $node_type);
|
|
||||||
if (!$nodeInfo)
|
|
||||||
throw new ApiException('server is not exist!');
|
|
||||||
$request->merge(['node_info' => $nodeInfo]);
|
|
||||||
return $next($request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Http\Middleware;
|
|||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Services\AuthService;
|
use App\Services\AuthService;
|
||||||
|
use Auth;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
@@ -18,14 +19,9 @@ class User
|
|||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
$authorization = $request->input('auth_data') ?? $request->header('authorization');
|
if (!Auth::guard('sanctum')->check()) {
|
||||||
if (!$authorization) throw new ApiException( '未登录或登陆已过期', 403);
|
throw new ApiException('未登录或登陆已过期', 403);
|
||||||
|
}
|
||||||
$user = AuthService::decryptAuthData($authorization);
|
|
||||||
if (!$user) throw new ApiException('未登录或登陆已过期', 403);
|
|
||||||
$request->merge([
|
|
||||||
'user' => $user
|
|
||||||
]);
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,46 +8,48 @@ class ConfigSave extends FormRequest
|
|||||||
{
|
{
|
||||||
const RULES = [
|
const RULES = [
|
||||||
// invite & commission
|
// invite & commission
|
||||||
'invite_force' => 'in:0,1',
|
'invite_force' => '',
|
||||||
'invite_commission' => 'integer',
|
'invite_commission' => 'integer|nullable',
|
||||||
'invite_gen_limit' => 'integer',
|
'invite_gen_limit' => 'integer|nullable',
|
||||||
'invite_never_expire' => 'in:0,1',
|
'invite_never_expire' => '',
|
||||||
'commission_first_time_enable' => 'in:0,1',
|
'commission_first_time_enable' => '',
|
||||||
'commission_auto_check_enable' => 'in:0,1',
|
'commission_auto_check_enable' => '',
|
||||||
'commission_withdraw_limit' => 'nullable|numeric',
|
'commission_withdraw_limit' => 'nullable|numeric',
|
||||||
'commission_withdraw_method' => 'nullable|array',
|
'commission_withdraw_method' => 'nullable|array',
|
||||||
'withdraw_close_enable' => 'in:0,1',
|
'withdraw_close_enable' => '',
|
||||||
'commission_distribution_enable' => 'in:0,1',
|
'commission_distribution_enable' => '',
|
||||||
'commission_distribution_l1' => 'nullable|numeric',
|
'commission_distribution_l1' => 'nullable|numeric',
|
||||||
'commission_distribution_l2' => 'nullable|numeric',
|
'commission_distribution_l2' => 'nullable|numeric',
|
||||||
'commission_distribution_l3' => 'nullable|numeric',
|
'commission_distribution_l3' => 'nullable|numeric',
|
||||||
// site
|
// site
|
||||||
'logo' => 'nullable|url',
|
'logo' => 'nullable|url',
|
||||||
'force_https' => 'in:0,1',
|
'force_https' => '',
|
||||||
'stop_register' => 'in:0,1',
|
'stop_register' => '',
|
||||||
'app_name' => '',
|
'app_name' => '',
|
||||||
'app_description' => '',
|
'app_description' => '',
|
||||||
'app_url' => 'nullable|url',
|
'app_url' => 'nullable|url',
|
||||||
'subscribe_url' => 'nullable',
|
'subscribe_url' => 'nullable',
|
||||||
'try_out_enable' => 'in:0,1',
|
'try_out_enable' => '',
|
||||||
'try_out_plan_id' => 'integer',
|
'try_out_plan_id' => 'integer',
|
||||||
'try_out_hour' => 'numeric',
|
'try_out_hour' => 'numeric',
|
||||||
'tos_url' => 'nullable|url',
|
'tos_url' => 'nullable|url',
|
||||||
'currency' => '',
|
'currency' => '',
|
||||||
'currency_symbol' => '',
|
'currency_symbol' => '',
|
||||||
// subscribe
|
// subscribe
|
||||||
'plan_change_enable' => 'in:0,1',
|
'plan_change_enable' => '',
|
||||||
'reset_traffic_method' => 'in:0,1,2,3,4',
|
'reset_traffic_method' => 'in:0,1,2,3,4',
|
||||||
'surplus_enable' => 'in:0,1',
|
'surplus_enable' => '',
|
||||||
'new_order_event_id' => 'in:0,1',
|
'new_order_event_id' => '',
|
||||||
'renew_order_event_id' => 'in:0,1',
|
'renew_order_event_id' => '',
|
||||||
'change_order_event_id' => 'in:0,1',
|
'change_order_event_id' => '',
|
||||||
'show_info_to_server_enable' => 'in:0,1',
|
'show_info_to_server_enable' => '',
|
||||||
'show_protocol_to_server_enable' => 'in:0,1',
|
'show_protocol_to_server_enable' => '',
|
||||||
|
'subscribe_path' => '',
|
||||||
// server
|
// server
|
||||||
'server_token' => 'nullable|min:16',
|
'server_token' => 'nullable|min:16',
|
||||||
'server_pull_interval' => 'integer',
|
'server_pull_interval' => 'integer',
|
||||||
'server_push_interval' => 'integer',
|
'server_push_interval' => 'integer',
|
||||||
|
'device_limit_mode' => 'integer',
|
||||||
// frontend
|
// frontend
|
||||||
'frontend_theme' => '',
|
'frontend_theme' => '',
|
||||||
'frontend_theme_sidebar' => 'nullable|in:dark,light',
|
'frontend_theme_sidebar' => 'nullable|in:dark,light',
|
||||||
@@ -62,8 +64,9 @@ class ConfigSave extends FormRequest
|
|||||||
'email_password' => '',
|
'email_password' => '',
|
||||||
'email_encryption' => '',
|
'email_encryption' => '',
|
||||||
'email_from_address' => '',
|
'email_from_address' => '',
|
||||||
|
'remind_mail_enable' => '',
|
||||||
// telegram
|
// telegram
|
||||||
'telegram_bot_enable' => 'in:0,1',
|
'telegram_bot_enable' => '',
|
||||||
'telegram_bot_token' => '',
|
'telegram_bot_token' => '',
|
||||||
'telegram_discuss_id' => '',
|
'telegram_discuss_id' => '',
|
||||||
'telegram_channel_id' => '',
|
'telegram_channel_id' => '',
|
||||||
@@ -76,23 +79,23 @@ class ConfigSave extends FormRequest
|
|||||||
'android_version' => '',
|
'android_version' => '',
|
||||||
'android_download_url' => '',
|
'android_download_url' => '',
|
||||||
// safe
|
// safe
|
||||||
'email_whitelist_enable' => 'in:0,1',
|
'email_whitelist_enable' => 'boolean',
|
||||||
'email_whitelist_suffix' => 'nullable|array',
|
'email_whitelist_suffix' => 'nullable|array',
|
||||||
'email_gmail_limit_enable' => 'in:0,1',
|
'email_gmail_limit_enable' => 'boolean',
|
||||||
'recaptcha_enable' => 'in:0,1',
|
'recaptcha_enable' => 'boolean',
|
||||||
'recaptcha_key' => '',
|
'recaptcha_key' => '',
|
||||||
'recaptcha_site_key' => '',
|
'recaptcha_site_key' => '',
|
||||||
'email_verify' => 'in:0,1',
|
'email_verify' => 'bool',
|
||||||
'safe_mode_enable' => 'in:0,1',
|
'safe_mode_enable' => 'boolean',
|
||||||
'register_limit_by_ip_enable' => 'in:0,1',
|
'register_limit_by_ip_enable' => 'boolean',
|
||||||
'register_limit_count' => 'integer',
|
'register_limit_count' => 'integer',
|
||||||
'register_limit_expire' => 'integer',
|
'register_limit_expire' => 'integer',
|
||||||
'secure_path' => 'min:8|regex:/^[\w-]*$/',
|
'secure_path' => 'min:8|regex:/^[\w-]*$/',
|
||||||
'password_limit_enable' => 'in:0,1',
|
'password_limit_enable' => 'boolean',
|
||||||
'password_limit_count' => 'integer',
|
'password_limit_count' => 'integer',
|
||||||
'password_limit_expire' => 'integer',
|
'password_limit_expire' => 'integer',
|
||||||
'default_remind_expire' => 'integer',
|
'default_remind_expire' => 'boolean',
|
||||||
'default_remind_traffic' => 'integer'
|
'default_remind_traffic' => 'boolean'
|
||||||
];
|
];
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
|
|||||||
115
app/Http/Requests/Admin/ServerSave.php
Normal file
115
app/Http/Requests/Admin/ServerSave.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ServerSave extends FormRequest
|
||||||
|
{
|
||||||
|
private const PROTOCOL_RULES = [
|
||||||
|
'shadowsocks' => [
|
||||||
|
'cipher' => 'required|string',
|
||||||
|
'obfs' => 'nullable|string',
|
||||||
|
'obfs_settings.path' => 'nullable|string',
|
||||||
|
'obfs_settings.host' => 'nullable|string',
|
||||||
|
],
|
||||||
|
'vmess' => [
|
||||||
|
'tls' => 'required|integer',
|
||||||
|
'network' => 'required|string',
|
||||||
|
'network_settings' => 'nullable|array',
|
||||||
|
'tls_settings.server_name' => 'nullable|string',
|
||||||
|
'tls_settings.allow_insecure' => 'nullable|boolean',
|
||||||
|
],
|
||||||
|
'trojan' => [
|
||||||
|
'network' => 'required|string',
|
||||||
|
'network_settings' => 'nullable|array',
|
||||||
|
'server_name' => 'nullable|string',
|
||||||
|
'allow_insecure' => 'nullable|boolean',
|
||||||
|
],
|
||||||
|
'hysteria' => [
|
||||||
|
'version' => 'required|integer',
|
||||||
|
'alpn' => 'nullable|string',
|
||||||
|
'obfs.open' => 'nullable|boolean',
|
||||||
|
'obfs.type' => 'string|nullable',
|
||||||
|
'obfs.password' => 'string|nullable',
|
||||||
|
'tls.server_name' => 'nullable|string',
|
||||||
|
'tls.allow_insecure' => 'nullable|boolean',
|
||||||
|
'bandwidth.up' => 'nullable|integer',
|
||||||
|
'bandwidth.down' => 'nullable|integer',
|
||||||
|
],
|
||||||
|
'vless' => [
|
||||||
|
'tls' => 'required|integer',
|
||||||
|
'network' => 'required|string',
|
||||||
|
'network_settings' => 'nullable|array',
|
||||||
|
'flow' => 'nullable|string',
|
||||||
|
'tls_settings.server_name' => 'nullable|string',
|
||||||
|
'tls_settings.allow_insecure' => 'nullable|boolean',
|
||||||
|
'reality_settings.allow_insecure' => 'nullable|boolean',
|
||||||
|
'reality_settings.server_name' => 'nullable|string',
|
||||||
|
'reality_settings.server_port' => 'nullable|integer',
|
||||||
|
'reality_settings.public_key' => 'nullable|string',
|
||||||
|
'reality_settings.private_key' => 'nullable|string',
|
||||||
|
'reality_settings.short_id' => 'nullable|string',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
private function getBaseRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'required|in:' . implode(',', Server::VALID_TYPES),
|
||||||
|
'spectific_key' => 'nullable|string',
|
||||||
|
'code' => 'nullable|string',
|
||||||
|
'show' => '',
|
||||||
|
'name' => 'required|string',
|
||||||
|
'group_ids' => 'nullable|array',
|
||||||
|
'route_ids' => 'nullable|array',
|
||||||
|
'parent_id' => 'nullable|integer',
|
||||||
|
'host' => 'required',
|
||||||
|
'port' => 'required',
|
||||||
|
'server_port' => 'required',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'excludes' => 'nullable|array',
|
||||||
|
'ips' => 'nullable|array',
|
||||||
|
'rate' => 'required|numeric',
|
||||||
|
'protocol_settings' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$type = $this->input('type');
|
||||||
|
$rules = $this->getBaseRules();
|
||||||
|
|
||||||
|
foreach (self::PROTOCOL_RULES[$type] ?? [] as $field => $rule) {
|
||||||
|
$rules['protocol_settings.' . $field] = $rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name.required' => '节点名称不能为空',
|
||||||
|
'group_ids.required' => '权限组不能为空',
|
||||||
|
'group_ids.array' => '权限组格式不正确',
|
||||||
|
'route_ids.array' => '路由组格式不正确',
|
||||||
|
'parent_id.integer' => '父ID格式不正确',
|
||||||
|
'host.required' => '节点地址不能为空',
|
||||||
|
'port.required' => '连接端口不能为空',
|
||||||
|
'server_port.required' => '后端服务端口不能为空',
|
||||||
|
'tls.required' => 'TLS不能为空',
|
||||||
|
'tags.array' => '标签格式不正确',
|
||||||
|
'rate.required' => '倍率不能为空',
|
||||||
|
'rate.numeric' => '倍率格式不正确',
|
||||||
|
'network.required' => '传输协议不能为空',
|
||||||
|
'network.in' => '传输协议格式不正确',
|
||||||
|
'networkSettings.array' => '传输协议配置有误',
|
||||||
|
'ruleSettings.array' => '规则配置有误',
|
||||||
|
'tlsSettings.array' => 'tls配置有误',
|
||||||
|
'dnsSettings.array' => 'dns配置有误'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
|
|
||||||
class ServerShadowsocksSave extends FormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules that apply to the request.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function rules()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show' => '',
|
|
||||||
'name' => 'required',
|
|
||||||
'group_id' => 'required|array',
|
|
||||||
'parent_id' => 'nullable|integer',
|
|
||||||
'route_id' => 'nullable|array',
|
|
||||||
'host' => 'required',
|
|
||||||
'port' => 'required',
|
|
||||||
'server_port' => 'required',
|
|
||||||
'cipher' => 'required|in:aes-128-gcm,aes-192-gcm,aes-256-gcm,chacha20-ietf-poly1305,2022-blake3-aes-128-gcm,2022-blake3-aes-256-gcm',
|
|
||||||
'obfs' => 'nullable|in:http',
|
|
||||||
'obfs_settings' => 'nullable|array',
|
|
||||||
'tags' => 'nullable|array',
|
|
||||||
'excludes' => 'nullable|array',
|
|
||||||
'ips' => 'nullable|array',
|
|
||||||
'rate' => 'required|numeric'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function messages()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name.required' => '节点名称不能为空',
|
|
||||||
'group_id.required' => '权限组不能为空',
|
|
||||||
'group_id.array' => '权限组格式不正确',
|
|
||||||
'route_id.array' => '路由组格式不正确',
|
|
||||||
'parent_id.integer' => '父节点格式不正确',
|
|
||||||
'host.required' => '节点地址不能为空',
|
|
||||||
'port.required' => '连接端口不能为空',
|
|
||||||
'server_port.required' => '后端服务端口不能为空',
|
|
||||||
'cipher.required' => '加密方式不能为空',
|
|
||||||
'tags.array' => '标签格式不正确',
|
|
||||||
'rate.required' => '倍率不能为空',
|
|
||||||
'rate.numeric' => '倍率格式不正确',
|
|
||||||
'obfs.in' => '混淆格式不正确',
|
|
||||||
'obfs_settings.array' => '混淆设置格式不正确'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
|
|
||||||
class ServerShadowsocksUpdate extends FormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules that apply to the request.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function rules()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show' => 'in:0,1'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function messages()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show.in' => '显示状态格式不正确'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
|
|
||||||
class ServerTrojanSave extends FormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules that apply to the request.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function rules()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show' => '',
|
|
||||||
'name' => 'required',
|
|
||||||
'network' => 'required',
|
|
||||||
'networkSettings' => 'nullable',
|
|
||||||
'group_id' => 'required|array',
|
|
||||||
'route_id' => 'nullable|array',
|
|
||||||
'parent_id' => 'nullable|integer',
|
|
||||||
'host' => 'required',
|
|
||||||
'port' => 'required',
|
|
||||||
'server_port' => 'required',
|
|
||||||
'allow_insecure' => 'nullable|in:0,1',
|
|
||||||
'server_name' => 'nullable',
|
|
||||||
'tags' => 'nullable|array',
|
|
||||||
'excludes' => 'nullable|array',
|
|
||||||
'ips' => 'nullable|array',
|
|
||||||
'rate' => 'required|numeric'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function messages()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name.required' => '节点名称不能为空',
|
|
||||||
'network.required' => '传输协议不能为空',
|
|
||||||
'group_id.required' => '权限组不能为空',
|
|
||||||
'group_id.array' => '权限组格式不正确',
|
|
||||||
'route_id.array' => '路由组格式不正确',
|
|
||||||
'parent_id.integer' => '父节点格式不正确',
|
|
||||||
'host.required' => '节点地址不能为空',
|
|
||||||
'port.required' => '连接端口不能为空',
|
|
||||||
'server_port.required' => '后端服务端口不能为空',
|
|
||||||
'allow_insecure.in' => '允许不安全格式不正确',
|
|
||||||
'tags.array' => '标签格式不正确',
|
|
||||||
'rate.required' => '倍率不能为空',
|
|
||||||
'rate.numeric' => '倍率格式不正确'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
|
|
||||||
class ServerTrojanUpdate extends FormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules that apply to the request.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function rules()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show' => 'in:0,1'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function messages()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show.in' => '显示状态格式不正确'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
|
|
||||||
class ServerVmessSave extends FormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules that apply to the request.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function rules()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show' => '',
|
|
||||||
'name' => 'required',
|
|
||||||
'group_id' => 'required|array',
|
|
||||||
'route_id' => 'nullable|array',
|
|
||||||
'parent_id' => 'nullable|integer',
|
|
||||||
'host' => 'required',
|
|
||||||
'port' => 'required',
|
|
||||||
'server_port' => 'required',
|
|
||||||
'tls' => 'required',
|
|
||||||
'tags' => 'nullable|array',
|
|
||||||
'excludes' => 'nullable|array',
|
|
||||||
'ips' => 'nullable|array',
|
|
||||||
'rate' => 'required|numeric',
|
|
||||||
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic,grpc',
|
|
||||||
'networkSettings' => 'nullable|array',
|
|
||||||
'ruleSettings' => 'nullable|array',
|
|
||||||
'tlsSettings' => 'nullable|array',
|
|
||||||
'dnsSettings' => 'nullable|array'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function messages()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name.required' => '节点名称不能为空',
|
|
||||||
'group_id.required' => '权限组不能为空',
|
|
||||||
'group_id.array' => '权限组格式不正确',
|
|
||||||
'route_id.array' => '路由组格式不正确',
|
|
||||||
'parent_id.integer' => '父ID格式不正确',
|
|
||||||
'host.required' => '节点地址不能为空',
|
|
||||||
'port.required' => '连接端口不能为空',
|
|
||||||
'server_port.required' => '后端服务端口不能为空',
|
|
||||||
'tls.required' => 'TLS不能为空',
|
|
||||||
'tags.array' => '标签格式不正确',
|
|
||||||
'rate.required' => '倍率不能为空',
|
|
||||||
'rate.numeric' => '倍率格式不正确',
|
|
||||||
'network.required' => '传输协议不能为空',
|
|
||||||
'network.in' => '传输协议格式不正确',
|
|
||||||
'networkSettings.array' => '传输协议配置有误',
|
|
||||||
'ruleSettings.array' => '规则配置有误',
|
|
||||||
'tlsSettings.array' => 'tls配置有误',
|
|
||||||
'dnsSettings.array' => 'dns配置有误'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
|
|
||||||
class ServerVmessUpdate extends FormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules that apply to the request.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function rules()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show' => 'in:0,1'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function messages()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'show.in' => '显示状态格式不正确'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,7 @@ class UserUpdate extends FormRequest
|
|||||||
'password' => 'nullable|min:8',
|
'password' => 'nullable|min:8',
|
||||||
'transfer_enable' => 'numeric',
|
'transfer_enable' => 'numeric',
|
||||||
'expired_at' => 'nullable|integer',
|
'expired_at' => 'nullable|integer',
|
||||||
'banned' => 'required|in:0,1',
|
'banned' => 'in:0,1',
|
||||||
'plan_id' => 'nullable|integer',
|
'plan_id' => 'nullable|integer',
|
||||||
'commission_rate' => 'nullable|integer|min:0|max:100',
|
'commission_rate' => 'nullable|integer|min:0|max:100',
|
||||||
'discount' => 'nullable|integer|min:0|max:100',
|
'discount' => 'nullable|integer|min:0|max:100',
|
||||||
@@ -26,11 +26,12 @@ class UserUpdate extends FormRequest
|
|||||||
'is_staff' => 'required|in:0,1',
|
'is_staff' => 'required|in:0,1',
|
||||||
'u' => 'integer',
|
'u' => 'integer',
|
||||||
'd' => 'integer',
|
'd' => 'integer',
|
||||||
'balance' => 'integer',
|
'balance' => 'numeric',
|
||||||
'commission_type' => 'integer',
|
'commission_type' => 'integer',
|
||||||
'commission_balance' => 'integer',
|
'commission_balance' => 'numeric',
|
||||||
'remarks' => 'nullable',
|
'remarks' => 'nullable',
|
||||||
'speed_limit' => 'nullable|integer'
|
'speed_limit' => 'nullable|integer',
|
||||||
|
'device_limit' => 'nullable|integer'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +42,6 @@ class UserUpdate extends FormRequest
|
|||||||
'email.email' => '邮箱格式不正确',
|
'email.email' => '邮箱格式不正确',
|
||||||
'transfer_enable.numeric' => '流量格式不正确',
|
'transfer_enable.numeric' => '流量格式不正确',
|
||||||
'expired_at.integer' => '到期时间格式不正确',
|
'expired_at.integer' => '到期时间格式不正确',
|
||||||
'banned.required' => '是否封禁不能为空',
|
|
||||||
'banned.in' => '是否封禁格式不正确',
|
'banned.in' => '是否封禁格式不正确',
|
||||||
'is_admin.required' => '是否管理员不能为空',
|
'is_admin.required' => '是否管理员不能为空',
|
||||||
'is_admin.in' => '是否管理员格式不正确',
|
'is_admin.in' => '是否管理员格式不正确',
|
||||||
@@ -61,7 +61,8 @@ class UserUpdate extends FormRequest
|
|||||||
'balance.integer' => '余额格式不正确',
|
'balance.integer' => '余额格式不正确',
|
||||||
'commission_balance.integer' => '佣金格式不正确',
|
'commission_balance.integer' => '佣金格式不正确',
|
||||||
'password.min' => '密码长度最小8位',
|
'password.min' => '密码长度最小8位',
|
||||||
'speed_limit.integer' => '限速格式不正确'
|
'speed_limit.integer' => '限速格式不正确',
|
||||||
|
'device_limit.integer' => '设备数量格式不正确'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
app/Http/Resources/OrderResource.php
Normal file
24
app/Http/Resources/OrderResource.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use App\Services\PlanService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class OrderResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
...parent::toArray($request),
|
||||||
|
'period' => PlanService::getLegacyPeriod($this->period),
|
||||||
|
'plan' => PlanResource::make($this->plan),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
75
app/Http/Resources/PlanResource.php
Normal file
75
app/Http/Resources/PlanResource.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use App\Models\Plan;
|
||||||
|
use App\Services\PlanService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class PlanResource extends JsonResource
|
||||||
|
{
|
||||||
|
private const PRICE_MULTIPLIER = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->resource['id'],
|
||||||
|
'group_id' => $this->resource['group_id'],
|
||||||
|
'name' => $this->resource['name'],
|
||||||
|
'content' => $this->resource['content'],
|
||||||
|
...$this->getPeriodPrices(),
|
||||||
|
'capacity_limit' => $this->getFormattedCapacityLimit(),
|
||||||
|
'transfer_enable' => $this->resource['transfer_enable'],
|
||||||
|
'speed_limit' => $this->resource['speed_limit'],
|
||||||
|
'show' => (bool) $this->resource['show'],
|
||||||
|
'sell' => (bool) $this->resource['sell'],
|
||||||
|
'renew' => (bool) $this->resource['renew'],
|
||||||
|
'reset_traffic_method' => $this->resource['reset_traffic_method'],
|
||||||
|
'sort' => $this->resource['sort'],
|
||||||
|
'created_at' => $this->resource['created_at'],
|
||||||
|
'updated_at' => $this->resource['updated_at']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transformed period prices using Plan mapping
|
||||||
|
*
|
||||||
|
* @return array<string, float|null>
|
||||||
|
*/
|
||||||
|
protected function getPeriodPrices(): array
|
||||||
|
{
|
||||||
|
return collect(Plan::LEGACY_PERIOD_MAPPING)
|
||||||
|
->mapWithKeys(function (string $newPeriod, string $legacyPeriod): array {
|
||||||
|
$price = $this->resource['prices'][$newPeriod] ?? null;
|
||||||
|
return [
|
||||||
|
$legacyPeriod => $price !== null
|
||||||
|
? (float) $price * self::PRICE_MULTIPLIER
|
||||||
|
: null
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get formatted capacity limit value
|
||||||
|
*
|
||||||
|
* @return int|string|null
|
||||||
|
*/
|
||||||
|
protected function getFormattedCapacityLimit(): int|string|null
|
||||||
|
{
|
||||||
|
$limit = $this->resource['capacity_limit'];
|
||||||
|
|
||||||
|
return match (true) {
|
||||||
|
$limit === null => null,
|
||||||
|
$limit <= 0 => __('Sold out'),
|
||||||
|
default => (int) $limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace App\Http\Routes\V1;
|
|
||||||
|
|
||||||
use Illuminate\Contracts\Routing\Registrar;
|
|
||||||
|
|
||||||
class AdminRoute
|
|
||||||
{
|
|
||||||
public function map(Registrar $router)
|
|
||||||
{
|
|
||||||
$router->group([
|
|
||||||
'prefix' => admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))),
|
|
||||||
'middleware' => ['admin', 'log'],
|
|
||||||
], function ($router) {
|
|
||||||
// Config
|
|
||||||
$router->get ('/config/fetch', 'V1\\Admin\\ConfigController@fetch');
|
|
||||||
$router->post('/config/save', 'V1\\Admin\\ConfigController@save');
|
|
||||||
$router->get ('/config/getEmailTemplate', 'V1\\Admin\\ConfigController@getEmailTemplate');
|
|
||||||
$router->get ('/config/getThemeTemplate', 'V1\\Admin\\ConfigController@getThemeTemplate');
|
|
||||||
$router->post('/config/setTelegramWebhook', 'V1\\Admin\\ConfigController@setTelegramWebhook');
|
|
||||||
$router->post('/config/testSendMail', 'V1\\Admin\\ConfigController@testSendMail');
|
|
||||||
// Plan
|
|
||||||
$router->get ('/plan/fetch', 'V1\\Admin\\PlanController@fetch');
|
|
||||||
$router->post('/plan/save', 'V1\\Admin\\PlanController@save');
|
|
||||||
$router->post('/plan/drop', 'V1\\Admin\\PlanController@drop');
|
|
||||||
$router->post('/plan/update', 'V1\\Admin\\PlanController@update');
|
|
||||||
$router->post('/plan/sort', 'V1\\Admin\\PlanController@sort');
|
|
||||||
// Server
|
|
||||||
$router->get ('/server/group/fetch', 'V1\\Admin\\Server\\GroupController@fetch');
|
|
||||||
$router->post('/server/group/save', 'V1\\Admin\\Server\\GroupController@save');
|
|
||||||
$router->post('/server/group/drop', 'V1\\Admin\\Server\\GroupController@drop');
|
|
||||||
$router->get ('/server/route/fetch', 'V1\\Admin\\Server\\RouteController@fetch');
|
|
||||||
$router->post('/server/route/save', 'V1\\Admin\\Server\\RouteController@save');
|
|
||||||
$router->post('/server/route/drop', 'V1\\Admin\\Server\\RouteController@drop');
|
|
||||||
$router->get ('/server/manage/getNodes', 'V1\\Admin\\Server\\ManageController@getNodes');
|
|
||||||
$router->post('/server/manage/sort', 'V1\\Admin\\Server\\ManageController@sort');
|
|
||||||
$router->group([
|
|
||||||
'prefix' => 'server/trojan'
|
|
||||||
], function ($router) {
|
|
||||||
$router->post('save', 'V1\\Admin\\Server\\TrojanController@save');
|
|
||||||
$router->post('drop', 'V1\\Admin\\Server\\TrojanController@drop');
|
|
||||||
$router->post('update', 'V1\\Admin\\Server\\TrojanController@update');
|
|
||||||
$router->post('copy', 'V1\\Admin\\Server\\TrojanController@copy');
|
|
||||||
});
|
|
||||||
$router->group([
|
|
||||||
'prefix' => 'server/vmess'
|
|
||||||
], function ($router) {
|
|
||||||
$router->post('save', 'V1\\Admin\\Server\\VmessController@save');
|
|
||||||
$router->post('drop', 'V1\\Admin\\Server\\VmessController@drop');
|
|
||||||
$router->post('update', 'V1\\Admin\\Server\\VmessController@update');
|
|
||||||
$router->post('copy', 'V1\\Admin\\Server\\VmessController@copy');
|
|
||||||
});
|
|
||||||
$router->group([
|
|
||||||
'prefix' => 'server/shadowsocks'
|
|
||||||
], function ($router) {
|
|
||||||
$router->post('save', 'V1\\Admin\\Server\\ShadowsocksController@save');
|
|
||||||
$router->post('drop', 'V1\\Admin\\Server\\ShadowsocksController@drop');
|
|
||||||
$router->post('update', 'V1\\Admin\\Server\\ShadowsocksController@update');
|
|
||||||
$router->post('copy', 'V1\\Admin\\Server\\ShadowsocksController@copy');
|
|
||||||
});
|
|
||||||
$router->group([
|
|
||||||
'prefix' => 'server/hysteria'
|
|
||||||
], function ($router) {
|
|
||||||
$router->post('save', 'V1\\Admin\\Server\\HysteriaController@save');
|
|
||||||
$router->post('drop', 'V1\\Admin\\Server\\HysteriaController@drop');
|
|
||||||
$router->post('update', 'V1\\Admin\\Server\\HysteriaController@update');
|
|
||||||
$router->post('copy', 'V1\\Admin\\Server\\HysteriaController@copy');
|
|
||||||
});
|
|
||||||
$router->group([
|
|
||||||
'prefix' => 'server/vless'
|
|
||||||
], function ($router) {
|
|
||||||
$router->post('save', 'V1\\Admin\\Server\\VlessController@save');
|
|
||||||
$router->post('drop', 'V1\\Admin\\Server\\VlessController@drop');
|
|
||||||
$router->post('update', 'V1\\Admin\\Server\\VlessController@update');
|
|
||||||
$router->post('copy', 'V1\\Admin\\Server\\VlessController@copy');
|
|
||||||
});
|
|
||||||
// Order
|
|
||||||
$router->get ('/order/fetch', 'V1\\Admin\\OrderController@fetch');
|
|
||||||
$router->post('/order/update', 'V1\\Admin\\OrderController@update');
|
|
||||||
$router->post('/order/assign', 'V1\\Admin\\OrderController@assign');
|
|
||||||
$router->post('/order/paid', 'V1\\Admin\\OrderController@paid');
|
|
||||||
$router->post('/order/cancel', 'V1\\Admin\\OrderController@cancel');
|
|
||||||
$router->post('/order/detail', 'V1\\Admin\\OrderController@detail');
|
|
||||||
// User
|
|
||||||
$router->get ('/user/fetch', 'V1\\Admin\\UserController@fetch');
|
|
||||||
$router->post('/user/update', 'V1\\Admin\\UserController@update');
|
|
||||||
$router->get ('/user/getUserInfoById', 'V1\\Admin\\UserController@getUserInfoById');
|
|
||||||
$router->post('/user/generate', 'V1\\Admin\\UserController@generate');
|
|
||||||
$router->post('/user/dumpCSV', 'V1\\Admin\\UserController@dumpCSV');
|
|
||||||
$router->post('/user/sendMail', 'V1\\Admin\\UserController@sendMail');
|
|
||||||
$router->post('/user/ban', 'V1\\Admin\\UserController@ban');
|
|
||||||
$router->post('/user/resetSecret', 'V1\\Admin\\UserController@resetSecret');
|
|
||||||
$router->post('/user/setInviteUser', 'V1\\Admin\\UserController@setInviteUser');
|
|
||||||
// Stat
|
|
||||||
$router->get ('/stat/getStat', 'V1\\Admin\\StatController@getStat');
|
|
||||||
$router->get ('/stat/getOverride', 'V1\\Admin\\StatController@getOverride');
|
|
||||||
$router->get ('/stat/getServerLastRank', 'V1\\Admin\\StatController@getServerLastRank');
|
|
||||||
$router->get ('/stat/getServerYesterdayRank', 'V1\\Admin\\StatController@getServerYesterdayRank');
|
|
||||||
$router->get ('/stat/getOrder', 'V1\\Admin\\StatController@getOrder');
|
|
||||||
$router->get ('/stat/getStatUser', 'V1\\Admin\\StatController@getStatUser');
|
|
||||||
$router->get ('/stat/getRanking', 'V1\\Admin\\StatController@getRanking');
|
|
||||||
$router->get ('/stat/getStatRecord', 'V1\\Admin\\StatController@getStatRecord');
|
|
||||||
// Notice
|
|
||||||
$router->get ('/notice/fetch', 'V1\\Admin\\NoticeController@fetch');
|
|
||||||
$router->post('/notice/save', 'V1\\Admin\\NoticeController@save');
|
|
||||||
$router->post('/notice/update', 'V1\\Admin\\NoticeController@update');
|
|
||||||
$router->post('/notice/drop', 'V1\\Admin\\NoticeController@drop');
|
|
||||||
$router->post('/notice/show', 'V1\\Admin\\NoticeController@show');
|
|
||||||
// Ticket
|
|
||||||
$router->get ('/ticket/fetch', 'V1\\Admin\\TicketController@fetch');
|
|
||||||
$router->post('/ticket/reply', 'V1\\Admin\\TicketController@reply');
|
|
||||||
$router->post('/ticket/close', 'V1\\Admin\\TicketController@close');
|
|
||||||
// Coupon
|
|
||||||
$router->get ('/coupon/fetch', 'V1\\Admin\\CouponController@fetch');
|
|
||||||
$router->post('/coupon/generate', 'V1\\Admin\\CouponController@generate');
|
|
||||||
$router->post('/coupon/drop', 'V1\\Admin\\CouponController@drop');
|
|
||||||
$router->post('/coupon/show', 'V1\\Admin\\CouponController@show');
|
|
||||||
// Knowledge
|
|
||||||
$router->get ('/knowledge/fetch', 'V1\\Admin\\KnowledgeController@fetch');
|
|
||||||
$router->get ('/knowledge/getCategory', 'V1\\Admin\\KnowledgeController@getCategory');
|
|
||||||
$router->post('/knowledge/save', 'V1\\Admin\\KnowledgeController@save');
|
|
||||||
$router->post('/knowledge/show', 'V1\\Admin\\KnowledgeController@show');
|
|
||||||
$router->post('/knowledge/drop', 'V1\\Admin\\KnowledgeController@drop');
|
|
||||||
$router->post('/knowledge/sort', 'V1\\Admin\\KnowledgeController@sort');
|
|
||||||
// Payment
|
|
||||||
$router->get ('/payment/fetch', 'V1\\Admin\\PaymentController@fetch');
|
|
||||||
$router->get ('/payment/getPaymentMethods', 'V1\\Admin\\PaymentController@getPaymentMethods');
|
|
||||||
$router->post('/payment/getPaymentForm', 'V1\\Admin\\PaymentController@getPaymentForm');
|
|
||||||
$router->post('/payment/save', 'V1\\Admin\\PaymentController@save');
|
|
||||||
$router->post('/payment/drop', 'V1\\Admin\\PaymentController@drop');
|
|
||||||
$router->post('/payment/show', 'V1\\Admin\\PaymentController@show');
|
|
||||||
$router->post('/payment/sort', 'V1\\Admin\\PaymentController@sort');
|
|
||||||
// System
|
|
||||||
$router->get ('/system/getSystemStatus', 'V1\\Admin\\SystemController@getSystemStatus');
|
|
||||||
$router->get ('/system/getQueueStats', 'V1\\Admin\\SystemController@getQueueStats');
|
|
||||||
$router->get ('/system/getQueueWorkload', 'V1\\Admin\\SystemController@getQueueWorkload');
|
|
||||||
$router->get ('/system/getQueueMasters', '\\Laravel\\Horizon\\Http\\Controllers\\MasterSupervisorController@index');
|
|
||||||
$router->get ('/system/getSystemLog', 'V1\\Admin\\SystemController@getSystemLog');
|
|
||||||
// Theme
|
|
||||||
$router->get ('/theme/getThemes', 'V1\\Admin\\ThemeController@getThemes');
|
|
||||||
$router->post('/theme/saveThemeConfig', 'V1\\Admin\\ThemeController@saveThemeConfig');
|
|
||||||
$router->post('/theme/getThemeConfig', 'V1\\Admin\\ThemeController@getThemeConfig');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,7 @@ class ClientRoute
|
|||||||
'middleware' => 'client'
|
'middleware' => 'client'
|
||||||
], function ($router) {
|
], function ($router) {
|
||||||
// Client
|
// Client
|
||||||
$router->get('/subscribe', 'V1\\Client\\ClientController@subscribe')->name('client.subscribe');
|
$router->get('/subscribe', 'V1\\Client\\ClientController@subscribe')->name('client.subscribe.legacy');
|
||||||
// App
|
// App
|
||||||
$router->get('/app/getConfig', 'V1\\Client\\AppController@getConfig');
|
$router->get('/app/getConfig', 'V1\\Client\\AppController@getConfig');
|
||||||
$router->get('/app/getVersion', 'V1\\Client\\AppController@getVersion');
|
$router->get('/app/getVersion', 'V1\\Client\\AppController@getVersion');
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\Http\Routes\V1;
|
namespace App\Http\Routes\V1;
|
||||||
|
|
||||||
|
use App\Http\Controllers\V1\Guest\CommController;
|
||||||
|
use App\Http\Controllers\V1\Guest\PaymentController;
|
||||||
|
use App\Http\Controllers\V1\Guest\PlanController;
|
||||||
|
use App\Http\Controllers\V1\Guest\TelegramController;
|
||||||
use Illuminate\Contracts\Routing\Registrar;
|
use Illuminate\Contracts\Routing\Registrar;
|
||||||
|
|
||||||
class GuestRoute
|
class GuestRoute
|
||||||
@@ -11,13 +15,13 @@ class GuestRoute
|
|||||||
'prefix' => 'guest'
|
'prefix' => 'guest'
|
||||||
], function ($router) {
|
], function ($router) {
|
||||||
// Plan
|
// Plan
|
||||||
$router->get ('/plan/fetch', 'V1\\Guest\\PlanController@fetch');
|
$router->get('/plan/fetch', [PlanController::class, 'fetch']);
|
||||||
// Telegram
|
// Telegram
|
||||||
$router->post('/telegram/webhook', 'V1\\Guest\\TelegramController@webhook');
|
$router->post('/telegram/webhook', [TelegramController::class, 'webhook']);
|
||||||
// Payment
|
// Payment
|
||||||
$router->match(['get', 'post'], '/payment/notify/{method}/{uuid}', 'V1\\Guest\\PaymentController@notify');
|
$router->match(['get', 'post'], '/payment/notify/{method}/{uuid}', [PaymentController::class, 'notify']);
|
||||||
// Comm
|
// Comm
|
||||||
$router->get ('/comm/config', 'V1\\Guest\\CommController@config');
|
$router->get('/comm/config', [CommController::class, 'config']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,12 @@ class ServerRoute
|
|||||||
$router->group([
|
$router->group([
|
||||||
'prefix' => 'UniProxy',
|
'prefix' => 'UniProxy',
|
||||||
'middleware' => 'server'
|
'middleware' => 'server'
|
||||||
] ,function ($route) {
|
], function ($route) {
|
||||||
$route->get('config', [UniProxyController::class, 'config']);
|
$route->get('config', [UniProxyController::class, 'config']);
|
||||||
$route->get('user', [UniProxyController::class, 'user']);
|
$route->get('user', [UniProxyController::class, 'user']);
|
||||||
$route->post('push', [UniProxyController::class, 'push']);
|
$route->post('push', [UniProxyController::class, 'push']);
|
||||||
$route->post('alive', [UniProxyController::class, 'alive']);
|
$route->post('alive', [UniProxyController::class, 'alive']);
|
||||||
});
|
$route->get('alivelist', [UniProxyController::class, 'alivelist']);
|
||||||
$router->group([
|
|
||||||
'prefix' => 'Deepbwork',
|
|
||||||
'middleware' => 'server:vmess'
|
|
||||||
], function ($route) {
|
|
||||||
$route->get('config', [DeepbworkController::class, 'config']);
|
|
||||||
$route->get('user', [DeepbworkController::class, 'user']);
|
|
||||||
$route->post('submit', [DeepbworkController::class, 'submit']);
|
|
||||||
});
|
});
|
||||||
$router->group([
|
$router->group([
|
||||||
'prefix' => 'ShadowsocksTidalab',
|
'prefix' => 'ShadowsocksTidalab',
|
||||||
|
|||||||
@@ -12,21 +12,21 @@ class StaffRoute
|
|||||||
'middleware' => 'staff'
|
'middleware' => 'staff'
|
||||||
], function ($router) {
|
], function ($router) {
|
||||||
// Ticket
|
// Ticket
|
||||||
$router->get ('/ticket/fetch', 'V1\\Staff\\TicketController@fetch');
|
// $router->get ('/ticket/fetch', 'V1\\Staff\\TicketController@fetch');
|
||||||
$router->post('/ticket/reply', 'V1\\Staff\\TicketController@reply');
|
// $router->post('/ticket/reply', 'V1\\Staff\\TicketController@reply');
|
||||||
$router->post('/ticket/close', 'V1\\Staff\\TicketController@close');
|
// $router->post('/ticket/close', 'V1\\Staff\\TicketController@close');
|
||||||
// User
|
// // User
|
||||||
$router->post('/user/update', 'V1\\Staff\\UserController@update');
|
// $router->post('/user/update', 'V1\\Staff\\UserController@update');
|
||||||
$router->get ('/user/getUserInfoById', 'V1\\Staff\\UserController@getUserInfoById');
|
// $router->get ('/user/getUserInfoById', 'V1\\Staff\\UserController@getUserInfoById');
|
||||||
$router->post('/user/sendMail', 'V1\\Staff\\UserController@sendMail');
|
// $router->post('/user/sendMail', 'V1\\Staff\\UserController@sendMail');
|
||||||
$router->post('/user/ban', 'V1\\Staff\\UserController@ban');
|
// $router->post('/user/ban', 'V1\\Staff\\UserController@ban');
|
||||||
// Plan
|
// // Plan
|
||||||
$router->get ('/plan/fetch', 'V1\\Staff\\PlanController@fetch');
|
// $router->get ('/plan/fetch', 'V1\\Staff\\PlanController@fetch');
|
||||||
// Notice
|
// // Notice
|
||||||
$router->get ('/notice/fetch', 'V1\\Admin\\NoticeController@fetch');
|
// $router->get ('/notice/fetch', 'V1\\Admin\\NoticeController@fetch');
|
||||||
$router->post('/notice/save', 'V1\\Admin\\NoticeController@save');
|
// $router->post('/notice/save', 'V1\\Admin\\NoticeController@save');
|
||||||
$router->post('/notice/update', 'V1\\Admin\\NoticeController@update');
|
// $router->post('/notice/update', 'V1\\Admin\\NoticeController@update');
|
||||||
$router->post('/notice/drop', 'V1\\Admin\\NoticeController@drop');
|
// $router->post('/notice/drop', 'V1\\Admin\\NoticeController@drop');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace App\Http\Routes\V2;
|
namespace App\Http\Routes\V2;
|
||||||
|
|
||||||
|
use App\Http\Controllers\V2\Admin\ConfigController;
|
||||||
|
use App\Http\Controllers\V2\Admin\PlanController;
|
||||||
|
use App\Http\Controllers\V2\Admin\Server\GroupController;
|
||||||
|
use App\Http\Controllers\V2\Admin\Server\RouteController;
|
||||||
|
use App\Http\Controllers\V2\Admin\Server\ManageController;
|
||||||
|
use App\Http\Controllers\V2\Admin\OrderController;
|
||||||
|
use App\Http\Controllers\V2\Admin\UserController;
|
||||||
|
use App\Http\Controllers\V2\Admin\StatController;
|
||||||
|
use App\Http\Controllers\V2\Admin\NoticeController;
|
||||||
|
use App\Http\Controllers\V2\Admin\TicketController;
|
||||||
|
use App\Http\Controllers\V2\Admin\CouponController;
|
||||||
|
use App\Http\Controllers\V2\Admin\KnowledgeController;
|
||||||
|
use App\Http\Controllers\V2\Admin\PaymentController;
|
||||||
|
use App\Http\Controllers\V2\Admin\SystemController;
|
||||||
|
use App\Http\Controllers\V2\Admin\ThemeController;
|
||||||
use Illuminate\Contracts\Routing\Registrar;
|
use Illuminate\Contracts\Routing\Registrar;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
class AdminRoute
|
class AdminRoute
|
||||||
{
|
{
|
||||||
@@ -11,10 +27,196 @@ class AdminRoute
|
|||||||
'prefix' => admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))),
|
'prefix' => admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))),
|
||||||
'middleware' => ['admin', 'log'],
|
'middleware' => ['admin', 'log'],
|
||||||
], function ($router) {
|
], function ($router) {
|
||||||
|
// Config
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'config'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/fetch', [ConfigController::class, 'fetch']);
|
||||||
|
$router->post('/save', [ConfigController::class, 'save']);
|
||||||
|
$router->get('/getEmailTemplate', [ConfigController::class, 'getEmailTemplate']);
|
||||||
|
$router->get('/getThemeTemplate', [ConfigController::class, 'getThemeTemplate']);
|
||||||
|
$router->post('/setTelegramWebhook', [ConfigController::class, 'setTelegramWebhook']);
|
||||||
|
$router->post('/testSendMail', [ConfigController::class, 'testSendMail']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Plan
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'plan'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/fetch', [PlanController::class, 'fetch']);
|
||||||
|
$router->post('/save', [PlanController::class, 'save']);
|
||||||
|
$router->post('/drop', [PlanController::class, 'drop']);
|
||||||
|
$router->post('/update', [PlanController::class, 'update']);
|
||||||
|
$router->post('/sort', [PlanController::class, 'sort']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Server
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'server/group'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/fetch', [GroupController::class, 'fetch']);
|
||||||
|
$router->post('/save', [GroupController::class, 'save']);
|
||||||
|
$router->post('/drop', [GroupController::class, 'drop']);
|
||||||
|
});
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'server/route'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/fetch', [RouteController::class, 'fetch']);
|
||||||
|
$router->post('/save', [RouteController::class, 'save']);
|
||||||
|
$router->post('/drop', [RouteController::class, 'drop']);
|
||||||
|
});
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'server/manage'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/getNodes', [ManageController::class, 'getNodes']);
|
||||||
|
$router->post('/sort', [ManageController::class, 'sort']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 节点更新接口
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'server/manage'
|
||||||
|
], function ($router) {
|
||||||
|
$router->post('/update', [ManageController::class, 'update']);
|
||||||
|
$router->post('/save', [ManageController::class, 'save']);
|
||||||
|
$router->post('/drop', [ManageController::class, 'drop']);
|
||||||
|
$router->post('/copy', [ManageController::class, 'copy']);
|
||||||
|
$router->post('/sort', [ManageController::class, 'sort']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Order
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'order'
|
||||||
|
], function ($router) {
|
||||||
|
$router->any('/fetch', [OrderController::class, 'fetch']);
|
||||||
|
$router->post('/update', [OrderController::class, 'update']);
|
||||||
|
$router->post('/assign', [OrderController::class, 'assign']);
|
||||||
|
$router->post('/paid', [OrderController::class, 'paid']);
|
||||||
|
$router->post('/cancel', [OrderController::class, 'cancel']);
|
||||||
|
$router->post('/detail', [OrderController::class, 'detail']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// User
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'user'
|
||||||
|
], function ($router) {
|
||||||
|
$router->any('/fetch', [UserController::class, 'fetch']);
|
||||||
|
$router->post('/update', [UserController::class, 'update']);
|
||||||
|
$router->get('/getUserInfoById', [UserController::class, 'getUserInfoById']);
|
||||||
|
$router->post('/generate', [UserController::class, 'generate']);
|
||||||
|
$router->post('/dumpCSV', [UserController::class, 'dumpCSV']);
|
||||||
|
$router->post('/user/sendMail', [UserController::class, 'sendMail']);
|
||||||
|
$router->post('/ban', [UserController::class, 'ban']);
|
||||||
|
$router->post('/resetSecret', [UserController::class, 'resetSecret']);
|
||||||
|
$router->post('/setInviteUser', [UserController::class, 'setInviteUser']);
|
||||||
|
});
|
||||||
|
|
||||||
// Stat
|
// Stat
|
||||||
$router->get ('/stat/override', 'V2\\Admin\\StatController@override');
|
$router->group([
|
||||||
$router->get ('/stat/record', 'V2\\Admin\\StatController@record');
|
'prefix' => 'stat'
|
||||||
$router->get ('/stat/ranking', 'V2\\Admin\\StatController@ranking');
|
], function ($router) {
|
||||||
|
$router->get('/getOverride', [StatController::class, 'getOverride']);
|
||||||
|
$router->get('/getStats', [StatController::class, 'getStats']);
|
||||||
|
$router->get('/getServerLastRank', [StatController::class, 'getServerLastRank']);
|
||||||
|
$router->get('/getServerYesterdayRank', [StatController::class, 'getServerYesterdayRank']);
|
||||||
|
$router->get('/getOrder', [StatController::class, 'getOrder']);
|
||||||
|
$router->any('/getStatUser', [StatController::class, 'getStatUser']);
|
||||||
|
$router->get('/getRanking', [StatController::class, 'getRanking']);
|
||||||
|
$router->get('/getStatRecord', [StatController::class, 'getStatRecord']);
|
||||||
|
$router->get('/getTrafficRank', [StatController::class, 'getTrafficRank']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notice
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'notice'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/fetch', [NoticeController::class, 'fetch']);
|
||||||
|
$router->post('/save', [NoticeController::class, 'save']);
|
||||||
|
$router->post('/update', [NoticeController::class, 'update']);
|
||||||
|
$router->post('/drop', [NoticeController::class, 'drop']);
|
||||||
|
$router->post('/show', [NoticeController::class, 'show']);
|
||||||
|
$router->post('/sort', [NoticeController::class, 'sort']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ticket
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'ticket'
|
||||||
|
], function ($router) {
|
||||||
|
$router->any('/fetch', [TicketController::class, 'fetch']);
|
||||||
|
$router->post('/reply', [TicketController::class, 'reply']);
|
||||||
|
$router->post('/close', [TicketController::class, 'close']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Coupon
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'coupon'
|
||||||
|
], function ($router) {
|
||||||
|
$router->any('/fetch', [CouponController::class, 'fetch']);
|
||||||
|
$router->post('/generate', [CouponController::class, 'generate']);
|
||||||
|
$router->post('/drop', [CouponController::class, 'drop']);
|
||||||
|
$router->post('/show', [CouponController::class, 'show']);
|
||||||
|
$router->post('/update', [CouponController::class, 'update']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Knowledge
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'knowledge'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/fetch', [KnowledgeController::class, 'fetch']);
|
||||||
|
$router->get('/getCategory', [KnowledgeController::class, 'getCategory']);
|
||||||
|
$router->post('/save', [KnowledgeController::class, 'save']);
|
||||||
|
$router->post('/show', [KnowledgeController::class, 'show']);
|
||||||
|
$router->post('/drop', [KnowledgeController::class, 'drop']);
|
||||||
|
$router->post('/sort', [KnowledgeController::class, 'sort']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Payment
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'payment'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/fetch', [PaymentController::class, 'fetch']);
|
||||||
|
$router->get('/getPaymentMethods', [PaymentController::class, 'getPaymentMethods']);
|
||||||
|
$router->post('/getPaymentForm', [PaymentController::class, 'getPaymentForm']);
|
||||||
|
$router->post('/save', [PaymentController::class, 'save']);
|
||||||
|
$router->post('/drop', [PaymentController::class, 'drop']);
|
||||||
|
$router->post('/show', [PaymentController::class, 'show']);
|
||||||
|
$router->post('/sort', [PaymentController::class, 'sort']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// System
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'system'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/getSystemStatus', [SystemController::class, 'getSystemStatus']);
|
||||||
|
$router->get('/getQueueStats', [SystemController::class, 'getQueueStats']);
|
||||||
|
$router->get('/getQueueWorkload', [SystemController::class, 'getQueueWorkload']);
|
||||||
|
$router->get('/getQueueMasters', '\\Laravel\\Horizon\\Http\\Controllers\\MasterSupervisorController@index');
|
||||||
|
$router->get('/getSystemLog', [SystemController::class, 'getSystemLog']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'theme'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/getThemes', [ThemeController::class, 'getThemes']);
|
||||||
|
$router->post('/upload', [ThemeController::class, 'upload']);
|
||||||
|
$router->post('/delete', [ThemeController::class, 'delete']);
|
||||||
|
$router->post('/saveThemeConfig', [ThemeController::class, 'saveThemeConfig']);
|
||||||
|
$router->post('/getThemeConfig', [ThemeController::class, 'getThemeConfig']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Plugin
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'plugin'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/getPlugins', [\App\Http\Controllers\V2\Admin\PluginController::class, 'index']);
|
||||||
|
$router->post('install', [\App\Http\Controllers\V2\Admin\PluginController::class, 'install']);
|
||||||
|
$router->post('uninstall', [\App\Http\Controllers\V2\Admin\PluginController::class, 'uninstall']);
|
||||||
|
$router->post('enable', [\App\Http\Controllers\V2\Admin\PluginController::class, 'enable']);
|
||||||
|
$router->post('disable', [\App\Http\Controllers\V2\Admin\PluginController::class, 'disable']);
|
||||||
|
$router->get('config', [\App\Http\Controllers\V2\Admin\PluginController::class, 'getConfig']);
|
||||||
|
$router->post('config', [\App\Http\Controllers\V2\Admin\PluginController::class, 'updateConfig']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
app/Http/Routes/V2/PassportRoute.php
Normal file
25
app/Http/Routes/V2/PassportRoute.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Http\Routes\V2;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Routing\Registrar;
|
||||||
|
|
||||||
|
class PassportRoute
|
||||||
|
{
|
||||||
|
public function map(Registrar $router)
|
||||||
|
{
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'passport'
|
||||||
|
], function ($router) {
|
||||||
|
// Auth
|
||||||
|
$router->post('/auth/register', 'V1\\Passport\\AuthController@register');
|
||||||
|
$router->post('/auth/login', 'V1\\Passport\\AuthController@login');
|
||||||
|
$router->get ('/auth/token2Login', 'V1\\Passport\\AuthController@token2Login');
|
||||||
|
$router->post('/auth/forget', 'V1\\Passport\\AuthController@forget');
|
||||||
|
$router->post('/auth/getQuickLoginUrl', 'V1\\Passport\\AuthController@getQuickLoginUrl');
|
||||||
|
$router->post('/auth/loginWithMailLink', 'V1\\Passport\\AuthController@loginWithMailLink');
|
||||||
|
// Comm
|
||||||
|
$router->post('/comm/sendEmailVerify', 'V1\\Passport\\CommController@sendEmailVerify');
|
||||||
|
$router->post('/comm/pv', 'V1\\Passport\\CommController@pv');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user